[Rt-commit] rt branch, 4.2/custom-field-groupings, created. rt-4.0.8-452-g312fa9b

Alex Vandiver alexmv at bestpractical.com
Tue Nov 13 18:01:45 EST 2012


The branch, 4.2/custom-field-groupings has been created
        at  312fa9b0fed961cd9b947ef430ab04fcdc7f6dc7 (commit)

- Log -----------------------------------------------------------------
commit a21af6019896d6441ea7741f15b27b6500e6a3f8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Aug 22 22:46:12 2012 +0400

    ::Test::Web->dom method that return Mojo::DOM
    
    Code with it looks sweet.
    
    It's a big module, but no production code will
    need it as it only required when --with-tests-deps
    configure option is used. We have plenty of modules
    required only for tests.
    
    Conflicts:
    	lib/RT/Test/Web.pm

diff --git a/lib/RT/Test/Web.pm b/lib/RT/Test/Web.pm
index 8fae537..f36ee23 100644
--- a/lib/RT/Test/Web.pm
+++ b/lib/RT/Test/Web.pm
@@ -393,6 +393,14 @@ sub auth_header {
         MIME::Base64::encode( join(":", @_) );
 }
 
+sub dom {
+    my $self = shift;
+    Carp::croak("Can not get DOM, not HTML repsone")
+        unless $self->is_html;
+    require Mojo::DOM;
+    return Mojo::DOM->new( $self->content );
+}
+
 sub DESTROY {
     my $self = shift;
     if ( !$RT::Test::Web::DESTROY++ ) {
diff --git a/sbin/rt-test-dependencies.in b/sbin/rt-test-dependencies.in
index baf6864..3a72798 100644
--- a/sbin/rt-test-dependencies.in
+++ b/sbin/rt-test-dependencies.in
@@ -299,6 +299,7 @@ Log::Dispatch::Perl
 Test::WWW::Mechanize::PSGI
 Plack::Middleware::Test::StashWarnings 0.06
 Test::LongString
+Mojo::DOM
 .
 
 $deps{'FASTCGI'} = [ text_to_hash( << '.') ];

commit adc2789f4883b766d8c9b3c95afe083ea9a64bcc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Aug 24 04:09:24 2012 +0400

    use messages returned by ValidateCustomFields
    
    ValidateCustomFields component returns validation
    errors in list context for a while. We can use it
    instead of looping through custom fields once again.
    
    Prefix messages with custom field name as we did in
    every caller.

diff --git a/share/html/Elements/ValidateCustomFields b/share/html/Elements/ValidateCustomFields
index 922b885..855f460 100644
--- a/share/html/Elements/ValidateCustomFields
+++ b/share/html/Elements/ValidateCustomFields
@@ -90,7 +90,7 @@ while ( my $CF = $CustomFields->Next ) {
                     my $msg =
                       loc( "Input can not be parsed as an IP address" );
                     $m->notes( ( 'InvalidField-' . $CF->Id ) => $msg );
-                    push @res, $msg;
+                    push @res, loc($CF->Name) .': '. $msg;
                     $valid = 0;
                 }
             }
@@ -101,7 +101,7 @@ while ( my $CF = $CustomFields->Next ) {
                     my $msg =
                       loc( "Input can not be parsed as an IP address range" );
                     $m->notes( ( 'InvalidField-' . $CF->Id ) => $msg );
-                    push @res, $msg;
+                    push @res, loc($CF->Name) .': '. $msg;
                     $valid = 0;
                 }
             }
@@ -111,7 +111,7 @@ while ( my $CF = $CustomFields->Next ) {
 
         my $msg = loc("Input must match [_1]", $CF->FriendlyPattern);
         $m->notes( ('InvalidField-' . $CF->Id) => $msg );
-        push @res, $msg;
+        push @res, loc($CF->Name) .': '. $msg;
         $valid = 0;
     }
 }
diff --git a/share/html/SelfService/Create.html b/share/html/SelfService/Create.html
index e28ab0f..097cad4 100644
--- a/share/html/SelfService/Create.html
+++ b/share/html/SelfService/Create.html
@@ -121,29 +121,29 @@ $Queue => undef
 my @results;
 my $queue_obj = RT::Queue->new($session{'CurrentUser'});
 $queue_obj->Load($Queue);
-my $CFs = $queue_obj->TicketCustomFields();
-my $ValidCFs = $m->comp(
-    '/Elements/ValidateCustomFields',
-    CustomFields => $CFs,
-    ARGSRef => \%ARGS
-);
 
 my $skip_create = 0;
+
+{
+    my ($status, @msg) = $m->comp(
+        '/Elements/ValidateCustomFields',
+        CustomFields => $queue_obj->TicketCustomFields,
+        ARGSRef => \%ARGS
+    );
+    unless ($status) {
+        push @results, @msg;
+        $skip_create = 1;
+    }
+}
+
 $m->callback( CallbackName => 'BeforeCreate', ARGSRef => \%ARGS, skip_create => \$skip_create, results => \@results );
 
 if ( defined($ARGS{'id'}) and $ARGS{'id'} eq 'new' ) { # new ticket?
-    if ( $ValidCFs && !$skip_create ) {
+    if ( !$skip_create ) {
         $m->comp('Display.html', %ARGS);
         $RT::Logger->crit("After display call; error is $@");
         $m->abort();
     }
-    elsif ( !$ValidCFs ) {
-        # Invalid CFs
-        while (my $CF = $CFs->Next) {
-            my $msg = $m->notes('InvalidField-' . $CF->Id) or next;
-            push @results, $CF->Name . ': ' . $msg;
-        }
-    }
 }
 
 </%init>
diff --git a/share/html/Ticket/Update.html b/share/html/Ticket/Update.html
index 14d13ad..d3bc54a 100644
--- a/share/html/Ticket/Update.html
+++ b/share/html/Ticket/Update.html
@@ -284,21 +284,17 @@ if ( $ARGS{'SubmitTicket'} ) {
     my @squelchlist = grep {not $checked{$_}} split /,/, ($ARGS{'TxnRecipients'}||'');
     $ARGS{'SquelchMailTo'} = \@squelchlist if @squelchlist;
 
-    my $CFs = $TicketObj->TransactionCustomFields;
-    my $ValidCFs = $m->comp(
+    my ($status, @msg) = $m->comp(
         '/Elements/ValidateCustomFields',
-        CustomFields => $CFs,
+        CustomFields => $TicketObj->TransactionCustomFields,
         NamePrefix => "Object-RT::Transaction--CustomField-",
         ARGSRef => \%ARGS
     );
-    unless ( $ValidCFs ) {
+    unless ( $status ) {
+        push @results, @msg;
         $checks_failure = 1;
-        while (my $CF = $CFs->Next) {
-            my $msg = $m->notes('InvalidField-' . $CF->Id) or next;
-            push @results, loc($CF->Name) . ': ' . $msg;
-        }
     }
-    my $status = $m->comp('/Elements/GnuPG/SignEncryptWidget:Check',
+    $status = $m->comp('/Elements/GnuPG/SignEncryptWidget:Check',
         self      => $gnupg_widget,
         TicketObj => $TicketObj,
     );
diff --git a/share/html/m/ticket/create b/share/html/m/ticket/create
index b42787d..b460488 100644
--- a/share/html/m/ticket/create
+++ b/share/html/m/ticket/create
@@ -147,14 +147,6 @@ $m->callback( QueueObj => $QueueObj, title => \$title, results => \@results, ARG
 
 $QueueObj->Disabled && Abort(loc("Cannot create tickets in a disabled queue."));
 
-my $CFs = $QueueObj->TicketCustomFields();
-
-my $ValidCFs = $m->comp(
-    '/Elements/ValidateCustomFields',
-    CustomFields => $CFs,
-    ARGSRef => \%ARGS
-);
-
 # deal with deleting uploaded attachments
 foreach my $key (keys %ARGS) {
     if ($key =~ m/^DeleteAttach-(.+)$/) {
@@ -183,6 +175,18 @@ unless (keys %{$session{'Attachments'}} and $ARGS{'id'} eq 'new') {
 
 my $checks_failure = 0;
 
+{
+    my ($status, @msg) = $m->comp(
+        '/Elements/ValidateCustomFields',
+        CustomFields => $QueueObj->TicketCustomFields,
+        ARGSRef      => \%ARGS
+    );
+    unless ( $status ) {
+        $checks_failure = 1;
+        push @results, @msg;
+    }
+}
+
 my $gnupg_widget = $m->comp('/Elements/GnuPG/SignEncryptWidget:new', Arguments => \%ARGS );
 $m->comp( '/Elements/GnuPG/SignEncryptWidget:Process',
     self      => $gnupg_widget,
@@ -220,18 +224,11 @@ $m->callback( CallbackName => 'BeforeCreate', ARGSRef => \%ARGS, skip_create =>
               checks_failure => $checks_failure, results => \@results );
 
 if ((!exists $ARGS{'AddMoreAttach'}) and (defined($ARGS{'id'}) and $ARGS{'id'} eq 'new')) { # new ticket?
-    if ( $ValidCFs && !$checks_failure && !$skip_create ) {
+    if ( !$checks_failure && !$skip_create ) {
         $m->comp('show', %ARGS);
         $RT::Logger->crit("After display call; error is $@");
         $m->abort();
     }
-    elsif ( !$ValidCFs ) {
-        # Invalid CFs
-        while (my $CF = $CFs->Next) {
-            my $msg = $m->notes('InvalidField-' . $CF->Id) or next;
-            push @results, $CF->Name . ': ' . $msg;
-        }
-    }
 }
 
 

commit 00de061042eb2307315d192ea3acb7eda0ea975a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Aug 25 02:06:23 2012 +0400

    names for forms on Ticket's pages
    
    testing is easier and it's more consistent to have
    them like on other pages.

diff --git a/share/html/Ticket/ModifyDates.html b/share/html/Ticket/ModifyDates.html
index c85d2f4..0d90ddd 100644
--- a/share/html/Ticket/ModifyDates.html
+++ b/share/html/Ticket/ModifyDates.html
@@ -51,7 +51,7 @@
 % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $TicketObj);
 <& /Elements/ListActions, actions => \@results &>
 
-<form method="post" action="ModifyDates.html">
+<form method="post" action="ModifyDates.html" name="TicketDates">
 % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS );
 <input type="hidden" class="hidden" name="id" value="<%$TicketObj->Id%>" />
 <&| /Widgets/TitleBox,title => loc('Modify dates for ticket # [_1]', $TicketObj->Id), class=> 'ticket-info-dates' &>
diff --git a/share/html/Ticket/ModifyPeople.html b/share/html/Ticket/ModifyPeople.html
index 56db2bb..9ce530d 100644
--- a/share/html/Ticket/ModifyPeople.html
+++ b/share/html/Ticket/ModifyPeople.html
@@ -51,7 +51,7 @@
 % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $Ticket);
 <& /Elements/ListActions, actions => \@results &>
 
-<form method="post" action="ModifyPeople.html">
+<form method="post" action="ModifyPeople.html" name="TicketPeople">
 <input type="hidden" class="hidden" name="id" value="<%$Ticket->Id%>" />
 % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS );
 <&| /Widgets/TitleBox, title => loc('Modify people related to ticket #[_1]', $Ticket->Id),   width => "100%", color=> "#333399", class=>'ticket-info-people' &>

commit 3ba2cb40cb161c1272b5b348d10d1588c4d557f7
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 1 02:55:58 2012 +0400

    add Groups related methods to RT::CustomField[s]
    
    We will have several built in groups for each object,
    for example Basics, People, Dates, etc. for Tickets.
    
    Now assigning custom fields to groups is in config,
    but we should move it to UI.

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 43618c5..758d77f 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2716,4 +2716,13 @@ with L</Lifecycles> (see L</Labeling and defining actions>).
 
 =cut
 
+Set(%CustomFieldGroups,
+    'RT::Ticket' => {
+        Basics => [],
+        Dates => [],
+        People => [],
+        Links   => [],
+    },
+);
+
 1;
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index a409c3a..1ca44cc 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1238,6 +1238,51 @@ sub CollectionClassFromLookupType {
     return $collection_class;
 }
 
+
+sub Group {
+
+
+}
+
+sub Groups {
+    my $self = shift;
+    my $record = shift;
+
+    my $record_class = ref($record) || $record || '';
+    $record_class = $self->RecordClassFromLookupType
+        if !$record_class && $self->id;
+
+    my $config = RT->Config->Get('CustomFieldGroups');
+    my @groups;
+    if ( $record_class ) {
+        @groups = keys %{ $config->{$record_class} };
+    } else {
+        @groups = map { keys %$_ } values %$config;
+    }
+
+    my %seen;
+    return
+        sort { lc($a) cmp lc($b) }
+        grep defined && length && !$seen{lc $_}++,
+        @groups;
+}
+
+my %BUILTIN_GROUPS = (
+    'RT::Ticket' => { map { $_ => 1 } qw(Basics Dates Links People) },
+);
+$BUILTIN_GROUPS{''} = { map { %$_ } values %BUILTIN_GROUPS  };
+
+sub CustomGroups {
+    my $self = shift;
+    my $record = shift;
+
+    my $record_class = ref($record) || $record || '';
+    $record_class = $self->RecordClassFromLookupType
+        if !$record_class && $self->id;
+
+    return grep !$BUILTIN_GROUPS{$record_class}{$_}, $self->Groups( $record_class );
+}
+
 =head1 ApplyGlobally
 
 Certain custom fields (users, groups) should only be applied globally
diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index d4a5bc7..cbc2686 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -96,6 +96,36 @@ sub _Init {
     return ( $self->SUPER::_Init(@_) );
 }
 
+sub LimitToGroup {
+    my $self = shift;
+    my $group = shift;
+
+    if ( $group ) {
+        my $list = RT->Config->Get('CustomFieldGroups')->{'RT::Ticket'}{$group};
+        unless ( $list && @$list ) {
+            return $self->Limit( FIELD => 'id', VALUE => 0, ENTRYAGGREGATOR => 'AND' );
+        }
+        foreach ( @$list ) {
+            $self->Limit( FIELD => 'Name', VALUE => $_ );
+        }
+    } else {
+        my @list = map {@$_} grep defined && ref $_,
+            values %{ RT->Config->Get('CustomFieldGroups')->{'RT::Ticket'} };
+
+        return unless @list;
+        foreach ( @list ) {
+            $self->Limit(
+                FIELD => 'Name',
+                OPERATOR => '!=',
+                VALUE => $_,
+                ENTRYAGGREGATOR => 'AND',
+            );
+        }
+
+    }
+    return;
+}
+
 
 =head2 LimitToLookupType
 

commit 2091eb0b17cabf098826618e9c5d0a2d8f349d9c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 1 02:59:51 2012 +0400

    Group argument in {Show,Edit}CustomFields comps
    
    If argument is defined then show/edit only custom fields from
    this particular group.

diff --git a/share/html/Elements/ShowCustomFields b/share/html/Elements/ShowCustomFields
index 6059f4e..1dfb171 100644
--- a/share/html/Elements/ShowCustomFields
+++ b/share/html/Elements/ShowCustomFields
@@ -83,6 +83,8 @@ $m->callback(
     CustomFields => $CustomFields,
 );
 
+$CustomFields->LimitToGroup($Group) if defined $Group;
+
 # don't print anything if there is no custom fields
 return unless $CustomFields->First;
 $CustomFields->GotoFirstItem;
@@ -127,5 +129,6 @@ my $print_value = sub {
 <%ARGS>
 $Object => undef
 $CustomFields => $Object->CustomFields
+$Group => undef
 $Table => 1
 </%ARGS>
diff --git a/share/html/Ticket/Elements/EditCustomFields b/share/html/Ticket/Elements/EditCustomFields
index a449f45..d0da698 100644
--- a/share/html/Ticket/Elements/EditCustomFields
+++ b/share/html/Ticket/Elements/EditCustomFields
@@ -90,6 +90,8 @@ if ($TicketObj && !$OnCreate) {
     $NamePrefix .= "Object-RT::Ticket--CustomField-";
 }
 
+$CustomFields->LimitToGroup( $Group ) if defined $Group;
+
 $m->callback( %ARGS, CallbackName => 'MassageCustomFields', CustomFields => $CustomFields );
 
 $AsTable ||= $InTable;
@@ -108,6 +110,7 @@ $NamePrefix => ''
 $TicketObj => undef
 $QueueObj => undef
 $OnCreate => undef
+$Group => undef
 $DefaultsFromTopArguments => 1
 $AsTable => 0
 $InTable => 0

commit b7fdc34dafb3681bf7eeb2776af4a59103ace327
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 1 03:24:09 2012 +0400

    pass all arguments from Tickets's ShowCustomFields
    
    It's a wrapper for /Elements/ShowCustomFields and
    there is no reason not to pass everything

diff --git a/share/html/Ticket/Elements/ShowCustomFields b/share/html/Ticket/Elements/ShowCustomFields
index 4a06043..179f531 100644
--- a/share/html/Ticket/Elements/ShowCustomFields
+++ b/share/html/Ticket/Elements/ShowCustomFields
@@ -45,7 +45,7 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<& /Elements/ShowCustomFields, Object => $Ticket &>
+<& /Elements/ShowCustomFields, %ARGS, Object => $Ticket &>
 <%ARGS>
 $Ticket => undef
 </%ARGS>

commit f885d11431af536fea00bb376f25e1bc0b7c975c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 1 03:29:47 2012 +0400

    show custom fields in different places in UI

diff --git a/share/html/Elements/ShowLinks b/share/html/Elements/ShowLinks
index 61628de..998e2b5 100644
--- a/share/html/Elements/ShowLinks
+++ b/share/html/Elements/ShowLinks
@@ -138,6 +138,7 @@ while ( my $link = $depends_on->Next ) {
 </ul>
     </td>
   </tr>
+  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Group => 'Links', Table => 0 &>
 % # Allow people to add more rows to the table
 % $m->callback( %ARGS );
 </table>
diff --git a/share/html/Ticket/Elements/ShowBasics b/share/html/Ticket/Elements/ShowBasics
index 0ecb6d8..22fe565 100644
--- a/share/html/Ticket/Elements/ShowBasics
+++ b/share/html/Ticket/Elements/ShowBasics
@@ -83,6 +83,7 @@
     <td class="value"><& ShowQueue, Ticket => $Ticket, QueueObj => $Ticket->QueueObj &></td>
   </tr>
 % }
+  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Group => 'Basics', Table => 0 &>
 % $m->callback( %ARGS, CallbackName => 'EndOfList', TicketObj => $Ticket );
 </table>
 <%ARGS>
diff --git a/share/html/Ticket/Elements/ShowDates b/share/html/Ticket/Elements/ShowDates
index e731b92..4430a57 100644
--- a/share/html/Ticket/Elements/ShowDates
+++ b/share/html/Ticket/Elements/ShowDates
@@ -84,6 +84,7 @@
     <td class="value"><% $UpdatedString | n %></td>
 % }
   </tr>
+  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Group => 'Dates', Table => 0 &>
 % $m->callback( %ARGS, CallbackName => 'EndOfList', TicketObj => $Ticket );
 </table>
 <%ARGS>
diff --git a/share/html/Ticket/Elements/ShowPeople b/share/html/Ticket/Elements/ShowPeople
index 8047aff..5fb399e 100644
--- a/share/html/Ticket/Elements/ShowPeople
+++ b/share/html/Ticket/Elements/ShowPeople
@@ -66,6 +66,7 @@
     <td class="labeltop"><&|/l&>AdminCc</&>:</td>
     <td class="value"><& ShowGroupMembers, Group => $Ticket->AdminCc, Ticket => $Ticket &></td>
   </tr>
+  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Group => 'People', Table => 0 &>
 </table>
 <%INIT>
 </%INIT>
diff --git a/share/html/Ticket/Elements/ShowSummary b/share/html/Ticket/Elements/ShowSummary
index 421dd9a..189d29b 100644
--- a/share/html/Ticket/Elements/ShowSummary
+++ b/share/html/Ticket/Elements/ShowSummary
@@ -53,11 +53,13 @@
         (($can_modify || $can_modify_cf) ? (title_href => RT->Config->Get('WebPath')."/Ticket/Modify.html?id=".$Ticket->Id) : ()),
         class => 'ticket-info-basics',
     &><& /Ticket/Elements/ShowBasics, Ticket => $Ticket &></&>
+% foreach my $group ( RT::CustomField->CustomGroups($Ticket), '' ) {
     <&| /Widgets/TitleBox, title => loc('Custom Fields'),
         (($can_modify || $can_modify_cf) ? (title_href => RT->Config->Get('WebPath')."/Ticket/Modify.html?id=".$Ticket->Id) : ()),
         class => 'ticket-info-cfs',
         hide_empty => 1,
-    &><& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket &></&>
+    &><& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Group => $group &></&>
+% }
 
     <&| /Widgets/TitleBox, title => loc('People'),
         (($can_modify || $can_modify_owner) ? (title_href => RT->Config->Get('WebPath')."/Ticket/ModifyPeople.html?id=".$Ticket->Id) : ()),

commit 0ff8960ff75d4c91dc980f24e8ac34a8683efc43
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 1 03:31:11 2012 +0400

    don't render any wrappers if content is empty
    
    this allows us to hide whole title boxes without content,
    but it fails if anything is rendered even an empty div.

diff --git a/share/html/Ticket/Elements/EditCustomFields b/share/html/Ticket/Elements/EditCustomFields
index d0da698..fa76ca1 100644
--- a/share/html/Ticket/Elements/EditCustomFields
+++ b/share/html/Ticket/Elements/EditCustomFields
@@ -94,6 +94,10 @@ $CustomFields->LimitToGroup( $Group ) if defined $Group;
 
 $m->callback( %ARGS, CallbackName => 'MassageCustomFields', CustomFields => $CustomFields );
 
+# don't print anything if there is no custom fields
+return unless $CustomFields->First;
+$CustomFields->GotoFirstItem;
+
 $AsTable ||= $InTable;
 my $FIELD = $AsTable ? 'tr' : 'div';
 my $CELL  = $AsTable ? 'td' : 'div';

commit b2307dbba4df001a8f55a6d4a630e097921a79d3
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 1 03:36:46 2012 +0400

    split editing CFs into groups
    
    we place CF editors links/dates/people pages and
    modify/modify all/create we place them into different
    boxes.

diff --git a/share/html/Elements/EditLinks b/share/html/Elements/EditLinks
index b3075a0..f063ccc 100644
--- a/share/html/Elements/EditLinks
+++ b/share/html/Elements/EditLinks
@@ -158,6 +158,9 @@
     <td class="label"><& ShowRelationLabel, id => $id, Label => loc('Referred to by'), Relation => 'ReferredToBy' &>:</td>
     <td class="entry"> <input name="RefersTo-<%$id%>" /></td>
   </tr>
+% if ( $Object->isa('RT::Ticket') ) {
+  <& /Ticket/Elements/EditCustomFields, TicketObj => $Object, Group => 'Links', InTable => 1 &>
+% }
 % $m->callback( CallbackName => 'NewLink' );
 </table>
 </td>
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index d0d99ab..e0d5de4 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -101,11 +101,18 @@
 
 % $m->callback( CallbackName => 'AfterOwner', ARGSRef => \%ARGS );
 
-      <& /Ticket/Elements/EditCustomFields, %ARGS, QueueObj => $QueueObj, InTable => 1 &>
+      <& /Ticket/Elements/EditCustomFields, %ARGS, QueueObj => $QueueObj, Group => 'Basics', InTable => 1 &>
       <& /Ticket/Elements/EditTransactionCustomFields, %ARGS, QueueObj => $QueueObj, InTable => 1 &>
     </table>
   </&>
 % $m->callback( CallbackName => 'AfterBasics', QueueObj => $QueueObj, ARGSRef => \%ARGS );
+
+% foreach my $group ( RT::CustomField->CustomGroups( 'RT::Ticket' ), '' ) {
+<&| /Widgets/TitleBox, title => $group? loc($group) : loc('Custom Fields'), class => 'ticket-info-cfs', hide_empty => 1 &>
+<& Elements/EditCustomFields, %ARGS, QueueObj => $QueueObj, DefaultsFromTopArguments => 0, Group => $group &>
+</&>
+% }
+
 </div>
 
 <div id="ticket-create-message">
@@ -152,6 +159,8 @@
   </td>
 </tr>
 
+<& Elements/EditCustomFields, QueueObj => $QueueObj, Group => 'People', InTable => 1 &>
+
 <tr>
 <td class="label">
 <&|/l&>Subject</&>:
@@ -238,6 +247,7 @@
 <table>
 <tr><td class="label"><&|/l&>Starts</&>:</td><td><& /Elements/SelectDate, Name => "Starts", Default => $ARGS{Starts} || '' &></td></tr>
 <tr><td class="label"><&|/l&>Due</&>:</td><td><& /Elements/SelectDate, Name => "Due", Default => $ARGS{Due} || '' &></td></tr>
+<& Elements/EditCustomFields, QueueObj => $QueueObj, Group => 'Dates', InTable => 1 &>
 </table>
 </&>
 </div>
@@ -257,8 +267,7 @@
 <tr><td class="label"><&|/l&>Children</&></td><td><input size="10" name="MemberOf-new" value="<% $ARGS{'MemberOf-new'} || '' %>" /></td></tr>
 <tr><td class="label"><&|/l&>Refers to</&></td><td><input size="10" name="new-RefersTo" value="<% $ARGS{'new-RefersTo'} || '' %>" /></td></tr>
 <tr><td class="label"><&|/l&>Referred to by</&></td><td><input size="10" name="RefersTo-new" value="<% $ARGS{'RefersTo-new'} || '' %>" /></td></tr>
-
-
+<& Elements/EditCustomFields, QueueObj => $QueueObj, Group => 'Links', InTable => 1 &>
 </table>
 </&>
 </div>
diff --git a/share/html/Ticket/Elements/EditDates b/share/html/Ticket/Elements/EditDates
index fb23981..e4c42c0 100644
--- a/share/html/Ticket/Elements/EditDates
+++ b/share/html/Ticket/Elements/EditDates
@@ -70,6 +70,7 @@
       <& /Elements/SelectDate, menu_prefix => 'Due', current => 0 &> (<% $TicketObj->DueObj->AsString %>)
     </td>
   </tr>
+  <& EditCustomFields, TicketObj => $TicketObj, Group => 'Dates', InTable => 1 &>
 % $m->callback( %ARGS, CallbackName => 'EndOfList', Ticket => $TicketObj );
 </table>
 <%ARGS>
diff --git a/share/html/Ticket/Elements/EditPeople b/share/html/Ticket/Elements/EditPeople
index 09cf6f3..d3d2844 100644
--- a/share/html/Ticket/Elements/EditPeople
+++ b/share/html/Ticket/Elements/EditPeople
@@ -66,17 +66,29 @@
 <h3><&|/l&>Owner</&></h3>
 <&|/l&>Owner</&>: <& /Elements/SelectOwner, Name => 'Owner', QueueObj => $Ticket->QueueObj, TicketObj => $Ticket, Default => $Ticket->OwnerObj->Id, DefaultValue => 0&>
 <h3><&|/l&>Current watchers</&></h3>
+<i><&|/l&>(Check box to delete)</&></i><br />
 
-<&|/l&>Requestors</&>:
-<& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->Requestors &>
+<table>
 
-<&|/l&>Cc</&>:
-<& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->Cc &>
+<tr>
+  <td class="label"><&|/l&>Requestors</&>:</td>
+  <td class="value"><& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->Requestors &></td>
+</tr>
 
-<&|/l&>Administrative Cc</&>:
-<& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->AdminCc &>
+<tr>
+  <td class="label"><&|/l&>Cc</&>:</td>
+  <td class="value"><& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->Cc &></td>
+</tr>
+
+<tr>
+  <td class="label"><&|/l&>Administrative Cc</&>:</td>
+  <td class="value"><& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->AdminCc &></td>
+</tr>
+
+<& EditCustomFields, TicketObj => $Ticket, Group => 'People', InTable => 1 &>
+
+</table>
 
-<i><&|/l&>(Check box to delete)</&></i><br />
 </td>
 </tr>
 </table>
diff --git a/share/html/Ticket/Modify.html b/share/html/Ticket/Modify.html
index 752b99d..2bfe3f9 100644
--- a/share/html/Ticket/Modify.html
+++ b/share/html/Ticket/Modify.html
@@ -57,10 +57,16 @@
 
 <&| /Widgets/TitleBox, title => loc('Modify ticket #[_1]',$TicketObj->Id), class=>'ticket-info-basics' &>
 <& Elements/EditBasics, TicketObj => $TicketObj &>
-<& Elements/EditCustomFields, TicketObj => $TicketObj, DefaultsFromTopArguments => 0 &>
+<& Elements/EditCustomFields, TicketObj => $TicketObj, DefaultsFromTopArguments => 0, Group => 'Basics' &>
 </&>
 % $m->callback( CallbackName => 'AfterBasics', Ticket => $TicketObj );
 
+% foreach my $group ( RT::CustomField->CustomGroups( $TicketObj ), '' ) {
+<&| /Widgets/TitleBox, title => $group? loc($group) : loc('Custom Fields'), class=>'ticket-info-cfs', hide_empty => 1 &>
+<& Elements/EditCustomFields, TicketObj => $TicketObj, DefaultsFromTopArguments => 0, Group => $group &>
+</&>
+% }
+
 <& /Elements/Submit, Name => 'SubmitTicket', Label => loc('Save Changes'), Caption => loc("If you've updated anything above, be sure to"), color => "#993333" &>
 </form>
 <%INIT>
diff --git a/share/html/Ticket/ModifyAll.html b/share/html/Ticket/ModifyAll.html
index 91005a2..b49e901 100644
--- a/share/html/Ticket/ModifyAll.html
+++ b/share/html/Ticket/ModifyAll.html
@@ -57,11 +57,16 @@
 
 <&| /Widgets/TitleBox, title => loc('Modify ticket # [_1]', $Ticket->Id), class=>'ticket-info-basics' &>
 <& Elements/EditBasics, TicketObj => $Ticket &>
-<& Elements/EditCustomFields, TicketObj => $Ticket &>
+<& Elements/EditCustomFields, TicketObj => $Ticket, Group => 'Basics' &>
 </&>
 
 % $m->callback(CallbackName => 'AfterBasics', Ticket => $Ticket);
-<br />
+
+% foreach my $group ( RT::CustomField->CustomGroups( $Ticket ), '' ) {
+<&| /Widgets/TitleBox, title => $group? loc($group) : loc('CustomFields'), class => 'ticket-info-cfs', hide_empty => 1 &>
+<& Elements/EditCustomFields, TicketObj => $Ticket, Group => $group &>
+</&>
+% }
 
 <&| /Widgets/TitleBox, title => loc('Dates'), class=>'ticket-info-dates'&>
 <& Elements/EditDates, TicketObj => $Ticket &>

commit aad27a3c3aa4a0ea6639fd6f9de1b9275bbff037
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 1 03:38:50 2012 +0400

    add CFs processing to pages where we need it

diff --git a/share/html/Ticket/ModifyLinks.html b/share/html/Ticket/ModifyLinks.html
index 9dceb2a..5343312 100644
--- a/share/html/Ticket/ModifyLinks.html
+++ b/share/html/Ticket/ModifyLinks.html
@@ -72,6 +72,7 @@ my $Ticket = LoadTicket($id);
 my @results;  
 $m->callback( TicketObj => $Ticket, ARGSRef => \%ARGS, Results => \@results );
 push @results, ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS );
+push @results, ProcessObjectCustomFieldUpdates( TicketObj => $Ticket, ARGSRef => \%ARGS );
 $Ticket->ApplyTransactionBatch;
     
 </%INIT>
diff --git a/share/html/Ticket/ModifyPeople.html b/share/html/Ticket/ModifyPeople.html
index 9ce530d..6294a4b 100644
--- a/share/html/Ticket/ModifyPeople.html
+++ b/share/html/Ticket/ModifyPeople.html
@@ -100,6 +100,8 @@ $Ticket->SquelchMailTo($_)
 unless ($OnlySearchForPeople or $OnlySearchForGroup) {
     push @results, ProcessTicketBasics( TicketObj => $Ticket, ARGSRef => \%ARGS);
     push @results, ProcessTicketWatchers( TicketObj => $Ticket, ARGSRef => \%ARGS);
+    push @results, ProcessObjectCustomFieldUpdates( TicketObj => $Ticket, ARGSRef => \%ARGS );
+
     $Ticket->ApplyTransactionBatch;
 }
 

commit 096c122575c5ed90b9c79f754197fa138f3d18cc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 1 04:12:30 2012 +0400

    test custom field groups

diff --git a/t/web/cf_groups.t b/t/web/cf_groups.t
new file mode 100644
index 0000000..ca50abf
--- /dev/null
+++ b/t/web/cf_groups.t
@@ -0,0 +1,156 @@
+use strict;
+use warnings;
+
+use RT::Test tests => 67;
+
+RT->Config->Set( 'CustomFieldGroups',
+    'RT::Ticket' => {
+        Basics => ['TestBasics'],
+        Dates  => ['TestDates'],
+        People => ['TestPeople'],
+        Links  => ['TestLinks'],
+        More   => ['TestMore'],
+    },
+);
+
+my %CF;
+
+foreach my $name ( map { @$_ } values %{ RT->Config->Get('CustomFieldGroups')->{'RT::Ticket'} } ) {
+    my $cf = RT::CustomField->new( RT->SystemUser );
+    my ($id, $msg) = $cf->Create(
+        Name => $name,
+        Queue => '0',
+        Description => 'A Testing custom field',
+        Type => 'FreeformSingle',
+        Pattern => qr{^(?!bad value).*$},
+    );
+    ok $id, "custom field '$name' correctly created";
+    $CF{$name} = $cf;
+}
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+
+my ( $baseurl, $m ) = RT::Test->started_ok;
+ok $m->login, 'logged in as root';
+
+{
+    note "testing Create";
+    $m->goto_create_ticket($queue);
+
+    my $prefix = 'Object-RT::Ticket--CustomField-';
+    my $dom = $m->dom;
+    $m->form_name('TicketCreate');
+
+    my $input_name = $prefix . $CF{'TestBasics'}->id .'-Value';
+    is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page";
+    ok $dom->at(qq{.ticket-info-basics input[name="$input_name"]}), "CF is in the right place";
+    $m->field( $input_name, 'TestBasicsValue' );
+
+    $input_name = $prefix . $CF{'TestPeople'}->id .'-Value';
+    is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page";
+    ok $dom->at(qq{#ticket-create-message input[name="$input_name"]}), "CF is in the right place";
+    $m->field( $input_name, 'TestPeopleValue' );
+
+    $input_name = $prefix . $CF{'TestDates'}->id .'-Value';
+    is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page";
+    ok $dom->at(qq{.ticket-info-dates input[name="$input_name"]}), "CF is in the right place";
+    $m->field( $input_name, 'TestDatesValue' );
+
+    $input_name = $prefix . $CF{'TestLinks'}->id .'-Value';
+    is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page";
+    ok $dom->at(qq{.ticket-info-links input[name="$input_name"]}), "CF is in the right place";
+    $m->field( $input_name, 'TestLinksValue' );
+
+    $input_name = $prefix . $CF{'TestMore'}->id .'-Value';
+    is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page";
+    ok $dom->at(qq{.ticket-info-cfs input[name="$input_name"]}), "CF is in the right place";
+    $m->field( $input_name, 'TestMoreValue' );
+
+    $m->submit;
+
+    note "testing Display";
+    my $id = $m->get_ticket_id;
+    ok $id, "created a ticket";
+    $dom = $m->dom;
+
+    foreach my $name ( qw(Basics People Dates Links) ) {
+        my $row_id = 'CF-'. $CF{"Test$name"}->id .'-ShowRow';
+        is $dom->find(qq{#$row_id})->size, 1, "CF on the page";
+        is $dom->at(qq{#$row_id})->all_text, "Test$name: Test${name}Value", "value is set";
+        ok $dom->at(qq{.ticket-info-\L$name\E #$row_id}), "CF is in the right place";
+    }
+    {
+        my $row_id = 'CF-'. $CF{"TestMore"}->id .'-ShowRow';
+        is $dom->find(qq{#$row_id})->size, 1, "CF on the page";
+        is $dom->at(qq{#$row_id})->all_text, "TestMore: TestMoreValue", "value is set";
+        ok $dom->at(qq{.ticket-info-cfs #$row_id}), "CF is in the right place";
+    }
+
+    $prefix = 'Object-RT::Ticket-'. $id .'-CustomField-';
+
+    note "testing Basics/People/Dates/Links pages";
+    { # Basics
+        $m->follow_link_ok({id => 'page-basics'}, 'Ticket -> Basics');
+        $m->form_name("TicketModify");
+        is $m->dom->find(qq{input[name^="$prefix"][name\$="-Value"]})->size, 2,
+            "only one CF input on the page";
+        my $input_name = $prefix . $CF{'TestBasics'}->id .'-Value';
+        ok $m->dom->at(qq{.ticket-info-basics input[name="$input_name"]}),
+            "CF is in the right place";
+        $m->field( $input_name, "TestBasicsChanged" );
+        $m->click('SubmitTicket');
+        $m->content_like(qr{to TestBasicsChanged});
+
+        $m->form_name("TicketModify");
+        $m->field( $input_name, "bad value" );
+        $m->click('SubmitTicket');
+        $m->content_like(qr{Input must match});
+    }
+    { # Custom group 'More'
+        $m->follow_link_ok({id => 'page-basics'}, 'Ticket -> Basics');
+        $m->form_name("TicketModify");
+        my $input_name = $prefix . $CF{'TestMore'}->id .'-Value';
+        ok $m->dom->at(qq{.ticket-info-cfs input[name="$input_name"]}),
+            "CF is in the right place";
+        $m->field( $input_name, "TestMoreChanged" );
+        $m->click('SubmitTicket');
+        $m->content_like(qr{to TestMoreChanged});
+
+        $m->form_name("TicketModify");
+        $m->field( $input_name, "bad value" );
+        $m->click('SubmitTicket');
+        $m->content_like(qr{Input must match});
+    }
+
+    foreach my $name ( qw(People Dates Links) ) {
+        $m->follow_link_ok({id => "page-\L$name"}, "Ticket's $name page");
+        $m->form_name("Ticket$name");
+        is $m->dom->find(qq{input[name^="$prefix"][name\$="-Value"]})->size, 1,
+            "only one CF input on the page";
+        my $input_name = $prefix . $CF{"Test$name"}->id .'-Value';
+        $m->field( $input_name, "Test${name}Changed" );
+        $m->click('SubmitTicket');
+        $m->content_like(qr{to Test${name}Changed});
+
+        $m->form_name("Ticket$name");
+        $m->field( $input_name, "bad value" );
+        $m->click('SubmitTicket');
+        $m->content_like(qr{Input must match});
+    }
+
+    note "testing Jumbo";
+    $m->follow_link_ok({id => "page-jumbo"}, "Ticket's Jumbo page");
+    $dom = $m->dom;
+    $m->form_name("TicketModifyAll");
+
+    foreach my $name ( qw(Basics People Dates Links More) ) {
+        my $input_name = $prefix . $CF{"Test$name"}->id .'-Value';
+        is $dom->find(qq{input[name="$input_name"]})->size, 1,
+            "only one CF input on the page";
+        $m->field( $input_name, "Test${name}Again" );
+    }
+    $m->click('SubmitTicket');
+    foreach my $name ( qw(Basics People Dates Links More) ) {
+        $m->content_like(qr{to Test${name}Again});
+    }
+}

commit a628b55220bcc35cb4a11b3f5f3c6b24c1dc1a5b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Sep 7 16:09:38 2012 +0400

    mark BUILTIN_GROUPS as our for potential extensions

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 1ca44cc..a76a6c0 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1267,7 +1267,7 @@ sub Groups {
         @groups;
 }
 
-my %BUILTIN_GROUPS = (
+our %BUILTIN_GROUPS = (
     'RT::Ticket' => { map { $_ => 1 } qw(Basics Dates Links People) },
 );
 $BUILTIN_GROUPS{''} = { map { %$_ } values %BUILTIN_GROUPS  };

commit 3bf03025b6aad4e6bc42b6642c60e097381f6513
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Sep 7 17:15:10 2012 +0400

    put group name into title of the box

diff --git a/share/html/Ticket/Elements/ShowSummary b/share/html/Ticket/Elements/ShowSummary
index 189d29b..3dbacf1 100644
--- a/share/html/Ticket/Elements/ShowSummary
+++ b/share/html/Ticket/Elements/ShowSummary
@@ -54,7 +54,8 @@
         class => 'ticket-info-basics',
     &><& /Ticket/Elements/ShowBasics, Ticket => $Ticket &></&>
 % foreach my $group ( RT::CustomField->CustomGroups($Ticket), '' ) {
-    <&| /Widgets/TitleBox, title => loc('Custom Fields'),
+    <&| /Widgets/TitleBox,
+        title => $group? loc($group) : loc('Custom Fields'),
         (($can_modify || $can_modify_cf) ? (title_href => RT->Config->Get('WebPath')."/Ticket/Modify.html?id=".$Ticket->Id) : ()),
         class => 'ticket-info-cfs',
         hide_empty => 1,

commit 9d8c6449b854c7f87212f0bb9f825ef63db166cb
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Sep 7 17:16:01 2012 +0400

    add ticket-info-cfs-group class to title boxes

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 70154fd..7a6c7ad 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -3341,6 +3341,13 @@ sub JSON {
     RT::Interface::Web::EncodeJSON(@_);
 }
 
+sub CSSClass {
+    my $value = shift;
+    return '' unless defined $value;
+    $value =~ s/[^A-Za-z0-9_-]/_/g;
+    return $value;
+}
+
 package RT::Interface::Web;
 RT::Base->_ImportOverlays();
 
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index e0d5de4..47d46a1 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -108,7 +108,11 @@
 % $m->callback( CallbackName => 'AfterBasics', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 
 % foreach my $group ( RT::CustomField->CustomGroups( 'RT::Ticket' ), '' ) {
-<&| /Widgets/TitleBox, title => $group? loc($group) : loc('Custom Fields'), class => 'ticket-info-cfs', hide_empty => 1 &>
+<&| /Widgets/TitleBox,
+    title => $group? loc($group) : loc('Custom Fields'),
+    class => 'ticket-info-cfs '. ($group? CSSClass("ticket-info-cfs-$group") : ''),
+    hide_empty => 1
+&>
 <& Elements/EditCustomFields, %ARGS, QueueObj => $QueueObj, DefaultsFromTopArguments => 0, Group => $group &>
 </&>
 % }
diff --git a/share/html/Ticket/Elements/ShowSummary b/share/html/Ticket/Elements/ShowSummary
index 3dbacf1..a7e2184 100644
--- a/share/html/Ticket/Elements/ShowSummary
+++ b/share/html/Ticket/Elements/ShowSummary
@@ -57,7 +57,7 @@
     <&| /Widgets/TitleBox,
         title => $group? loc($group) : loc('Custom Fields'),
         (($can_modify || $can_modify_cf) ? (title_href => RT->Config->Get('WebPath')."/Ticket/Modify.html?id=".$Ticket->Id) : ()),
-        class => 'ticket-info-cfs',
+        class => 'ticket-info-cfs '. ($group? CSSClass("ticket-info-cfs-$group") : ''),
         hide_empty => 1,
     &><& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Group => $group &></&>
 % }
diff --git a/share/html/Ticket/Modify.html b/share/html/Ticket/Modify.html
index 2bfe3f9..4c9709a 100644
--- a/share/html/Ticket/Modify.html
+++ b/share/html/Ticket/Modify.html
@@ -62,7 +62,11 @@
 % $m->callback( CallbackName => 'AfterBasics', Ticket => $TicketObj );
 
 % foreach my $group ( RT::CustomField->CustomGroups( $TicketObj ), '' ) {
-<&| /Widgets/TitleBox, title => $group? loc($group) : loc('Custom Fields'), class=>'ticket-info-cfs', hide_empty => 1 &>
+<&| /Widgets/TitleBox,
+    title => $group? loc($group) : loc('Custom Fields'),
+    class => 'ticket-info-cfs '. ($group? CSSClass("ticket-info-cfs-$group") : ''),
+    hide_empty => 1,
+&>
 <& Elements/EditCustomFields, TicketObj => $TicketObj, DefaultsFromTopArguments => 0, Group => $group &>
 </&>
 % }
diff --git a/share/html/Ticket/ModifyAll.html b/share/html/Ticket/ModifyAll.html
index b49e901..a2f6561 100644
--- a/share/html/Ticket/ModifyAll.html
+++ b/share/html/Ticket/ModifyAll.html
@@ -63,7 +63,11 @@
 % $m->callback(CallbackName => 'AfterBasics', Ticket => $Ticket);
 
 % foreach my $group ( RT::CustomField->CustomGroups( $Ticket ), '' ) {
-<&| /Widgets/TitleBox, title => $group? loc($group) : loc('CustomFields'), class => 'ticket-info-cfs', hide_empty => 1 &>
+<&| /Widgets/TitleBox,
+    title => $group? loc($group) : loc('CustomFields'),
+    class => 'ticket-info-cfs '. ($group? CSSClass("ticket-info-cfs-$group") : ''),
+    hide_empty => 1,
+&>
 <& Elements/EditCustomFields, TicketObj => $Ticket, Group => $group &>
 </&>
 % }

commit 2929ca4ad68769a8e5661da175bf0459c57a0750
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Sep 7 14:03:20 2012 -0700

    Stricter checking for a valid CustomFieldGroups config when using it
    
    This avoids many broken pages due to a potentially common configuration
    error (such as I made when testing out the branch).

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index a76a6c0..2d732db 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1253,6 +1253,8 @@ sub Groups {
         if !$record_class && $self->id;
 
     my $config = RT->Config->Get('CustomFieldGroups');
+       $config = {} unless ref($config) eq 'HASH';
+
     my @groups;
     if ( $record_class ) {
         @groups = keys %{ $config->{$record_class} };
diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index cbc2686..0db4728 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -100,17 +100,20 @@ sub LimitToGroup {
     my $self = shift;
     my $group = shift;
 
+    my $config = RT->Config->Get('CustomFieldGroups');
+       $config = {} unless ref($config) eq 'HASH';
+
     if ( $group ) {
-        my $list = RT->Config->Get('CustomFieldGroups')->{'RT::Ticket'}{$group};
-        unless ( $list && @$list ) {
+        my $list = $config->{'RT::Ticket'}{$group};
+        unless ( $list and ref($list) eq 'ARRAY' and @$list ) {
             return $self->Limit( FIELD => 'id', VALUE => 0, ENTRYAGGREGATOR => 'AND' );
         }
         foreach ( @$list ) {
             $self->Limit( FIELD => 'Name', VALUE => $_ );
         }
     } else {
-        my @list = map {@$_} grep defined && ref $_,
-            values %{ RT->Config->Get('CustomFieldGroups')->{'RT::Ticket'} };
+        my @list = map {@$_} grep defined && ref($_) eq 'ARRAY',
+            values %{ $config->{'RT::Ticket'} };
 
         return unless @list;
         foreach ( @list ) {

commit 4701bd3ba4bd66f01eaaf8b205ca9968874cb17f
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Sep 7 14:28:40 2012 -0700

    Validate the CustomFieldGroups config option after the config is loaded
    
    This doesn't help direct calls to RT->Config->Set() after load
    (extensions?), but it will avoid most common configuration errors.
    
    Sanity checking the structure would be so much nicer with Moose types...

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index d293dc9..6e1a666 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -807,6 +807,34 @@ lifecycle and add transition rules; see RT_Config.pm for documentation.
 EOT
         },
     },
+    CustomFieldGroups   => {
+        Type            => 'HASH',
+        PostLoadCheck   => sub {
+            my $config = shift;
+            # use scalar context intentionally to avoid not a hash error
+            my $groups = $config->Get('CustomFieldGroups') || {};
+
+            unless (ref($groups) eq 'HASH') {
+                RT->Logger->error("Config option %CustomFieldGroups is a @{[ref $groups]} not a HASH; ignoring");
+                $groups = {};
+            }
+
+            for my $class (keys %$groups) {
+                unless (ref($groups->{$class}) eq 'HASH') {
+                    RT->Logger->error("Config option \%CustomFieldGroups{$class} is not a HASH; ignoring");
+                    delete $groups->{$class};
+                    next;
+                }
+                for my $group (keys %{ $groups->{$class} }) {
+                    unless (ref($groups->{$class}{$group}) eq 'ARRAY') {
+                        RT->Logger->error("Config option \%CustomFieldGroups{$class}{$group} is not an ARRAY; ignoring");
+                        delete $groups->{$class}{$group};
+                    }
+                }
+            }
+            $config->Set( CustomFieldGroups => %$groups );
+        },
+    },
 );
 my %OPTIONS = ();
 

commit f64323d02cb1d11d3c1db690056d853ca6d90680
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Sep 27 22:33:58 2012 +0400

    add generic /Elements/EditCustomFields

diff --git a/share/html/Ticket/Elements/EditCustomFields b/share/html/Elements/EditCustomFields
similarity index 80%
copy from share/html/Ticket/Elements/EditCustomFields
copy to share/html/Elements/EditCustomFields
index fa76ca1..f08d92c 100644
--- a/share/html/Ticket/Elements/EditCustomFields
+++ b/share/html/Elements/EditCustomFields
@@ -45,13 +45,13 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-% $m->callback( %ARGS, CallbackName => 'BeforeCustomFields' );
 % if ( $WRAP ) {
 <<% $WRAP %> class="edit-custom-fields">
 % }
 % while ( my $CustomField = $CustomFields->Next ) {
 % next unless $CustomField->CurrentUserHasRight('ModifyCustomField');
 % my $Type = $CustomField->Type || 'Unknown';
+
   <<% $FIELD %> class="edit-custom-field cftype-<% $Type %>">
     <<% $CELL %> class="cflabel">
       <span class="name"><% loc($CustomField->Name) %></span><br />
@@ -60,9 +60,8 @@
     <<% $CELL %> class="entry">
 % my $default = $m->notes('Field-' . $CustomField->Id);
 % $default ||= $ARGS{"CustomField-". $CustomField->Id };
-      <& /Elements/EditCustomField, 
+      <& /Elements/EditCustomField,
           %ARGS,
-          Object => $TicketObj,
           CustomField => $CustomField,
           NamePrefix => $NamePrefix,
           Default => $default,
@@ -78,21 +77,14 @@
 % if ( $WRAP ) {
 </<% $WRAP %>>
 % }
-% $m->callback( %ARGS, CallbackName => 'AfterCustomFields', TicketObj => $TicketObj, QueueObj => $QueueObj );
+% $m->callback( %ARGS, CallbackName => 'AfterCustomFields' );
 <%INIT>
-my $CustomFields;
-
-if ($TicketObj && !$OnCreate) {
-    $CustomFields = $TicketObj->CustomFields();
-    $NamePrefix .= "Object-RT::Ticket-".$TicketObj->Id."-CustomField-";
-} else {
-    $CustomFields = $QueueObj->TicketCustomFields();
-    $NamePrefix .= "Object-RT::Ticket--CustomField-";
-}
+$NamePrefix   ||= join '-', 'Object', ref($Object), ($Object->Id || ''), 'CustomField', '';
+$CustomFields ||= $Object->CustomFields;
 
-$CustomFields->LimitToGroup( $Group ) if defined $Group;
+$CustomFields->LimitToGroup( $Group  ) if defined $Group;
 
-$m->callback( %ARGS, CallbackName => 'MassageCustomFields', CustomFields => $CustomFields );
+$m->callback( %ARGS, CallbackName => 'MassageCustomFields', $CustomFields => $CustomFields );
 
 # don't print anything if there is no custom fields
 return unless $CustomFields->First;
@@ -110,12 +102,10 @@ if ( $AsTable ) {
 
 </%INIT>
 <%ARGS>
-$NamePrefix => ''
-$TicketObj => undef
-$QueueObj => undef
-$OnCreate => undef
-$Group => undef
-$DefaultsFromTopArguments => 1
-$AsTable => 0
+$Object
+$CustomFields => undef
+$NamePrefix   => ''
+$Group        => undef
+$AsTable => 1
 $InTable => 0
 </%ARGS>
diff --git a/share/html/Ticket/Elements/EditCustomFields b/share/html/Ticket/Elements/EditCustomFields
index fa76ca1..0c09eb7 100644
--- a/share/html/Ticket/Elements/EditCustomFields
+++ b/share/html/Ticket/Elements/EditCustomFields
@@ -45,77 +45,24 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-% $m->callback( %ARGS, CallbackName => 'BeforeCustomFields' );
-% if ( $WRAP ) {
-<<% $WRAP %> class="edit-custom-fields">
-% }
-% while ( my $CustomField = $CustomFields->Next ) {
-% next unless $CustomField->CurrentUserHasRight('ModifyCustomField');
-% my $Type = $CustomField->Type || 'Unknown';
-  <<% $FIELD %> class="edit-custom-field cftype-<% $Type %>">
-    <<% $CELL %> class="cflabel">
-      <span class="name"><% loc($CustomField->Name) %></span><br />
-      <span class="type"><% $CustomField->FriendlyType %></span>
-    </<% $CELL %>>
-    <<% $CELL %> class="entry">
-% my $default = $m->notes('Field-' . $CustomField->Id);
-% $default ||= $ARGS{"CustomField-". $CustomField->Id };
-      <& /Elements/EditCustomField, 
-          %ARGS,
-          Object => $TicketObj,
-          CustomField => $CustomField,
-          NamePrefix => $NamePrefix,
-          Default => $default,
-      &>
-%  if (my $msg = $m->notes('InvalidField-' . $CustomField->Id)) {
-        <br />
-        <span class="cfinvalidfield"><% $msg %></span>
-%  }
-    </<% $CELL %>>
-  </<% $FIELD %>>
-% }
-
-% if ( $WRAP ) {
-</<% $WRAP %>>
-% }
-% $m->callback( %ARGS, CallbackName => 'AfterCustomFields', TicketObj => $TicketObj, QueueObj => $QueueObj );
 <%INIT>
 my $CustomFields;
-
 if ($TicketObj && !$OnCreate) {
-    $CustomFields = $TicketObj->CustomFields();
-    $NamePrefix .= "Object-RT::Ticket-".$TicketObj->Id."-CustomField-";
+    $CustomFields = $TicketObj->CustomFields;
 } else {
-    $CustomFields = $QueueObj->TicketCustomFields();
-    $NamePrefix .= "Object-RT::Ticket--CustomField-";
+    $CustomFields = $QueueObj->TicketCustomFields;
 }
-
-$CustomFields->LimitToGroup( $Group ) if defined $Group;
-
 $m->callback( %ARGS, CallbackName => 'MassageCustomFields', CustomFields => $CustomFields );
 
-# don't print anything if there is no custom fields
-return unless $CustomFields->First;
-$CustomFields->GotoFirstItem;
-
-$AsTable ||= $InTable;
-my $FIELD = $AsTable ? 'tr' : 'div';
-my $CELL  = $AsTable ? 'td' : 'div';
-my $WRAP  = '';
-if ( $AsTable ) {
-    $WRAP = 'table' unless $InTable;
-} else {
-    $WRAP = 'div';
-}
+return $m->comp('/Elements/EditCustomFields',
+    %ARGS,
+    Object => $TicketObj || RT::Ticket->new( $session{'CurrenUser'} ),
+    CustomFields => $CustomFields,
+);
 
 </%INIT>
 <%ARGS>
-$NamePrefix => ''
 $TicketObj => undef
 $QueueObj => undef
 $OnCreate => undef
-$Group => undef
-$DefaultsFromTopArguments => 1
-$AsTable => 0
-$InTable => 0
 </%ARGS>

commit b0d77265b25a1798056a0af5b91c04bb8c74685f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Sep 28 00:13:45 2012 +0400

    /Elements/EditCustomFieldCustomGroups
    
    Renders boxes with custom custom field's groups.

diff --git a/share/html/Elements/EditCustomFieldCustomGroups b/share/html/Elements/EditCustomFieldCustomGroups
new file mode 100644
index 0000000..04e04d7
--- /dev/null
+++ b/share/html/Elements/EditCustomFieldCustomGroups
@@ -0,0 +1,66 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+%#                                          <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+% foreach my $group ( RT::CustomField->CustomGroups( $Object ), '' ) {
+<&| /Widgets/TitleBox,
+    title => $group? loc($group) : loc('Custom Fields'),
+    class => $css_class .' '. ($group? CSSClass("$css_class-$group") : ''),
+    hide_empty => 1,
+&>
+<& EditCustomFields, %ARGS, Object => $Object, Group => $group &>
+</&>
+% }
+<%ARGS>
+$Object
+</%ARGS>
+<%INIT>
+my $css_class = lc(ref($Object)||$Object);
+$css_class =~ s/^rt:://;
+$css_class =~ s/::/-/g;
+$css_class = CSSClass($css_class);
+$css_class .= '-info-cfs';
+</%INIT>
\ No newline at end of file
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 47d46a1..9871914 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -107,15 +107,12 @@
   </&>
 % $m->callback( CallbackName => 'AfterBasics', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 
-% foreach my $group ( RT::CustomField->CustomGroups( 'RT::Ticket' ), '' ) {
-<&| /Widgets/TitleBox,
-    title => $group? loc($group) : loc('Custom Fields'),
-    class => 'ticket-info-cfs '. ($group? CSSClass("ticket-info-cfs-$group") : ''),
-    hide_empty => 1
+<& /Elements/EditCustomFieldCustomGroups,
+    %ARGS,
+    Object => $ticket,
+    CustomFields => $QueueObj->TicketCustomFields,
+    DefaultsFromTopArguments => 0,
 &>
-<& Elements/EditCustomFields, %ARGS, QueueObj => $QueueObj, DefaultsFromTopArguments => 0, Group => $group &>
-</&>
-% }
 
 </div>
 
@@ -368,6 +365,8 @@ $m->scomp( '/Articles/Elements/SubjectOverride', ARGSRef => \%ARGS, QueueObj =>
 
 $QueueObj->Disabled && Abort(loc("Cannot create tickets in a disabled queue."));
 
+my $ticket = RT::Ticket->new($session{'CurrentUser'}); # empty ticket object
+
 my $CFs = $QueueObj->TicketCustomFields();
 
 my $ValidCFs = $m->comp(
diff --git a/share/html/Ticket/Modify.html b/share/html/Ticket/Modify.html
index 4c9709a..b1979ab 100644
--- a/share/html/Ticket/Modify.html
+++ b/share/html/Ticket/Modify.html
@@ -61,15 +61,7 @@
 </&>
 % $m->callback( CallbackName => 'AfterBasics', Ticket => $TicketObj );
 
-% foreach my $group ( RT::CustomField->CustomGroups( $TicketObj ), '' ) {
-<&| /Widgets/TitleBox,
-    title => $group? loc($group) : loc('Custom Fields'),
-    class => 'ticket-info-cfs '. ($group? CSSClass("ticket-info-cfs-$group") : ''),
-    hide_empty => 1,
-&>
-<& Elements/EditCustomFields, TicketObj => $TicketObj, DefaultsFromTopArguments => 0, Group => $group &>
-</&>
-% }
+<& /Elements/EditCustomFieldCustomGroups, Object => $TicketObj, DefaultsFromTopArguments => 0 &>
 
 <& /Elements/Submit, Name => 'SubmitTicket', Label => loc('Save Changes'), Caption => loc("If you've updated anything above, be sure to"), color => "#993333" &>
 </form>
diff --git a/share/html/Ticket/ModifyAll.html b/share/html/Ticket/ModifyAll.html
index a2f6561..cb8385f 100644
--- a/share/html/Ticket/ModifyAll.html
+++ b/share/html/Ticket/ModifyAll.html
@@ -62,15 +62,7 @@
 
 % $m->callback(CallbackName => 'AfterBasics', Ticket => $Ticket);
 
-% foreach my $group ( RT::CustomField->CustomGroups( $Ticket ), '' ) {
-<&| /Widgets/TitleBox,
-    title => $group? loc($group) : loc('CustomFields'),
-    class => 'ticket-info-cfs '. ($group? CSSClass("ticket-info-cfs-$group") : ''),
-    hide_empty => 1,
-&>
-<& Elements/EditCustomFields, TicketObj => $Ticket, Group => $group &>
-</&>
-% }
+<& /Elements/EditCustomFieldCustomGroups, Object => $Ticket &>
 
 <&| /Widgets/TitleBox, title => loc('Dates'), class=>'ticket-info-dates'&>
 <& Elements/EditDates, TicketObj => $Ticket &>

commit ed6cb3aef90a70bbe10c58b6c847780631966543
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Sep 28 00:15:00 2012 +0400

    deprecate /Ticket/Elements/EditCustomFields
    
    In favor of /Elements/EditCustomFields.
    
    Call on create is slightly longer. Note that passing
    new custom fields collection is done to avoid re-applying
    limits on the same collection.
    
    It's deperecated not only because of its simplicity, but
    also because of new /Elements/EditCustomFieldCustomGroups
    comp which calls /Elements/EditCustomFields directly skiping
    component in /Ticket/Elements. So any customization the subject
    wouldn't be applied to all CF editors.

diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 9871914..6596267 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -101,7 +101,13 @@
 
 % $m->callback( CallbackName => 'AfterOwner', ARGSRef => \%ARGS );
 
-      <& /Ticket/Elements/EditCustomFields, %ARGS, QueueObj => $QueueObj, Group => 'Basics', InTable => 1 &>
+      <& /Elements/EditCustomFields,
+          %ARGS,
+          Object => $ticket,
+          CustomFields => $QueueObj->TicketCustomFields,
+          Group => 'Basics',
+          InTable => 1,
+      &>
       <& /Ticket/Elements/EditTransactionCustomFields, %ARGS, QueueObj => $QueueObj, InTable => 1 &>
     </table>
   </&>
@@ -160,7 +166,13 @@
   </td>
 </tr>
 
-<& Elements/EditCustomFields, QueueObj => $QueueObj, Group => 'People', InTable => 1 &>
+<& /Elements/EditCustomFields,
+    %ARGS,
+    Object => $ticket,
+    CustomFields => $QueueObj->TicketCustomFields,
+    Group => 'People',
+    InTable => 1,
+&>
 
 <tr>
 <td class="label">
@@ -248,7 +260,13 @@
 <table>
 <tr><td class="label"><&|/l&>Starts</&>:</td><td><& /Elements/SelectDate, Name => "Starts", Default => $ARGS{Starts} || '' &></td></tr>
 <tr><td class="label"><&|/l&>Due</&>:</td><td><& /Elements/SelectDate, Name => "Due", Default => $ARGS{Due} || '' &></td></tr>
-<& Elements/EditCustomFields, QueueObj => $QueueObj, Group => 'Dates', InTable => 1 &>
+<& /Elements/EditCustomFields,
+    %ARGS,
+    Object => $ticket,
+    CustomFields => $QueueObj->TicketCustomFields,
+    Group => 'Dates',
+    InTable => 1,
+&>
 </table>
 </&>
 </div>
@@ -268,7 +286,13 @@
 <tr><td class="label"><&|/l&>Children</&></td><td><input size="10" name="MemberOf-new" value="<% $ARGS{'MemberOf-new'} || '' %>" /></td></tr>
 <tr><td class="label"><&|/l&>Refers to</&></td><td><input size="10" name="new-RefersTo" value="<% $ARGS{'new-RefersTo'} || '' %>" /></td></tr>
 <tr><td class="label"><&|/l&>Referred to by</&></td><td><input size="10" name="RefersTo-new" value="<% $ARGS{'RefersTo-new'} || '' %>" /></td></tr>
-<& Elements/EditCustomFields, QueueObj => $QueueObj, Group => 'Links', InTable => 1 &>
+<& /Elements/EditCustomFields,
+    %ARGS,
+    Object => $ticket,
+    CustomFields => $QueueObj->TicketCustomFields,
+    Group => 'Links',
+    InTable => 1,
+&>
 </table>
 </&>
 </div>
diff --git a/share/html/Ticket/Elements/EditCustomFields b/share/html/Ticket/Elements/EditCustomFields
index 0c09eb7..ec56f78 100644
--- a/share/html/Ticket/Elements/EditCustomFields
+++ b/share/html/Ticket/Elements/EditCustomFields
@@ -46,6 +46,8 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <%INIT>
+$RT::Logger->warning("DEPRECATED: use /Elements/EditCustomFields");
+
 my $CustomFields;
 if ($TicketObj && !$OnCreate) {
     $CustomFields = $TicketObj->CustomFields;
diff --git a/share/html/Ticket/Elements/EditDates b/share/html/Ticket/Elements/EditDates
index e4c42c0..7d550fe 100644
--- a/share/html/Ticket/Elements/EditDates
+++ b/share/html/Ticket/Elements/EditDates
@@ -70,7 +70,7 @@
       <& /Elements/SelectDate, menu_prefix => 'Due', current => 0 &> (<% $TicketObj->DueObj->AsString %>)
     </td>
   </tr>
-  <& EditCustomFields, TicketObj => $TicketObj, Group => 'Dates', InTable => 1 &>
+  <& /Elements/EditCustomFields, Object => $TicketObj, Group => 'Dates', InTable => 1 &>
 % $m->callback( %ARGS, CallbackName => 'EndOfList', Ticket => $TicketObj );
 </table>
 <%ARGS>
diff --git a/share/html/Ticket/Elements/EditPeople b/share/html/Ticket/Elements/EditPeople
index d3d2844..4e7fec5 100644
--- a/share/html/Ticket/Elements/EditPeople
+++ b/share/html/Ticket/Elements/EditPeople
@@ -85,7 +85,7 @@
   <td class="value"><& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->AdminCc &></td>
 </tr>
 
-<& EditCustomFields, TicketObj => $Ticket, Group => 'People', InTable => 1 &>
+<& /Elements/EditCustomFields, Object => $Ticket, Group => 'People', InTable => 1 &>
 
 </table>
 
diff --git a/share/html/Ticket/Modify.html b/share/html/Ticket/Modify.html
index b1979ab..87beeab 100644
--- a/share/html/Ticket/Modify.html
+++ b/share/html/Ticket/Modify.html
@@ -57,7 +57,7 @@
 
 <&| /Widgets/TitleBox, title => loc('Modify ticket #[_1]',$TicketObj->Id), class=>'ticket-info-basics' &>
 <& Elements/EditBasics, TicketObj => $TicketObj &>
-<& Elements/EditCustomFields, TicketObj => $TicketObj, DefaultsFromTopArguments => 0, Group => 'Basics' &>
+<& /Elements/EditCustomFields, Object => $TicketObj, DefaultsFromTopArguments => 0, Group => 'Basics' &>
 </&>
 % $m->callback( CallbackName => 'AfterBasics', Ticket => $TicketObj );
 
diff --git a/share/html/Ticket/ModifyAll.html b/share/html/Ticket/ModifyAll.html
index cb8385f..252ed7f 100644
--- a/share/html/Ticket/ModifyAll.html
+++ b/share/html/Ticket/ModifyAll.html
@@ -57,7 +57,7 @@
 
 <&| /Widgets/TitleBox, title => loc('Modify ticket # [_1]', $Ticket->Id), class=>'ticket-info-basics' &>
 <& Elements/EditBasics, TicketObj => $Ticket &>
-<& Elements/EditCustomFields, TicketObj => $Ticket, Group => 'Basics' &>
+<& /Elements/EditCustomFields, Object => $Ticket, Group => 'Basics' &>
 </&>
 
 % $m->callback(CallbackName => 'AfterBasics', Ticket => $Ticket);

commit 96114a15832e9908d8ae829ea7e9ab4c81485f05
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 29 01:51:56 2012 +0400

    add a new argument in LimitToGroup
    
    object/class argument, without it we have to guess in
    which object we're interested in

diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index 0db4728..4e90d48 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -98,13 +98,15 @@ sub _Init {
 
 sub LimitToGroup {
     my $self = shift;
+    my $obj = shift;
     my $group = shift;
 
     my $config = RT->Config->Get('CustomFieldGroups');
        $config = {} unless ref($config) eq 'HASH';
+       $config = $config->{ref($obj) || $obj} || {};
 
     if ( $group ) {
-        my $list = $config->{'RT::Ticket'}{$group};
+        my $list = $config->{$group};
         unless ( $list and ref($list) eq 'ARRAY' and @$list ) {
             return $self->Limit( FIELD => 'id', VALUE => 0, ENTRYAGGREGATOR => 'AND' );
         }
@@ -113,7 +115,7 @@ sub LimitToGroup {
         }
     } else {
         my @list = map {@$_} grep defined && ref($_) eq 'ARRAY',
-            values %{ $config->{'RT::Ticket'} };
+            values %{ $config };
 
         return unless @list;
         foreach ( @list ) {
diff --git a/share/html/Elements/EditCustomFields b/share/html/Elements/EditCustomFields
index f08d92c..e0fd76e 100644
--- a/share/html/Elements/EditCustomFields
+++ b/share/html/Elements/EditCustomFields
@@ -82,7 +82,7 @@
 $NamePrefix   ||= join '-', 'Object', ref($Object), ($Object->Id || ''), 'CustomField', '';
 $CustomFields ||= $Object->CustomFields;
 
-$CustomFields->LimitToGroup( $Group  ) if defined $Group;
+$CustomFields->LimitToGroup( $Object => $Group ) if defined $Group;
 
 $m->callback( %ARGS, CallbackName => 'MassageCustomFields', $CustomFields => $CustomFields );
 
diff --git a/share/html/Elements/ShowCustomFields b/share/html/Elements/ShowCustomFields
index 1dfb171..acf4a56 100644
--- a/share/html/Elements/ShowCustomFields
+++ b/share/html/Elements/ShowCustomFields
@@ -83,7 +83,7 @@ $m->callback(
     CustomFields => $CustomFields,
 );
 
-$CustomFields->LimitToGroup($Group) if defined $Group;
+$CustomFields->LimitToGroup( $Object => $Group ) if defined $Group;
 
 # don't print anything if there is no custom fields
 return unless $CustomFields->First;

commit f1b499b08e07b5f8202bf11fe6fc20ef9c04a3be
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 29 01:55:53 2012 +0400

    custom field groups for Users

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 758d77f..66dbd28 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2723,6 +2723,12 @@ Set(%CustomFieldGroups,
         People => [],
         Links   => [],
     },
+    'RT::User' => {
+        Identity => [],
+        'Access control' => [],
+        Location => [],
+        Phones   => [],
+    },
 );
 
 1;
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 2d732db..740d2c8 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1271,6 +1271,7 @@ sub Groups {
 
 our %BUILTIN_GROUPS = (
     'RT::Ticket' => { map { $_ => 1 } qw(Basics Dates Links People) },
+    'RT::User' => { map { $_ => 1 } 'Identity', 'Access control', 'Location', 'Phones' },
 );
 $BUILTIN_GROUPS{''} = { map { %$_ } values %BUILTIN_GROUPS  };
 
diff --git a/share/html/Admin/Users/Modify.html b/share/html/Admin/Users/Modify.html
index ab4a98a..1f219a0 100644
--- a/share/html/Admin/Users/Modify.html
+++ b/share/html/Admin/Users/Modify.html
@@ -50,7 +50,7 @@
 
 <& /Elements/ListActions, actions => \@results &>
 
-<form action="<%RT->Config->Get('WebPath')%>/Admin/Users/Modify.html" method="post" enctype="multipart/form-data">
+<form action="<%RT->Config->Get('WebPath')%>/Admin/Users/Modify.html" method="post" enctype="multipart/form-data" name="<% $Create ? 'UserCreate': 'UserModify' %>">
 %if ($Create) {
 <input type="hidden" class="hidden" name="id" value="new" />
 % } else {
@@ -60,7 +60,7 @@
 <tr>
 
 <td valign="top" class="boxcontainer">
-<&| /Widgets/TitleBox, title => loc('Identity') &>
+<&| /Widgets/TitleBox, title => loc('Identity'), class => 'user-info-identity' &>
 
 <table>
 <tr><td align="right">
@@ -98,10 +98,12 @@
 </td><td>
 <textarea name="FreeformContactInfo" cols="20" rows="5"><%$UserObj->FreeformContactInfo||$FreeformContactInfo||''%></textarea>
 </td></tr>
+<& /Elements/EditCustomFields, Object => $UserObj, Group => 'Identity', InTable => 1 &>
 </table>
 </&>
+
 <br />
-<&| /Widgets/TitleBox, title => loc('Access control') &>
+<&| /Widgets/TitleBox, title => loc('Access control'), class => 'user-info-access-control' &>
 <input type="hidden" class="hidden" name="SetEnabled" value="1" />
 <input type="checkbox" class="checkbox" name="Enabled" value="1" <%$EnabledChecked%> />
 <&|/l&>Let this user access RT</&><br />
@@ -114,12 +116,15 @@
     User => $UserObj,
     Name => [qw(CurrentPass Pass1 Pass2)],
 &>
+
+<& /Elements/EditCustomFields, Object => $UserObj, Group => 'Access control' &>
+
 </&>
 % $m->callback( %ARGS, CallbackName => 'LeftColumnBottom', UserObj => $UserObj );
 </td>
 
 <td valign="top" class="boxcontainer">
-<&| /Widgets/TitleBox, title => loc('Location') &>
+<&| /Widgets/TitleBox, title => loc('Location'), class => 'user-info-location' &>
 <table>
 <tr><td align="right">
 <&|/l&>Organization</&>: 
@@ -158,10 +163,13 @@
 </td><td>
 <input name="Country" value="<%$UserObj->Country||$Country||''%>" />
 </td></tr>
+
+<& /Elements/EditCustomFields, Object => $UserObj, Group => 'Location', InTable => 1 &>
+
 </table>
 </&>
 <br />
-<&| /Widgets/TitleBox, title => loc('Phone numbers') &>
+<&| /Widgets/TitleBox, title => loc('Phone numbers'), class => 'user-info-phones' &>
 <table>
 <tr><td align="right">
 <&|/l&>Residence</&>: 
@@ -184,25 +192,15 @@
 <input name="PagerPhone" value="<%$UserObj->PagerPhone||$PagerPhone||''%>" size="13" /><br />
 </td>
 </tr>
+
+<& /Elements/EditCustomFields, Object => $UserObj, Group => 'Phones', InTable => 1 &>
+
 </table>
 </&>
 <br />
-<&| /Widgets/TitleBox, title => loc('Custom Fields') &>
-<table>
-% my $CFs = $UserObj->CustomFields;
-% while (my $CF = $CFs->Next) {
-<tr valign="top"><td align="right">
-<% loc($CF->Name) %>:
-</td><td>
-% if ($UserObj->id) {
-<& /Elements/EditCustomField, %ARGS, Object => $UserObj, CustomField => $CF &>
-% } else {
-<& /Elements/EditCustomField, %ARGS, NamePrefix => 'Object-RT::User--CustomField-', CustomField => $CF &>
-% }
-</td></tr>
-% }
-</table>
-</&>
+
+<& /Elements/EditCustomFieldCustomGroups, Object => $UserObj &>
+
 % $m->callback( %ARGS, CallbackName => 'RightColumnBottom', UserObj => $UserObj );
 </td></tr>
 <tr>
diff --git a/t/web/cf_groups_users.t b/t/web/cf_groups_users.t
new file mode 100644
index 0000000..cf9ce46
--- /dev/null
+++ b/t/web/cf_groups_users.t
@@ -0,0 +1,118 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef;
+
+RT->Config->Set( 'CustomFieldGroups',
+    'RT::User' => {
+        Identity         => ['TestIdentity'],
+        'Access control' => ['TestAccessControl'],
+        Location         => ['TestLocation'],
+        Phones           => ['TestPhones'],
+        More             => ['TestMore'],
+    },
+);
+
+my %CF;
+
+foreach my $name ( map { @$_ } values %{ RT->Config->Get('CustomFieldGroups')->{'RT::User'} } ) {
+    my $cf = RT::CustomField->new( RT->SystemUser );
+    my ($id, $msg) = $cf->Create(
+        Name => $name,
+        Description => 'A custom field',
+        LookupType => RT::User->new( $RT::SystemUser )->CustomFieldLookupType,
+        Type => 'FreeformSingle',
+        Pattern => qr{^(?!bad value).*$},
+    );
+    ok $id, "custom field '$name' correctly created";
+
+    ($id, $msg) = $cf->AddToObject( RT::User->new( $cf->CurrentUser ) );
+    ok $id, "applied custom field" or diag "error: $msg";
+
+    $CF{$name} = $cf;
+}
+
+my ( $baseurl, $m ) = RT::Test->started_ok;
+ok $m->login, 'logged in as root';
+
+my $index = 1;
+
+{
+    note "testing Create";
+    $m->follow_link_ok({id => 'tools-config-users-create'}, 'Create ');
+
+    my $dom = $m->dom;
+    $m->form_name('UserCreate');
+
+    $m->field( 'Name', 'user'. $index++ );
+
+    my $prefix = 'Object-RT::User--CustomField-';
+    my $input_name = $prefix . $CF{'TestIdentity'}->id .'-Value';
+    is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page";
+    ok $dom->at(qq{.user-info-identity input[name="$input_name"]}), "CF is in the right place";
+    $m->field( $input_name, 'TestIdentityValue' );
+
+    $input_name = $prefix . $CF{'TestAccessControl'}->id .'-Value';
+    is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page";
+    ok $dom->at(qq{.user-info-access-control input[name="$input_name"]}), "CF is in the right place";
+    $m->field( $input_name, 'TestAccessControlValue' );
+
+    $input_name = $prefix . $CF{'TestLocation'}->id .'-Value';
+    is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page";
+    ok $dom->at(qq{.user-info-location input[name="$input_name"]}), "CF is in the right place";
+    $m->field( $input_name, 'TestLocationValue' );
+
+    $input_name = $prefix . $CF{'TestPhones'}->id .'-Value';
+    is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page";
+    ok $dom->at(qq{.user-info-phones input[name="$input_name"]}), "CF is in the right place";
+    $m->field( $input_name, 'TestPhonesValue' );
+
+    $input_name = $prefix . $CF{'TestMore'}->id .'-Value';
+    is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page";
+    ok $dom->at(qq{.user-info-cfs input[name="$input_name"]}), "CF is in the right place";
+    $m->field( $input_name, 'TestMoreValue' );
+
+    $m->submit;
+    $m->content_like(qr{User created});
+    my ($id) = ($m->uri =~ /id=(\d+)/);
+    ok $id, "found user's id #$id";
+
+    note "testing values on Modify page and on the object";
+    {
+        my $user = RT::User->new( RT->SystemUser );
+        $user->Load( $id );
+        ok $user->id, "loaded user";
+
+        $m->form_name('UserModify');
+        foreach my $cf_name ( keys %CF ) {
+            is $user->FirstCustomFieldValue($cf_name), "${cf_name}Value",
+                "correct value of $cf_name CF";
+            my $input = 'Object-RT::User-'. $id .'-CustomField-'
+                . $CF{$cf_name}->id .'-Value';
+            is $m->value($input), "${cf_name}Value",
+                "correct value in UI";
+            $m->field( $input, "${cf_name}Changed" );
+        }
+        $m->submit;
+    }
+
+    note "testing that update works";
+    {
+        my $user = RT::User->new( RT->SystemUser );
+        $user->Load( $id );
+        ok $user->id, "loaded user";
+
+        $m->form_name('UserModify');
+        foreach my $cf_name ( keys %CF ) {
+            is $user->FirstCustomFieldValue($cf_name), "${cf_name}Changed",
+                "correct value of $cf_name CF";
+            my $input = 'Object-RT::User-'. $id .'-CustomField-'
+                . $CF{$cf_name}->id .'-Value';
+            is $m->value($input), "${cf_name}Changed",
+                "correct value in UI";
+        }
+    }
+}
+
+undef $m;
+done_testing;

commit 9e61a0d778e4078c119809e80b0269a276b5471d
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Oct 10 14:03:38 2012 -0700

    Correct typo resulting in no current user for the fallback value

diff --git a/share/html/Ticket/Elements/EditCustomFields b/share/html/Ticket/Elements/EditCustomFields
index ec56f78..1a0b222 100644
--- a/share/html/Ticket/Elements/EditCustomFields
+++ b/share/html/Ticket/Elements/EditCustomFields
@@ -58,7 +58,7 @@ $m->callback( %ARGS, CallbackName => 'MassageCustomFields', CustomFields => $Cus
 
 return $m->comp('/Elements/EditCustomFields',
     %ARGS,
-    Object => $TicketObj || RT::Ticket->new( $session{'CurrenUser'} ),
+    Object => $TicketObj || RT::Ticket->new( $session{'CurrentUser'} ),
     CustomFields => $CustomFields,
 );
 

commit 4795001b66a9b72a962fae81382d6be436827cc3
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Oct 10 14:21:36 2012 -0700

    Replace more uses of the now-deprecated /Ticket/Elements/EditCustomFields

diff --git a/share/html/Elements/EditLinks b/share/html/Elements/EditLinks
index f063ccc..04a888d 100644
--- a/share/html/Elements/EditLinks
+++ b/share/html/Elements/EditLinks
@@ -158,9 +158,7 @@
     <td class="label"><& ShowRelationLabel, id => $id, Label => loc('Referred to by'), Relation => 'ReferredToBy' &>:</td>
     <td class="entry"> <input name="RefersTo-<%$id%>" /></td>
   </tr>
-% if ( $Object->isa('RT::Ticket') ) {
-  <& /Ticket/Elements/EditCustomFields, TicketObj => $Object, Group => 'Links', InTable => 1 &>
-% }
+  <& /Elements/EditCustomFields, Object => $Object, Group => 'Links', InTable => 1 &>
 % $m->callback( CallbackName => 'NewLink' );
 </table>
 </td>
diff --git a/share/html/SelfService/Create.html b/share/html/SelfService/Create.html
index 097cad4..2bcf107 100644
--- a/share/html/SelfService/Create.html
+++ b/share/html/SelfService/Create.html
@@ -88,7 +88,12 @@
 </tr>
 <tr>
     <td colspan="2">
-        <& /Ticket/Elements/EditCustomFields, %ARGS, QueueObj => $queue_obj &>
+        <& /Elements/EditCustomFields,
+            %ARGS,
+            Object          => RT::Ticket->new($session{CurrentUser}),
+            CustomFields    => $queue_obj->TicketCustomFields,
+            AsTable         => 0,
+            &>
     </td>
 </tr>
 <tr>
diff --git a/share/html/SelfService/Update.html b/share/html/SelfService/Update.html
index 6525d3d..b2c4fe4 100644
--- a/share/html/SelfService/Update.html
+++ b/share/html/SelfService/Update.html
@@ -73,7 +73,7 @@
 
     </tr>
     <& /Ticket/Elements/AddAttachments, %ARGS, TicketObj => $Ticket &>
-    <tr><td colspan="2"><& /Ticket/Elements/EditCustomFields,  TicketObj => $Ticket &></td></tr>
+    <tr><td colspan="2"><& /Elements/EditCustomFields, Object => $Ticket, AsTable => 0 &></td></tr>
 </table>
 <& /Elements/MessageBox, 
     Name => "UpdateContent", 
diff --git a/share/html/m/ticket/create b/share/html/m/ticket/create
index b460488..e3885ff 100644
--- a/share/html/m/ticket/create
+++ b/share/html/m/ticket/create
@@ -321,7 +321,12 @@ $showrows->(
 
 </%perl>
 
-<& /Ticket/Elements/EditCustomFields, %ARGS, QueueObj => $QueueObj &>
+<& /Elements/EditCustomFields,
+    %ARGS,
+    Object          => RT::Ticket->new($session{CurrentUser}),
+    CustomFields    => $QueueObj->TicketCustomFields,
+    AsTable         => 0,
+    &>
 <& /Ticket/Elements/EditTransactionCustomFields, %ARGS, QueueObj => $QueueObj &>
 
 % if (exists $session{'Attachments'}) {

commit d7fcd2a9ae5711af74c5a134fa28f3aa9a8fff61
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Oct 10 14:33:14 2012 -0700

    Avoid an uninitialized warning triggered by new user creation
    
    These warnings caused t/web/cf_groups_users.t to fail.

diff --git a/share/html/Admin/Users/Modify.html b/share/html/Admin/Users/Modify.html
index 1f219a0..8883562 100644
--- a/share/html/Admin/Users/Modify.html
+++ b/share/html/Admin/Users/Modify.html
@@ -317,7 +317,7 @@ if ($UserObj->Id && $id ne 'new') {
 
 
     # {{{ Deal with special fields: Privileged, Enabled
-    if  ( $SetPrivileged and $Privileged != $UserObj->Privileged ) {
+    if  ( $SetPrivileged and $Privileged != ($UserObj->Privileged || 0) ) {
          my ($code, $msg) = $UserObj->SetPrivileged($Privileged);
          push @results, loc('Privileged status: [_1]', loc_fuzzy($msg));
     }
@@ -378,7 +378,7 @@ $FreeformContactInfo => undef
 $Organization  => undef
 $RealName  => undef
 $NickName  => undef
-$Privileged => undef
+$Privileged => 0
 $SetPrivileged => undef
 $Enabled => undef
 $SetEnabled => undef

commit 1beb0717694a0d2aabe66abd25066062a20ea6e3
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 15 09:35:50 2012 -0700

    ShowCustomFieldCustomGroups element to match the element for Edit

diff --git a/share/html/Elements/ShowCustomFieldCustomGroups b/share/html/Elements/ShowCustomFieldCustomGroups
new file mode 100644
index 0000000..de10e4c
--- /dev/null
+++ b/share/html/Elements/ShowCustomFieldCustomGroups
@@ -0,0 +1,69 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+%#                                          <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+% foreach my $group ( RT::CustomField->CustomGroups( $Object ), '' ) {
+<&| /Widgets/TitleBox,
+    title => $group? loc($group) : loc('Custom Fields'),
+    class => $css_class .' '. ($group? CSSClass("$css_class-$group") : ''),
+    hide_empty => 1,
+    %$TitleBoxARGS,
+&>
+<& ShowCustomFields, %ARGS, Object => $Object, Group => $group &>
+</&>
+% }
+<%ARGS>
+$Object
+</%ARGS>
+<%INIT>
+my $css_class = lc(ref($Object)||$Object);
+$css_class =~ s/^rt:://;
+$css_class =~ s/::/-/g;
+$css_class = CSSClass($css_class);
+$css_class .= '-info-cfs';
+
+my $TitleBoxARGS = delete $ARGS{TitleBoxARGS} || {};
+</%INIT>
diff --git a/share/html/Ticket/Elements/ShowSummary b/share/html/Ticket/Elements/ShowSummary
index a7e2184..e6a63dc 100644
--- a/share/html/Ticket/Elements/ShowSummary
+++ b/share/html/Ticket/Elements/ShowSummary
@@ -53,14 +53,15 @@
         (($can_modify || $can_modify_cf) ? (title_href => RT->Config->Get('WebPath')."/Ticket/Modify.html?id=".$Ticket->Id) : ()),
         class => 'ticket-info-basics',
     &><& /Ticket/Elements/ShowBasics, Ticket => $Ticket &></&>
-% foreach my $group ( RT::CustomField->CustomGroups($Ticket), '' ) {
-    <&| /Widgets/TitleBox,
-        title => $group? loc($group) : loc('Custom Fields'),
-        (($can_modify || $can_modify_cf) ? (title_href => RT->Config->Get('WebPath')."/Ticket/Modify.html?id=".$Ticket->Id) : ()),
-        class => 'ticket-info-cfs '. ($group? CSSClass("ticket-info-cfs-$group") : ''),
-        hide_empty => 1,
-    &><& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Group => $group &></&>
-% }
+
+    <& /Elements/ShowCustomFieldCustomGroups,
+        Object       => $Ticket,
+        TitleBoxARGS => {
+            (($can_modify || $can_modify_cf)
+                ? (title_href => RT->Config->Get('WebPath')."/Ticket/Modify.html?id=".$Ticket->Id)
+                : ()),
+        },
+        &>
 
     <&| /Widgets/TitleBox, title => loc('People'),
         (($can_modify || $can_modify_owner) ? (title_href => RT->Config->Get('WebPath')."/Ticket/ModifyPeople.html?id=".$Ticket->Id) : ()),

commit f8f182b8d73b9fa7956851c6d57a6dd1b7a2f3ac
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 15 13:59:17 2012 -0700

    Refactor built-in custom field grouping registration into a method
    
    Allows for easier extensibility of built-in groupings by extensions,
    particularly ones which add new objects for CFs to apply to.

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 740d2c8..e148276 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -201,6 +201,11 @@ RT::CustomField->_ForObjectType( 'RT::User'  => "Users", );
 RT::CustomField->_ForObjectType( 'RT::Queue'  => "Queues", );                         #loc
 RT::CustomField->_ForObjectType( 'RT::Group' => "Groups", );                          #loc
 
+__PACKAGE__->RegisterBuiltInGroups(
+    'RT::Ticket'    => [ qw(Basics Dates Links People) ],
+    'RT::User'      => [ 'Identity', 'Access control', 'Location', 'Phones' ],
+);
+
 our $RIGHTS = {
     SeeCustomField            => 'View custom fields',                                    # loc_pair
     AdminCustomField          => 'Create, modify and delete custom fields',               # loc_pair
@@ -1269,11 +1274,17 @@ sub Groups {
         @groups;
 }
 
-our %BUILTIN_GROUPS = (
-    'RT::Ticket' => { map { $_ => 1 } qw(Basics Dates Links People) },
-    'RT::User' => { map { $_ => 1 } 'Identity', 'Access control', 'Location', 'Phones' },
-);
-$BUILTIN_GROUPS{''} = { map { %$_ } values %BUILTIN_GROUPS  };
+my %BUILTIN_GROUPS;
+sub RegisterBuiltInGroups {
+    my $self = shift;
+    my %new  = @_;
+
+    while (my ($k,$v) = each %new) {
+        $v = [$v] unless ref($v) eq 'ARRAY';
+        $BUILTIN_GROUPS{$k} = { map { $_ => 1 } @$v };
+    }
+    $BUILTIN_GROUPS{''} = { map { %$_ } values %BUILTIN_GROUPS  };
+}
 
 sub CustomGroups {
     my $self = shift;

commit b28cceff19948570f3f6ec9d1783e9aa45700963
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 15 14:08:03 2012 -0700

    Extend rather than override existing built-in groupings for an object class
    
    Now adding another grouping to an existing object class is possible with
    an extension:
    
        RT::CustomField->RegisterBuiltInGroups( "RT::Ticket" => ["Foo"] );
    
    and the "Foo" grouping will be left alone to be handled by the
    extension.

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index e148276..87c0ec4 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1281,7 +1281,10 @@ sub RegisterBuiltInGroups {
 
     while (my ($k,$v) = each %new) {
         $v = [$v] unless ref($v) eq 'ARRAY';
-        $BUILTIN_GROUPS{$k} = { map { $_ => 1 } @$v };
+        $BUILTIN_GROUPS{$k} = {
+            %{$BUILTIN_GROUPS{$k} || {}},
+            map { $_ => 1 } @$v
+        };
     }
     $BUILTIN_GROUPS{''} = { map { %$_ } values %BUILTIN_GROUPS  };
 }

commit 5e73eac7b046eeb2651f88696f8519f6b5d774ce
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 15 14:59:38 2012 -0700

    Remove unimplemented Group method
    
    We can implement the method as needed in the future.  The method will
    need to account for CFs appearing in multiple groupings.

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 87c0ec4..558246a 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1243,12 +1243,6 @@ sub CollectionClassFromLookupType {
     return $collection_class;
 }
 
-
-sub Group {
-
-
-}
-
 sub Groups {
     my $self = shift;
     my $record = shift;

commit 43cbc992efa2a5f69a78a07e57e825825c2ec8f9
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 15 15:18:53 2012 -0700

    Rename custom field "group" concept to "groupings"
    
    Grouping conveys the simple organizational intent better and clarifies
    the relationship (none!) to RT::Group.

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 66dbd28..a5ff80c 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2716,7 +2716,7 @@ with L</Lifecycles> (see L</Labeling and defining actions>).
 
 =cut
 
-Set(%CustomFieldGroups,
+Set(%CustomFieldGroupings,
     'RT::Ticket' => {
         Basics => [],
         Dates => [],
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 6e1a666..bbf85dc 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -807,32 +807,32 @@ lifecycle and add transition rules; see RT_Config.pm for documentation.
 EOT
         },
     },
-    CustomFieldGroups   => {
+    CustomFieldGroupings => {
         Type            => 'HASH',
         PostLoadCheck   => sub {
             my $config = shift;
             # use scalar context intentionally to avoid not a hash error
-            my $groups = $config->Get('CustomFieldGroups') || {};
+            my $groups = $config->Get('CustomFieldGroupings') || {};
 
             unless (ref($groups) eq 'HASH') {
-                RT->Logger->error("Config option %CustomFieldGroups is a @{[ref $groups]} not a HASH; ignoring");
+                RT->Logger->error("Config option %CustomFieldGroupings is a @{[ref $groups]} not a HASH; ignoring");
                 $groups = {};
             }
 
             for my $class (keys %$groups) {
                 unless (ref($groups->{$class}) eq 'HASH') {
-                    RT->Logger->error("Config option \%CustomFieldGroups{$class} is not a HASH; ignoring");
+                    RT->Logger->error("Config option \%CustomFieldGroupings{$class} is not a HASH; ignoring");
                     delete $groups->{$class};
                     next;
                 }
                 for my $group (keys %{ $groups->{$class} }) {
                     unless (ref($groups->{$class}{$group}) eq 'ARRAY') {
-                        RT->Logger->error("Config option \%CustomFieldGroups{$class}{$group} is not an ARRAY; ignoring");
+                        RT->Logger->error("Config option \%CustomFieldGroupings{$class}{$group} is not an ARRAY; ignoring");
                         delete $groups->{$class}{$group};
                     }
                 }
             }
-            $config->Set( CustomFieldGroups => %$groups );
+            $config->Set( CustomFieldGroupings => %$groups );
         },
     },
 );
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 558246a..739fa38 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -201,7 +201,7 @@ RT::CustomField->_ForObjectType( 'RT::User'  => "Users", );
 RT::CustomField->_ForObjectType( 'RT::Queue'  => "Queues", );                         #loc
 RT::CustomField->_ForObjectType( 'RT::Group' => "Groups", );                          #loc
 
-__PACKAGE__->RegisterBuiltInGroups(
+__PACKAGE__->RegisterBuiltInGroupings(
     'RT::Ticket'    => [ qw(Basics Dates Links People) ],
     'RT::User'      => [ 'Identity', 'Access control', 'Location', 'Phones' ],
 );
@@ -1243,7 +1243,7 @@ sub CollectionClassFromLookupType {
     return $collection_class;
 }
 
-sub Groups {
+sub Groupings {
     my $self = shift;
     my $record = shift;
 
@@ -1251,7 +1251,7 @@ sub Groups {
     $record_class = $self->RecordClassFromLookupType
         if !$record_class && $self->id;
 
-    my $config = RT->Config->Get('CustomFieldGroups');
+    my $config = RT->Config->Get('CustomFieldGroupings');
        $config = {} unless ref($config) eq 'HASH';
 
     my @groups;
@@ -1268,22 +1268,22 @@ sub Groups {
         @groups;
 }
 
-my %BUILTIN_GROUPS;
-sub RegisterBuiltInGroups {
+my %BUILTIN_GROUPINGS;
+sub RegisterBuiltInGroupings {
     my $self = shift;
     my %new  = @_;
 
     while (my ($k,$v) = each %new) {
         $v = [$v] unless ref($v) eq 'ARRAY';
-        $BUILTIN_GROUPS{$k} = {
-            %{$BUILTIN_GROUPS{$k} || {}},
+        $BUILTIN_GROUPINGS{$k} = {
+            %{$BUILTIN_GROUPINGS{$k} || {}},
             map { $_ => 1 } @$v
         };
     }
-    $BUILTIN_GROUPS{''} = { map { %$_ } values %BUILTIN_GROUPS  };
+    $BUILTIN_GROUPINGS{''} = { map { %$_ } values %BUILTIN_GROUPINGS  };
 }
 
-sub CustomGroups {
+sub CustomGroupings {
     my $self = shift;
     my $record = shift;
 
@@ -1291,7 +1291,7 @@ sub CustomGroups {
     $record_class = $self->RecordClassFromLookupType
         if !$record_class && $self->id;
 
-    return grep !$BUILTIN_GROUPS{$record_class}{$_}, $self->Groups( $record_class );
+    return grep !$BUILTIN_GROUPINGS{$record_class}{$_}, $self->Groupings( $record_class );
 }
 
 =head1 ApplyGlobally
diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index 4e90d48..5dc2b97 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -96,17 +96,17 @@ sub _Init {
     return ( $self->SUPER::_Init(@_) );
 }
 
-sub LimitToGroup {
+sub LimitToGrouping {
     my $self = shift;
     my $obj = shift;
-    my $group = shift;
+    my $grouping = shift;
 
-    my $config = RT->Config->Get('CustomFieldGroups');
+    my $config = RT->Config->Get('CustomFieldGroupings');
        $config = {} unless ref($config) eq 'HASH';
        $config = $config->{ref($obj) || $obj} || {};
 
-    if ( $group ) {
-        my $list = $config->{$group};
+    if ( $grouping ) {
+        my $list = $config->{$grouping};
         unless ( $list and ref($list) eq 'ARRAY' and @$list ) {
             return $self->Limit( FIELD => 'id', VALUE => 0, ENTRYAGGREGATOR => 'AND' );
         }
diff --git a/share/html/Admin/Users/Modify.html b/share/html/Admin/Users/Modify.html
index 8883562..f459343 100644
--- a/share/html/Admin/Users/Modify.html
+++ b/share/html/Admin/Users/Modify.html
@@ -98,7 +98,7 @@
 </td><td>
 <textarea name="FreeformContactInfo" cols="20" rows="5"><%$UserObj->FreeformContactInfo||$FreeformContactInfo||''%></textarea>
 </td></tr>
-<& /Elements/EditCustomFields, Object => $UserObj, Group => 'Identity', InTable => 1 &>
+<& /Elements/EditCustomFields, Object => $UserObj, Grouping => 'Identity', InTable => 1 &>
 </table>
 </&>
 
@@ -117,7 +117,7 @@
     Name => [qw(CurrentPass Pass1 Pass2)],
 &>
 
-<& /Elements/EditCustomFields, Object => $UserObj, Group => 'Access control' &>
+<& /Elements/EditCustomFields, Object => $UserObj, Grouping => 'Access control' &>
 
 </&>
 % $m->callback( %ARGS, CallbackName => 'LeftColumnBottom', UserObj => $UserObj );
@@ -164,7 +164,7 @@
 <input name="Country" value="<%$UserObj->Country||$Country||''%>" />
 </td></tr>
 
-<& /Elements/EditCustomFields, Object => $UserObj, Group => 'Location', InTable => 1 &>
+<& /Elements/EditCustomFields, Object => $UserObj, Grouping => 'Location', InTable => 1 &>
 
 </table>
 </&>
@@ -193,13 +193,13 @@
 </td>
 </tr>
 
-<& /Elements/EditCustomFields, Object => $UserObj, Group => 'Phones', InTable => 1 &>
+<& /Elements/EditCustomFields, Object => $UserObj, Grouping => 'Phones', InTable => 1 &>
 
 </table>
 </&>
 <br />
 
-<& /Elements/EditCustomFieldCustomGroups, Object => $UserObj &>
+<& /Elements/EditCustomFieldCustomGroupings, Object => $UserObj &>
 
 % $m->callback( %ARGS, CallbackName => 'RightColumnBottom', UserObj => $UserObj );
 </td></tr>
diff --git a/share/html/Elements/EditCustomFieldCustomGroups b/share/html/Elements/EditCustomFieldCustomGroupings
similarity index 94%
rename from share/html/Elements/EditCustomFieldCustomGroups
rename to share/html/Elements/EditCustomFieldCustomGroupings
index 04e04d7..15d1e7a 100644
--- a/share/html/Elements/EditCustomFieldCustomGroups
+++ b/share/html/Elements/EditCustomFieldCustomGroupings
@@ -45,13 +45,13 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-% foreach my $group ( RT::CustomField->CustomGroups( $Object ), '' ) {
+% foreach my $group ( RT::CustomField->CustomGroupings( $Object ), '' ) {
 <&| /Widgets/TitleBox,
     title => $group? loc($group) : loc('Custom Fields'),
     class => $css_class .' '. ($group? CSSClass("$css_class-$group") : ''),
     hide_empty => 1,
 &>
-<& EditCustomFields, %ARGS, Object => $Object, Group => $group &>
+<& EditCustomFields, %ARGS, Object => $Object, Grouping => $group &>
 </&>
 % }
 <%ARGS>
@@ -63,4 +63,4 @@ $css_class =~ s/^rt:://;
 $css_class =~ s/::/-/g;
 $css_class = CSSClass($css_class);
 $css_class .= '-info-cfs';
-</%INIT>
\ No newline at end of file
+</%INIT>
diff --git a/share/html/Elements/EditCustomFields b/share/html/Elements/EditCustomFields
index e0fd76e..d184d81 100644
--- a/share/html/Elements/EditCustomFields
+++ b/share/html/Elements/EditCustomFields
@@ -82,7 +82,7 @@
 $NamePrefix   ||= join '-', 'Object', ref($Object), ($Object->Id || ''), 'CustomField', '';
 $CustomFields ||= $Object->CustomFields;
 
-$CustomFields->LimitToGroup( $Object => $Group ) if defined $Group;
+$CustomFields->LimitToGrouping( $Object => $Grouping ) if defined $Grouping;
 
 $m->callback( %ARGS, CallbackName => 'MassageCustomFields', $CustomFields => $CustomFields );
 
@@ -105,7 +105,7 @@ if ( $AsTable ) {
 $Object
 $CustomFields => undef
 $NamePrefix   => ''
-$Group        => undef
+$Grouping     => undef
 $AsTable => 1
 $InTable => 0
 </%ARGS>
diff --git a/share/html/Elements/EditLinks b/share/html/Elements/EditLinks
index 04a888d..e293d56 100644
--- a/share/html/Elements/EditLinks
+++ b/share/html/Elements/EditLinks
@@ -158,7 +158,7 @@
     <td class="label"><& ShowRelationLabel, id => $id, Label => loc('Referred to by'), Relation => 'ReferredToBy' &>:</td>
     <td class="entry"> <input name="RefersTo-<%$id%>" /></td>
   </tr>
-  <& /Elements/EditCustomFields, Object => $Object, Group => 'Links', InTable => 1 &>
+  <& /Elements/EditCustomFields, Object => $Object, Grouping => 'Links', InTable => 1 &>
 % $m->callback( CallbackName => 'NewLink' );
 </table>
 </td>
diff --git a/share/html/Elements/ShowCustomFieldCustomGroups b/share/html/Elements/ShowCustomFieldCustomGroupings
similarity index 94%
rename from share/html/Elements/ShowCustomFieldCustomGroups
rename to share/html/Elements/ShowCustomFieldCustomGroupings
index de10e4c..98ae30b 100644
--- a/share/html/Elements/ShowCustomFieldCustomGroups
+++ b/share/html/Elements/ShowCustomFieldCustomGroupings
@@ -45,14 +45,14 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-% foreach my $group ( RT::CustomField->CustomGroups( $Object ), '' ) {
+% foreach my $group ( RT::CustomField->CustomGroupings( $Object ), '' ) {
 <&| /Widgets/TitleBox,
     title => $group? loc($group) : loc('Custom Fields'),
     class => $css_class .' '. ($group? CSSClass("$css_class-$group") : ''),
     hide_empty => 1,
     %$TitleBoxARGS,
 &>
-<& ShowCustomFields, %ARGS, Object => $Object, Group => $group &>
+<& ShowCustomFields, %ARGS, Object => $Object, Grouping => $group &>
 </&>
 % }
 <%ARGS>
diff --git a/share/html/Elements/ShowCustomFields b/share/html/Elements/ShowCustomFields
index acf4a56..2607108 100644
--- a/share/html/Elements/ShowCustomFields
+++ b/share/html/Elements/ShowCustomFields
@@ -83,7 +83,7 @@ $m->callback(
     CustomFields => $CustomFields,
 );
 
-$CustomFields->LimitToGroup( $Object => $Group ) if defined $Group;
+$CustomFields->LimitToGrouping( $Object => $Grouping ) if defined $Grouping;
 
 # don't print anything if there is no custom fields
 return unless $CustomFields->First;
@@ -129,6 +129,6 @@ my $print_value = sub {
 <%ARGS>
 $Object => undef
 $CustomFields => $Object->CustomFields
-$Group => undef
+$Grouping => undef
 $Table => 1
 </%ARGS>
diff --git a/share/html/Elements/ShowLinks b/share/html/Elements/ShowLinks
index 998e2b5..b6a5cc7 100644
--- a/share/html/Elements/ShowLinks
+++ b/share/html/Elements/ShowLinks
@@ -138,7 +138,7 @@ while ( my $link = $depends_on->Next ) {
 </ul>
     </td>
   </tr>
-  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Group => 'Links', Table => 0 &>
+  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'Links', Table => 0 &>
 % # Allow people to add more rows to the table
 % $m->callback( %ARGS );
 </table>
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 6596267..56cd074 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -105,7 +105,7 @@
           %ARGS,
           Object => $ticket,
           CustomFields => $QueueObj->TicketCustomFields,
-          Group => 'Basics',
+          Grouping => 'Basics',
           InTable => 1,
       &>
       <& /Ticket/Elements/EditTransactionCustomFields, %ARGS, QueueObj => $QueueObj, InTable => 1 &>
@@ -113,7 +113,7 @@
   </&>
 % $m->callback( CallbackName => 'AfterBasics', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 
-<& /Elements/EditCustomFieldCustomGroups,
+<& /Elements/EditCustomFieldCustomGroupings,
     %ARGS,
     Object => $ticket,
     CustomFields => $QueueObj->TicketCustomFields,
@@ -170,7 +170,7 @@
     %ARGS,
     Object => $ticket,
     CustomFields => $QueueObj->TicketCustomFields,
-    Group => 'People',
+    Grouping => 'People',
     InTable => 1,
 &>
 
@@ -264,7 +264,7 @@
     %ARGS,
     Object => $ticket,
     CustomFields => $QueueObj->TicketCustomFields,
-    Group => 'Dates',
+    Grouping => 'Dates',
     InTable => 1,
 &>
 </table>
@@ -290,7 +290,7 @@
     %ARGS,
     Object => $ticket,
     CustomFields => $QueueObj->TicketCustomFields,
-    Group => 'Links',
+    Grouping => 'Links',
     InTable => 1,
 &>
 </table>
diff --git a/share/html/Ticket/Elements/EditDates b/share/html/Ticket/Elements/EditDates
index 7d550fe..67bef11 100644
--- a/share/html/Ticket/Elements/EditDates
+++ b/share/html/Ticket/Elements/EditDates
@@ -70,7 +70,7 @@
       <& /Elements/SelectDate, menu_prefix => 'Due', current => 0 &> (<% $TicketObj->DueObj->AsString %>)
     </td>
   </tr>
-  <& /Elements/EditCustomFields, Object => $TicketObj, Group => 'Dates', InTable => 1 &>
+  <& /Elements/EditCustomFields, Object => $TicketObj, Grouping => 'Dates', InTable => 1 &>
 % $m->callback( %ARGS, CallbackName => 'EndOfList', Ticket => $TicketObj );
 </table>
 <%ARGS>
diff --git a/share/html/Ticket/Elements/EditPeople b/share/html/Ticket/Elements/EditPeople
index 4e7fec5..877cbd9 100644
--- a/share/html/Ticket/Elements/EditPeople
+++ b/share/html/Ticket/Elements/EditPeople
@@ -85,7 +85,7 @@
   <td class="value"><& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->AdminCc &></td>
 </tr>
 
-<& /Elements/EditCustomFields, Object => $Ticket, Group => 'People', InTable => 1 &>
+<& /Elements/EditCustomFields, Object => $Ticket, Grouping => 'People', InTable => 1 &>
 
 </table>
 
diff --git a/share/html/Ticket/Elements/ShowBasics b/share/html/Ticket/Elements/ShowBasics
index 22fe565..c6364e4 100644
--- a/share/html/Ticket/Elements/ShowBasics
+++ b/share/html/Ticket/Elements/ShowBasics
@@ -83,7 +83,7 @@
     <td class="value"><& ShowQueue, Ticket => $Ticket, QueueObj => $Ticket->QueueObj &></td>
   </tr>
 % }
-  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Group => 'Basics', Table => 0 &>
+  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'Basics', Table => 0 &>
 % $m->callback( %ARGS, CallbackName => 'EndOfList', TicketObj => $Ticket );
 </table>
 <%ARGS>
diff --git a/share/html/Ticket/Elements/ShowDates b/share/html/Ticket/Elements/ShowDates
index 4430a57..4b40e86 100644
--- a/share/html/Ticket/Elements/ShowDates
+++ b/share/html/Ticket/Elements/ShowDates
@@ -84,7 +84,7 @@
     <td class="value"><% $UpdatedString | n %></td>
 % }
   </tr>
-  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Group => 'Dates', Table => 0 &>
+  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'Dates', Table => 0 &>
 % $m->callback( %ARGS, CallbackName => 'EndOfList', TicketObj => $Ticket );
 </table>
 <%ARGS>
diff --git a/share/html/Ticket/Elements/ShowPeople b/share/html/Ticket/Elements/ShowPeople
index 5fb399e..d056c35 100644
--- a/share/html/Ticket/Elements/ShowPeople
+++ b/share/html/Ticket/Elements/ShowPeople
@@ -66,7 +66,7 @@
     <td class="labeltop"><&|/l&>AdminCc</&>:</td>
     <td class="value"><& ShowGroupMembers, Group => $Ticket->AdminCc, Ticket => $Ticket &></td>
   </tr>
-  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Group => 'People', Table => 0 &>
+  <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'People', Table => 0 &>
 </table>
 <%INIT>
 </%INIT>
diff --git a/share/html/Ticket/Elements/ShowSummary b/share/html/Ticket/Elements/ShowSummary
index e6a63dc..2fbc46c 100644
--- a/share/html/Ticket/Elements/ShowSummary
+++ b/share/html/Ticket/Elements/ShowSummary
@@ -54,7 +54,7 @@
         class => 'ticket-info-basics',
     &><& /Ticket/Elements/ShowBasics, Ticket => $Ticket &></&>
 
-    <& /Elements/ShowCustomFieldCustomGroups,
+    <& /Elements/ShowCustomFieldCustomGroupings,
         Object       => $Ticket,
         TitleBoxARGS => {
             (($can_modify || $can_modify_cf)
diff --git a/share/html/Ticket/Modify.html b/share/html/Ticket/Modify.html
index 87beeab..bc42f7e 100644
--- a/share/html/Ticket/Modify.html
+++ b/share/html/Ticket/Modify.html
@@ -57,11 +57,11 @@
 
 <&| /Widgets/TitleBox, title => loc('Modify ticket #[_1]',$TicketObj->Id), class=>'ticket-info-basics' &>
 <& Elements/EditBasics, TicketObj => $TicketObj &>
-<& /Elements/EditCustomFields, Object => $TicketObj, DefaultsFromTopArguments => 0, Group => 'Basics' &>
+<& /Elements/EditCustomFields, Object => $TicketObj, DefaultsFromTopArguments => 0, Grouping => 'Basics' &>
 </&>
 % $m->callback( CallbackName => 'AfterBasics', Ticket => $TicketObj );
 
-<& /Elements/EditCustomFieldCustomGroups, Object => $TicketObj, DefaultsFromTopArguments => 0 &>
+<& /Elements/EditCustomFieldCustomGroupings, Object => $TicketObj, DefaultsFromTopArguments => 0 &>
 
 <& /Elements/Submit, Name => 'SubmitTicket', Label => loc('Save Changes'), Caption => loc("If you've updated anything above, be sure to"), color => "#993333" &>
 </form>
diff --git a/share/html/Ticket/ModifyAll.html b/share/html/Ticket/ModifyAll.html
index 252ed7f..5143925 100644
--- a/share/html/Ticket/ModifyAll.html
+++ b/share/html/Ticket/ModifyAll.html
@@ -57,12 +57,12 @@
 
 <&| /Widgets/TitleBox, title => loc('Modify ticket # [_1]', $Ticket->Id), class=>'ticket-info-basics' &>
 <& Elements/EditBasics, TicketObj => $Ticket &>
-<& /Elements/EditCustomFields, Object => $Ticket, Group => 'Basics' &>
+<& /Elements/EditCustomFields, Object => $Ticket, Grouping => 'Basics' &>
 </&>
 
 % $m->callback(CallbackName => 'AfterBasics', Ticket => $Ticket);
 
-<& /Elements/EditCustomFieldCustomGroups, Object => $Ticket &>
+<& /Elements/EditCustomFieldCustomGroupings, Object => $Ticket &>
 
 <&| /Widgets/TitleBox, title => loc('Dates'), class=>'ticket-info-dates'&>
 <& Elements/EditDates, TicketObj => $Ticket &>
diff --git a/t/web/cf_groups.t b/t/web/cf_groups.t
index ca50abf..8479b55 100644
--- a/t/web/cf_groups.t
+++ b/t/web/cf_groups.t
@@ -3,7 +3,7 @@ use warnings;
 
 use RT::Test tests => 67;
 
-RT->Config->Set( 'CustomFieldGroups',
+RT->Config->Set( 'CustomFieldGroupings',
     'RT::Ticket' => {
         Basics => ['TestBasics'],
         Dates  => ['TestDates'],
@@ -15,7 +15,7 @@ RT->Config->Set( 'CustomFieldGroups',
 
 my %CF;
 
-foreach my $name ( map { @$_ } values %{ RT->Config->Get('CustomFieldGroups')->{'RT::Ticket'} } ) {
+foreach my $name ( map { @$_ } values %{ RT->Config->Get('CustomFieldGroupings')->{'RT::Ticket'} } ) {
     my $cf = RT::CustomField->new( RT->SystemUser );
     my ($id, $msg) = $cf->Create(
         Name => $name,
diff --git a/t/web/cf_groups_users.t b/t/web/cf_groups_users.t
index cf9ce46..3aeb096 100644
--- a/t/web/cf_groups_users.t
+++ b/t/web/cf_groups_users.t
@@ -3,7 +3,7 @@ use warnings;
 
 use RT::Test tests => undef;
 
-RT->Config->Set( 'CustomFieldGroups',
+RT->Config->Set( 'CustomFieldGroupings',
     'RT::User' => {
         Identity         => ['TestIdentity'],
         'Access control' => ['TestAccessControl'],
@@ -15,7 +15,7 @@ RT->Config->Set( 'CustomFieldGroups',
 
 my %CF;
 
-foreach my $name ( map { @$_ } values %{ RT->Config->Get('CustomFieldGroups')->{'RT::User'} } ) {
+foreach my $name ( map { @$_ } values %{ RT->Config->Get('CustomFieldGroupings')->{'RT::User'} } ) {
     my $cf = RT::CustomField->new( RT->SystemUser );
     my ($id, $msg) = $cf->Create(
         Name => $name,

commit 0e12510448f04c5d29fb86e54bd345be6814223f
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 15 15:21:50 2012 -0700

    Typo caused intepolation of non-existent variable

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index bbf85dc..f52f24a 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -815,7 +815,7 @@ EOT
             my $groups = $config->Get('CustomFieldGroupings') || {};
 
             unless (ref($groups) eq 'HASH') {
-                RT->Logger->error("Config option %CustomFieldGroupings is a @{[ref $groups]} not a HASH; ignoring");
+                RT->Logger->error("Config option \%CustomFieldGroupings is a @{[ref $groups]} not a HASH; ignoring");
                 $groups = {};
             }
 

commit 03cec0b26798d3b6fa1ea6024abab9df29951a0a
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 15 16:49:12 2012 -0700

    Document the new CF grouping config and methods

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index a5ff80c..5d89375 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -835,6 +835,39 @@ custom field values from external sources at runtime.
 
 Set(@CustomFieldValuesSources, ());
 
+=item C<%CustomFieldGroupings>
+
+This option affects the display of ticket and user custom fields in the web
+interface.  A nested datastructure defines how to group together custom fields
+under a mix of built-in and arbitrary headings ("groupings").
+
+Set C<%CustomFieldGroupings> to a nested hash similar to the following:
+
+    Set(%CustomFieldGroupings,
+        'RT::Ticket' => {
+            'Grouping Name'     => ['CF Name', 'Another CF'],
+            'Another Grouping'  => ['Some CF'],
+            'Dates'             => ['Shipped date'],
+        },
+        'RT::User' => {
+            'Phones' => ['Fax number'],
+        },
+    );
+
+The first level keys are record types for which CFs may be used, and the values
+are hashrefs.  The second level keys are the grouping names and the values are
+array refs containing a list of CF names.
+
+There are several special built-in groupings which RT displays in specific
+places (usually the collapsible box of the same title).
+
+For C<RT::Ticket>, these groupings are: C<Basics>, C<Dates>, C<Links>, C<People>
+
+For C<RT::User>: C<Identity>, C<Access control>, C<Location>, C<Phones>
+
+Extensions may also add their own built-in groupings, refer to the individual
+extension documentation for those.
+
 =item C<$CanonicalizeRedirectURLs>
 
 Set C<$CanonicalizeRedirectURLs> to 1 to use C<$WebURL> when
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 739fa38..f1a7b6a 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1243,6 +1243,21 @@ sub CollectionClassFromLookupType {
     return $collection_class;
 }
 
+=head2 Groupings
+
+Returns a (sorted and lowercased) list of the groupings in which this custom
+field appears.
+
+If called on a loaded object, the returned list is limited to groupings which
+apply to the record class this CF applies to (L</RecordClassFromLookupType>).
+
+If passed a loaded object or a class name, the returned list is limited to
+groupings which apply to the class of the object or the specified class.
+
+If called on an unloaded object, all potential groupings are returned.
+
+=cut
+
 sub Groupings {
     my $self = shift;
     my $record = shift;
@@ -1268,6 +1283,22 @@ sub Groupings {
         @groups;
 }
 
+=head2 RegisterBuiltInGroupings
+
+Registers groupings to be considered a fundamental part of RT, either via use
+in core RT or via an extension.  These groupings must be rendered explicitly in
+Mason by specific calls to F</Elements/ShowCustomFields> and
+F</Elements/EditCustomFields>.  They will not show up automatically on normal
+display pages like configured custom groupings.
+
+Takes a set of key-value pairs of class names (valid L<RT::Record> subclasses)
+and array refs of grouping names to consider built-in.
+
+If a class already contains built-in groupings (such as L<RT::Ticket> and
+L<RT::User>), new groupings are appended.
+
+=cut
+
 my %BUILTIN_GROUPINGS;
 sub RegisterBuiltInGroupings {
     my $self = shift;
@@ -1283,6 +1314,13 @@ sub RegisterBuiltInGroupings {
     $BUILTIN_GROUPINGS{''} = { map { %$_ } values %BUILTIN_GROUPINGS  };
 }
 
+=head2 CustomGroupings
+
+Identical to L</Groupings> but filters out built-in groupings from the the
+returned list.
+
+=cut
+
 sub CustomGroupings {
     my $self = shift;
     my $record = shift;
diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index 5dc2b97..a827ac0 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -96,6 +96,25 @@ sub _Init {
     return ( $self->SUPER::_Init(@_) );
 }
 
+=head2 LimitToGrouping
+
+Limits this collection object to custom fields which appear under a
+specified grouping by calling L</Limit> for each CF name as appropriate.
+
+Requires an L<RT::Record> object or class name as the first argument and
+accepts a grouping name as the second.  If the grouping name is false
+(usually via the empty string), limits to custom fields which appear in no
+grouping.
+
+I<Caveat:> While the record object or class name is used to find the
+available groupings, no automatic limit is placed on the lookup type of
+the custom fields.  It's highly suggested you limit the collection by
+queue or another lookup type first.  This is already done for you if
+you're creating the collection via the L</CustomFields> method on an
+L<RT::Record> object.
+
+=cut
+
 sub LimitToGrouping {
     my $self = shift;
     my $obj = shift;

commit 32ad540ee6ea4818cb7955a493e60158621f436c
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 15 16:51:21 2012 -0700

    Refactor calculation of the grouping record class to use

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index f1a7b6a..9014d55 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1260,11 +1260,7 @@ If called on an unloaded object, all potential groupings are returned.
 
 sub Groupings {
     my $self = shift;
-    my $record = shift;
-
-    my $record_class = ref($record) || $record || '';
-    $record_class = $self->RecordClassFromLookupType
-        if !$record_class && $self->id;
+    my $record_class = $self->_GroupingClass(shift);
 
     my $config = RT->Config->Get('CustomFieldGroupings');
        $config = {} unless ref($config) eq 'HASH';
@@ -1323,13 +1319,19 @@ returned list.
 
 sub CustomGroupings {
     my $self = shift;
-    my $record = shift;
+    my $record_class = $self->_GroupingClass(shift);
+    return grep !$BUILTIN_GROUPINGS{$record_class}{$_}, $self->Groupings( $record_class );
+}
+
+sub _GroupingClass {
+    my $self    = shift;
+    my $record  = shift;
 
     my $record_class = ref($record) || $record || '';
     $record_class = $self->RecordClassFromLookupType
         if !$record_class && $self->id;
 
-    return grep !$BUILTIN_GROUPINGS{$record_class}{$_}, $self->Groupings( $record_class );
+    return $record_class;
 }
 
 =head1 ApplyGlobally

commit a66030dc4c1b528437cd5adacb78295d827037aa
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 15 16:52:31 2012 -0700

    Move CustomGroupings closer to Groupings so the documentation is adjacent

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 9014d55..bf9d376 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1279,6 +1279,30 @@ sub Groupings {
         @groups;
 }
 
+=head2 CustomGroupings
+
+Identical to L</Groupings> but filters out built-in groupings from the the
+returned list.
+
+=cut
+
+sub CustomGroupings {
+    my $self = shift;
+    my $record_class = $self->_GroupingClass(shift);
+    return grep !$BUILTIN_GROUPINGS{$record_class}{$_}, $self->Groupings( $record_class );
+}
+
+sub _GroupingClass {
+    my $self    = shift;
+    my $record  = shift;
+
+    my $record_class = ref($record) || $record || '';
+    $record_class = $self->RecordClassFromLookupType
+        if !$record_class && $self->id;
+
+    return $record_class;
+}
+
 =head2 RegisterBuiltInGroupings
 
 Registers groupings to be considered a fundamental part of RT, either via use
@@ -1310,30 +1334,6 @@ sub RegisterBuiltInGroupings {
     $BUILTIN_GROUPINGS{''} = { map { %$_ } values %BUILTIN_GROUPINGS  };
 }
 
-=head2 CustomGroupings
-
-Identical to L</Groupings> but filters out built-in groupings from the the
-returned list.
-
-=cut
-
-sub CustomGroupings {
-    my $self = shift;
-    my $record_class = $self->_GroupingClass(shift);
-    return grep !$BUILTIN_GROUPINGS{$record_class}{$_}, $self->Groupings( $record_class );
-}
-
-sub _GroupingClass {
-    my $self    = shift;
-    my $record  = shift;
-
-    my $record_class = ref($record) || $record || '';
-    $record_class = $self->RecordClassFromLookupType
-        if !$record_class && $self->id;
-
-    return $record_class;
-}
-
 =head1 ApplyGlobally
 
 Certain custom fields (users, groups) should only be applied globally

commit 69128b644d58d44237b901383be5ec80b04debcd
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 15 17:42:22 2012 -0700

    RT::CustomField->Groupings should always return built-in groupings
    
    Even if they're not specified in the config.

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 5d89375..2200a48 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2749,19 +2749,4 @@ with L</Lifecycles> (see L</Labeling and defining actions>).
 
 =cut
 
-Set(%CustomFieldGroupings,
-    'RT::Ticket' => {
-        Basics => [],
-        Dates => [],
-        People => [],
-        Links   => [],
-    },
-    'RT::User' => {
-        Identity => [],
-        'Access control' => [],
-        Location => [],
-        Phones   => [],
-    },
-);
-
 1;
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index bf9d376..18bad78 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -192,6 +192,7 @@ our %FieldTypes = (
 );
 
 
+my %BUILTIN_GROUPINGS;
 our %FRIENDLY_OBJECT_TYPES =  ();
 
 RT::CustomField->_ForObjectType( 'RT::Queue-RT::Ticket' => "Tickets", );    #loc
@@ -1267,9 +1268,11 @@ sub Groupings {
 
     my @groups;
     if ( $record_class ) {
-        @groups = keys %{ $config->{$record_class} };
+        push @groups, keys %{ $config->{$record_class} || {} };
+        push @groups, keys %{ $BUILTIN_GROUPINGS{$record_class} || {} };
     } else {
-        @groups = map { keys %$_ } values %$config;
+        push @groups, map { keys %$_ } values %$config;
+        push @groups, map { keys %$_ } values %BUILTIN_GROUPINGS;
     }
 
     my %seen;
@@ -1319,7 +1322,6 @@ L<RT::User>), new groupings are appended.
 
 =cut
 
-my %BUILTIN_GROUPINGS;
 sub RegisterBuiltInGroupings {
     my $self = shift;
     my %new  = @_;

commit 290d17a662a98940cbe6061e7b2d1a76f6c8cec0
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 15 17:46:34 2012 -0700

    Allow Groupings and CustomGroupings to be called as class methods

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 18bad78..a036534 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -57,7 +57,7 @@ use base 'RT::Record';
 
 sub Table {'CustomFields'}
 
-
+use Scalar::Util qw(blessed);
 use RT::CustomFieldValues;
 use RT::ObjectCustomFields;
 use RT::ObjectCustomFieldValues;
@@ -1301,7 +1301,7 @@ sub _GroupingClass {
 
     my $record_class = ref($record) || $record || '';
     $record_class = $self->RecordClassFromLookupType
-        if !$record_class && $self->id;
+        if !$record_class and blessed($self) and $self->id;
 
     return $record_class;
 }

commit 0b297ee2748589ae856a876c23dc1dc96e9f5c13
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Nov 2 23:12:01 2012 -0700

    Replace form_name/input/click pattern with a simple usage of submit_form_ok
    
    The "with_fields" form selection is more robust than relying on the form
    name, which may change when this branch is merged.  Additionally,
    submit_form_ok validates that it was able to a) find a form, b) find the
    fields to fill in, and c) click the button.  None of previously used
    methods were error checked.
    
    Two uses of form_name still exist, but they are a slightly more complex
    pattern of selecting a form and building up inputs in a loop.
    Additionally, the DOM is checked more rigorously for correct inputs in
    those test blocks.
    
    For ease of development, switch to a "done_testing()" approach to the
    test plan rather than a static count.

diff --git a/t/web/cf_groups.t b/t/web/cf_groups.t
index 8479b55..dfbbb7e 100644
--- a/t/web/cf_groups.t
+++ b/t/web/cf_groups.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 67;
+use RT::Test tests => undef;
 
 RT->Config->Set( 'CustomFieldGroupings',
     'RT::Ticket' => {
@@ -91,50 +91,56 @@ ok $m->login, 'logged in as root';
     note "testing Basics/People/Dates/Links pages";
     { # Basics
         $m->follow_link_ok({id => 'page-basics'}, 'Ticket -> Basics');
-        $m->form_name("TicketModify");
         is $m->dom->find(qq{input[name^="$prefix"][name\$="-Value"]})->size, 2,
             "only one CF input on the page";
         my $input_name = $prefix . $CF{'TestBasics'}->id .'-Value';
         ok $m->dom->at(qq{.ticket-info-basics input[name="$input_name"]}),
             "CF is in the right place";
-        $m->field( $input_name, "TestBasicsChanged" );
-        $m->click('SubmitTicket');
+        $m->submit_form_ok({
+            with_fields => { $input_name => "TestBasicsChanged" },
+            button      => 'SubmitTicket',
+        });
         $m->content_like(qr{to TestBasicsChanged});
 
-        $m->form_name("TicketModify");
-        $m->field( $input_name, "bad value" );
-        $m->click('SubmitTicket');
+        $m->submit_form_ok({
+            with_fields => { $input_name => "bad value" },
+            button      => 'SubmitTicket',
+        });
         $m->content_like(qr{Input must match});
     }
     { # Custom group 'More'
         $m->follow_link_ok({id => 'page-basics'}, 'Ticket -> Basics');
-        $m->form_name("TicketModify");
         my $input_name = $prefix . $CF{'TestMore'}->id .'-Value';
         ok $m->dom->at(qq{.ticket-info-cfs input[name="$input_name"]}),
             "CF is in the right place";
-        $m->field( $input_name, "TestMoreChanged" );
-        $m->click('SubmitTicket');
+        $m->submit_form_ok({
+            with_fields => { $input_name => "TestMoreChanged" },
+            button      => 'SubmitTicket',
+        });
         $m->content_like(qr{to TestMoreChanged});
 
-        $m->form_name("TicketModify");
-        $m->field( $input_name, "bad value" );
-        $m->click('SubmitTicket');
+        $m->submit_form_ok({
+            with_fields => { $input_name => "bad value" },
+            button      => 'SubmitTicket',
+        });
         $m->content_like(qr{Input must match});
     }
 
     foreach my $name ( qw(People Dates Links) ) {
         $m->follow_link_ok({id => "page-\L$name"}, "Ticket's $name page");
-        $m->form_name("Ticket$name");
         is $m->dom->find(qq{input[name^="$prefix"][name\$="-Value"]})->size, 1,
             "only one CF input on the page";
         my $input_name = $prefix . $CF{"Test$name"}->id .'-Value';
-        $m->field( $input_name, "Test${name}Changed" );
-        $m->click('SubmitTicket');
+        $m->submit_form_ok({
+            with_fields => { $input_name => "Test${name}Changed" },
+            button      => 'SubmitTicket',
+        });
         $m->content_like(qr{to Test${name}Changed});
 
-        $m->form_name("Ticket$name");
-        $m->field( $input_name, "bad value" );
-        $m->click('SubmitTicket');
+        $m->submit_form_ok({
+            with_fields => { $input_name => "bad value" },
+            button      => 'SubmitTicket',
+        });
         $m->content_like(qr{Input must match});
     }
 
@@ -154,3 +160,6 @@ ok $m->login, 'logged in as root';
         $m->content_like(qr{to Test${name}Again});
     }
 }
+
+undef $m;
+done_testing;

commit 312fa9b0fed961cd9b947ef430ab04fcdc7f6dc7
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Mon Nov 5 17:05:58 2012 -0500

    Don't pass one shared CustomFields object between CF grouping lists
    
    In the simple case os passing one $CustomFields into
    EditCustomFieldCustomGroupings, the same $CustomFields is passed to each
    EditCustomFields component, causing it to be repeatedly limited.
    Instead pass in a generator sub, which allows us to produce a distinct
    object for each grouping.

diff --git a/share/html/Elements/EditCustomFieldCustomGroupings b/share/html/Elements/EditCustomFieldCustomGroupings
index 15d1e7a..34e245c 100644
--- a/share/html/Elements/EditCustomFieldCustomGroupings
+++ b/share/html/Elements/EditCustomFieldCustomGroupings
@@ -51,11 +51,13 @@
     class => $css_class .' '. ($group? CSSClass("$css_class-$group") : ''),
     hide_empty => 1,
 &>
+% $ARGS{CustomFields} = $CustomFieldGenerator->() if $CustomFieldGenerator;
 <& EditCustomFields, %ARGS, Object => $Object, Grouping => $group &>
 </&>
 % }
 <%ARGS>
 $Object
+$CustomFieldGenerator => undef,
 </%ARGS>
 <%INIT>
 my $css_class = lc(ref($Object)||$Object);
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 56cd074..7d4784d 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -116,7 +116,7 @@
 <& /Elements/EditCustomFieldCustomGroupings,
     %ARGS,
     Object => $ticket,
-    CustomFields => $QueueObj->TicketCustomFields,
+    CustomFieldGenerator => sub { $QueueObj->TicketCustomFields },
     DefaultsFromTopArguments => 0,
 &>
 

-----------------------------------------------------------------------


More information about the Rt-commit mailing list