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

Alex Vandiver alexmv at bestpractical.com
Thu Nov 15 18:30:52 EST 2012


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

- Log -----------------------------------------------------------------
commit efb26763bed7aa807e811b75c50df2b99596f97c
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 bc253693e97b026ce73d7f3205478dae3638399c
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 f3c3903da9d1ec9e0875f51584ffaa571d2ff983
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 ec3c23af337709ce46498f6b0f54a25f0f0ae266
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 c2802d83fb9037f58b5c49c71b04d90022f41e7f
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 6614b3985294f1fb1d75e14b4b847e6144933f8b
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 0902cde5926aad53415cf4e0db862cd86e8e501f
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 17a3afd20d79cc277b7676407a1083ed3cfc2ad2
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 68aade51a53c05145ad80541ac37420594f58e07
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 8917f7942209fcec20c898c2ea80e2eaa138b3cf
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 23c9d675a7f8a65a02995668b4efd68b9484ff28
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 d3637ac058173e88667fbf35de4eca28f499d6da
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 9ddb3dbb570be241517d3fa09ac3dc6e075c66b5
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 db1846bc8d9bc3c27bd8fcbfb519fc92540fbe73
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 4a1176f..8c4d6d6 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -3382,6 +3382,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 354a9ee3b2929cd1ef13ec9f46df39b2e82ed5eb
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 31eb53e1072794e7b8f8e4e62496d985c19f68ce
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 c0333c2cb31e00bdb564868548112dbeb8ae61fa
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 66ef0b01c623307832c3f7b914ab5ef7e53ece33
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 43f152d1950de42268649c10391dfd6000152ce5
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 47f6b6df4d4939ce873db596d5d32a66d5165595
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 1364022818f362f6736b90bcc5ddc761c543416e
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 2b29c4765829219e148488b8d4ad63d3f44fec34
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 5083f796d4b616d1bd9c2bbf46567a4cad43a91c
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 7ebac86a041922ff3c868fdb4b73cba420c7db6a
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 038c4b52bad0285a4436ad9dd0b4059bca56aff1
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 21fe63650e6df9d265fa83d19f711b98be95a280
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 8ef95236d66fd68962e333932f739a24bb3db208
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 d77618cbb06e9a14440fe5d70b7678ae71952e60
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 a47ef9133bfdb190a94e98521f6c4242f03570cc
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 d2a79aac0fbcbe3d8c1aa76433af82bd93ee2fa9
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 ce7dbd542785021aea360f2d119483de56ff8360
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 1f41abd89aea5a0d9082f34008b2507e8fdc7ed2
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 90c1a308d6cb7b8f0eda4c7454cfe9c6806762d1
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 a491d555fb9fe5c74c737078b1c5328a332ef367
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 f8e7ad49c1f478fa8c6290cdce8437e99a0845d1
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 f6e146fb79532dd0e9a6f7ec11bf4b3e0c8e044e
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 a79755e1acccc79e7732048bf92d5b0532d3016c
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