[Rt-commit] rt branch, 4.2/apply-scrips-to-multiple-queues, created. rt-4.0.8-460-ge168ad8

Alex Vandiver alexmv at bestpractical.com
Tue Nov 13 17:04:53 EST 2012


The branch, 4.2/apply-scrips-to-multiple-queues has been created
        at  e168ad8f10451250f79e8bbc47d655e50c4ca9df (commit)

- Log -----------------------------------------------------------------
commit d9636ddee9b6c1d27adf246ca11eb7f955d9b62c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Dec 12 19:05:31 2011 +0400

    Extract concept of many-to-many association and sorting
    
    ObjectCustomFields contain the core concept of taking a set of objects
    (custom fields) and applying them either globally or to a distinct
    subset of other objects (queues).  Furthermore, it provides a way of
    sorting those applications, including dealing with the complexities of
    the interaction between global and queue-specific ordering.  This
    concept is a useful one to abstract out into a RT::Record (and
    RT::SearchBuilder) subclass, named ApplyAndSort.

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index a409c3a..1a0c17a 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1160,9 +1160,7 @@ sub SetLookupType {
     my $lookup = shift;
     if ( $lookup ne $self->LookupType ) {
         # Okay... We need to invalidate our existing relationships
-        my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
-        $ObjectCustomFields->LimitToCustomField($self->Id);
-        $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
+        RT::ObjectCustomField->new($self->CurrentUser)->DeleteAll( CustomField => $self );
     }
     return $self->_Set(Field => 'LookupType', Value =>$lookup);
 }
@@ -1265,18 +1263,8 @@ Doesn't takes into account if object is applied globally.
 
 sub AppliedTo {
     my $self = shift;
-
-    my ($res, $ocfs_alias) = $self->_AppliedTo;
-    return $res unless $res;
-
-    $res->Limit(
-        ALIAS     => $ocfs_alias,
-        FIELD     => 'id',
-        OPERATOR  => 'IS NOT',
-        VALUE     => 'NULL',
-    );
-
-    return $res;
+    return RT::ObjectCustomField->new( $self->CurrentUser )
+        ->AppliedTo( CustomField => $self );
 }
 
 =head1 NotAppliedTo
@@ -1291,48 +1279,8 @@ Doesn't takes into account if object is applied globally.
 
 sub NotAppliedTo {
     my $self = shift;
-
-    my ($res, $ocfs_alias) = $self->_AppliedTo;
-    return $res unless $res;
-
-    $res->Limit(
-        ALIAS     => $ocfs_alias,
-        FIELD     => 'id',
-        OPERATOR  => 'IS',
-        VALUE     => 'NULL',
-    );
-
-    return $res;
-}
-
-sub _AppliedTo {
-    my $self = shift;
-
-    my ($class) = $self->CollectionClassFromLookupType;
-    return undef unless $class;
-
-    my $res = $class->new( $self->CurrentUser );
-
-    # If CF is a Group CF, only display user-defined groups
-    if ( $class eq 'RT::Groups' ) {
-        $res->LimitToUserDefinedGroups;
-    }
-
-    $res->OrderBy( FIELD => 'Name' );
-    my $ocfs_alias = $res->Join(
-        TYPE   => 'LEFT',
-        ALIAS1 => 'main',
-        FIELD1 => 'id',
-        TABLE2 => 'ObjectCustomFields',
-        FIELD2 => 'ObjectId',
-    );
-    $res->Limit(
-        LEFTJOIN => $ocfs_alias,
-        ALIAS    => $ocfs_alias,
-        FIELD    => 'CustomField',
-        VALUE    => $self->id,
-    );
-    return ($res, $ocfs_alias);
+    return RT::ObjectCustomField->new( $self->CurrentUser )
+        ->NotAppliedTo( CustomField => $self );
 }
 
 =head2 IsApplied
@@ -1374,26 +1322,9 @@ sub AddToObject {
         return ( 0, $self->loc('Permission Denied') );
     }
 
-    if ( $self->IsApplied( $id ) ) {
-        return ( 0, $self->loc("Custom field is already applied to the object") );
-    }
-
-    if ( $id ) {
-        # applying locally
-        return (0, $self->loc("Couldn't apply custom field to an object as it's global already") )
-            if $self->IsApplied( 0 );
-    }
-    else {
-        my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
-        $applied->LimitToCustomField( $self->id );
-        while ( my $record = $applied->Next ) {
-            $record->Delete;
-        }
-    }
-
     my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
-    my ( $oid, $msg ) = $ocf->Create(
-        ObjectId => $id, CustomField => $self->id,
+    my ( $oid, $msg ) = $ocf->Apply(
+        CustomField => $self->id, ObjectId => $id,
     );
     return ( $oid, $msg );
 }
diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index d4a5bc7..bf15b16 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -170,28 +170,6 @@ sub LimitToGlobalOrObjectId {
                  ENTRYAGGREGATOR => 'OR' ) unless $global_only;
 }
 
-sub _LimitToOCFs {
-    my $self = shift;
-    my @ids = @_;
-
-    my $ocfs_alias = $self->_OCFAlias( New => 1, Left => 1 );
-    if ( @ids ) {
-        # XXX: we need different EA in join clause, but DBIx::SB
-        # doesn't support them, use IN (X) instead
-        my $dbh = $self->_Handle->dbh;
-        $self->Limit(
-            LEFTJOIN   => $ocfs_alias,
-            ALIAS      => $ocfs_alias,
-            FIELD      => 'ObjectId',
-            OPERATOR   => 'IN',
-            QUOTEVALUE => 0,
-            VALUE      => "(". join( ',', map $dbh->quote($_), @ids ) .")",
-        );
-    }
-
-    return $ocfs_alias;
-}
-
 =head2 LimitToNotApplied
 
 Takes either list of object ids or nothing. Limits collection
@@ -202,17 +180,8 @@ zero to mean global.
 
 sub LimitToNotApplied {
     my $self = shift;
-    my @ids = @_;
-
-    my $ocfs_alias = $self->_LimitToOCFs(@ids);
-
-    $self->Limit(
-        ENTRYAGGREGATOR => 'AND',
-        ALIAS    => $ocfs_alias,
-        FIELD    => 'id',
-        OPERATOR => 'IS',
-        VALUE    => 'NULL',
-    );
+    return RT::ObjectCustomFields->new( $self->CurrentUser )
+        ->LimitTargetToNotApplied( $self => @_ );
 }
 
 =head2 LimitToApplied
@@ -224,17 +193,8 @@ zero to mean global.
 
 sub LimitToApplied {
     my $self = shift;
-    my @ids = @_;
-
-    my $ocfs_alias = $self->_LimitToOCFs(@ids);
-
-    $self->Limit(
-        ENTRYAGGREGATOR => 'AND',
-        ALIAS    => $ocfs_alias,
-        FIELD    => 'id',
-        OPERATOR => 'IS NOT',
-        VALUE    => 'NULL',
-    );
+    return RT::ObjectCustomFields->new( $self->CurrentUser )
+        ->LimitTargetToApplied( $self => @_ );
 }
 
 =head2 LimitToGlobalOrQueue QUEUEID
@@ -344,19 +304,8 @@ sub SetContextObject {
 
 sub _OCFAlias {
     my $self = shift;
-    my %args = ( New => 0, Left => 0, @_ );
-
-    return $self->{'_sql_ocfalias'} if $self->{'_sql_ocfalias'} && !$args{'New'};
-
-    my $alias = $self->Join(
-        $args{'Left'} ? (TYPE => 'LEFT') : (),
-        ALIAS1 => 'main',
-        FIELD1 => 'id',
-        TABLE2 => 'ObjectCustomFields',
-        FIELD2 => 'CustomField'
-    );
-    return $alias if $args{'New'};
-    return $self->{'_sql_ocfalias'} = $alias;
+    return RT::ObjectCustomFields->new( $self->CurrentUser )
+        ->JoinTargetToThis( $self => @_ );
 }
 
 
diff --git a/lib/RT/ObjectCustomField.pm b/lib/RT/ObjectCustomField.pm
index 61bc355..f4d2483 100644
--- a/lib/RT/ObjectCustomField.pm
+++ b/lib/RT/ObjectCustomField.pm
@@ -45,93 +45,29 @@
 # those contributions and any derivatives thereof.
 #
 # END BPS TAGGED BLOCK }}}
-
-package RT::ObjectCustomField;
-
 use strict;
 use warnings;
 
+package RT::ObjectCustomField;
+use base 'RT::Record::ApplyAndSort';
 
 use RT::CustomField;
-use base 'RT::Record';
+use RT::ObjectCustomFields;
 
 sub Table {'ObjectCustomFields'}
 
-
-
-
-
-
-sub Create {
+sub ObjectCollectionClass {
     my $self = shift;
-    my %args = (
-        CustomField => 0,
-        ObjectId    => 0,
-        SortOrder   => undef,
-        @_
-    );
-
-    my $cf = $self->CustomFieldObj( $args{'CustomField'} );
-    unless ( $cf->id ) {
-        $RT::Logger->error("Couldn't load '$args{'CustomField'}' custom field");
-        return 0;
-    }
-
-    #XXX: Where is ACL check for 'AssignCustomFields'?
-
-    my $ObjectCFs = RT::ObjectCustomFields->new($self->CurrentUser);
-    $ObjectCFs->LimitToObjectId( $args{'ObjectId'} );
-    $ObjectCFs->LimitToCustomField( $cf->id );
-    $ObjectCFs->LimitToLookupType( $cf->LookupType );
-    if ( my $first = $ObjectCFs->First ) {
-        $self->Load( $first->id );
-        return $first->id;
-    }
-
-    unless ( defined $args{'SortOrder'} ) {
-        my $ObjectCFs = RT::ObjectCustomFields->new( RT->SystemUser );
-        $ObjectCFs->LimitToObjectId( $args{'ObjectId'} );
-        $ObjectCFs->LimitToObjectId( 0 ) if $args{'ObjectId'};
-        $ObjectCFs->LimitToLookupType( $cf->LookupType );
-        $ObjectCFs->OrderBy( FIELD => 'SortOrder', ORDER => 'DESC' );
-        if ( my $first = $ObjectCFs->First ) {
-            $args{'SortOrder'} = $first->SortOrder + 1;
-        } else {
-            $args{'SortOrder'} = 0;
-        }
-    }
-
-    return $self->SUPER::Create(
-        CustomField => $args{'CustomField'},
-        ObjectId    => $args{'ObjectId'},
-        SortOrder   => $args{'SortOrder'},
-    );
-}
-
-sub Delete {
-    my $self = shift;
-
-    my $ObjectCFs = RT::ObjectCustomFields->new($self->CurrentUser);
-    $ObjectCFs->LimitToObjectId($self->ObjectId);
-    $ObjectCFs->LimitToLookupType($self->CustomFieldObj->LookupType);
-
-    # Move everything below us up
-    my $sort_order = $self->SortOrder;
-    while (my $OCF = $ObjectCFs->Next) {
-        my $this_order = $OCF->SortOrder;
-        next if $this_order <= $sort_order; 
-        $OCF->SetSortOrder($this_order - 1);
-    }
-
-    $self->SUPER::Delete;
+    my %args = (@_);
+    return $args{'CustomField'}->CollectionClassFromLookupType;
 }
 
+# XXX: Where is ACL check when we create a record?
 
 =head2 CustomFieldObj
 
 Returns the CustomField Object which has the id returned by CustomField
 
-
 =cut
 
 sub CustomFieldObj {
@@ -154,148 +90,17 @@ sub CustomFieldObj {
     return $CF;
 }
 
-=head2 Sorting custom fields applications
-
-Custom fields sorted on multiple layers. First of all custom
-fields with different lookup type are sorted independently. All
-global custom fields have fixed order for all objects, but you
-can insert object specific custom fields between them. Object
-specific custom fields can be applied to several objects and
-be on different place. For example you have GCF1, GCF2, LCF1,
-LCF2 and LCF3 that applies to tickets. You can place GCF2
-above GCF1, but they will be in the same order in all queues.
-However, LCF1 and other local can be placed at any place
-for particular queue: above global, between them or below.
-
-=head3 MoveUp
-
-Moves custom field up. See </Sorting custom fields applications>.
-
-=cut
-
-sub MoveUp {
+sub Neighbors {
     my $self = shift;
+    my %args = @_;
 
-    my $ocfs = RT::ObjectCustomFields->new( $self->CurrentUser );
-
-    my $oid = $self->ObjectId;
-    $ocfs->LimitToObjectId( $oid );
-    if ( $oid ) {
-        $ocfs->LimitToObjectId( 0 );
-    }
-
-    my $cf = $self->CustomFieldObj;
-    $ocfs->LimitToLookupType( $cf->LookupType );
-
-    $ocfs->Limit( FIELD => 'SortOrder', OPERATOR => '<', VALUE => $self->SortOrder );
-    $ocfs->OrderByCols( { FIELD => 'SortOrder', ORDER => 'DESC' } );
-
-    my @above = ($ocfs->Next, $ocfs->Next);
-    unless ($above[0]) {
-        return (0, "Can not move up. It's already at the top");
-    }
-
-    my $new_sort_order;
-    if ( $above[0]->ObjectId == $self->ObjectId ) {
-        $new_sort_order = $above[0]->SortOrder;
-        my ($status, $msg) = $above[0]->SetSortOrder( $self->SortOrder );
-        unless ( $status ) {
-            return (0, "Couldn't move custom field");
-        }
-    }
-    elsif ( $above[1] && $above[0]->SortOrder == $above[1]->SortOrder + 1 ) {
-        my $move_ocfs = RT::ObjectCustomFields->new( RT->SystemUser );
-        $move_ocfs->LimitToLookupType( $cf->LookupType );
-        $move_ocfs->Limit(
-            FIELD => 'SortOrder',
-            OPERATOR => '>=',
-            VALUE => $above[0]->SortOrder,
-        );
-        $move_ocfs->OrderByCols( { FIELD => 'SortOrder', ORDER => 'DESC' } );
-        while ( my $record = $move_ocfs->Next ) {
-            my ($status, $msg) = $record->SetSortOrder( $record->SortOrder + 1 );
-            unless ( $status ) {
-                return (0, "Couldn't move custom field");
-            }
-        }
-        $new_sort_order = $above[0]->SortOrder;
-    } else {
-        $new_sort_order = $above[0]->SortOrder - 1;
-    }
-
-    my ($status, $msg) = $self->SetSortOrder( $new_sort_order );
-    unless ( $status ) {
-        return (0, "Couldn't move custom field");
-    }
-
-    return (1,"Moved custom field up");
-}
-
-=head3 MoveDown
-
-Moves custom field down. See </Sorting custom fields applications>.
-
-=cut
-
-sub MoveDown {
-    my $self = shift;
-
-    my $ocfs = RT::ObjectCustomFields->new( $self->CurrentUser );
-
-    my $oid = $self->ObjectId;
-    $ocfs->LimitToObjectId( $oid );
-    if ( $oid ) {
-        $ocfs->LimitToObjectId( 0 );
-    }
-
-    my $cf = $self->CustomFieldObj;
-    $ocfs->LimitToLookupType( $cf->LookupType );
-
-    $ocfs->Limit( FIELD => 'SortOrder', OPERATOR => '>', VALUE => $self->SortOrder );
-    $ocfs->OrderByCols( { FIELD => 'SortOrder', ORDER => 'ASC' } );
-
-    my @below = ($ocfs->Next, $ocfs->Next);
-    unless ($below[0]) {
-        return (0, "Can not move down. It's already at the bottom");
-    }
-
-    my $new_sort_order;
-    if ( $below[0]->ObjectId == $self->ObjectId ) {
-        $new_sort_order = $below[0]->SortOrder;
-        my ($status, $msg) = $below[0]->SetSortOrder( $self->SortOrder );
-        unless ( $status ) {
-            return (0, "Couldn't move custom field");
-        }
-    }
-    elsif ( $below[1] && $below[0]->SortOrder + 1 == $below[1]->SortOrder ) {
-        my $move_ocfs = RT::ObjectCustomFields->new( RT->SystemUser );
-        $move_ocfs->LimitToLookupType( $cf->LookupType );
-        $move_ocfs->Limit(
-            FIELD => 'SortOrder',
-            OPERATOR => '<=',
-            VALUE => $below[0]->SortOrder,
-        );
-        $move_ocfs->OrderByCols( { FIELD => 'SortOrder', ORDER => 'ASC' } );
-        while ( my $record = $move_ocfs->Next ) {
-            my ($status, $msg) = $record->SetSortOrder( $record->SortOrder - 1 );
-            unless ( $status ) {
-                return (0, "Couldn't move custom field");
-            }
-        }
-        $new_sort_order = $below[0]->SortOrder;
-    } else {
-        $new_sort_order = $below[0]->SortOrder + 1;
-    }
-
-    my ($status, $msg) = $self->SetSortOrder( $new_sort_order );
-    unless ( $status ) {
-        return (0, "Couldn't move custom field");
-    }
-
-    return (1,"Moved custom field down");
+    my $res = $self->CollectionClass->new( $self->CurrentUser );
+    $res->LimitToLookupType(
+        ($args{'CustomField'} || $self->CustomFieldObj)->LookupType
+    );
+    return $res;
 }
 
-
 =head2 id
 
 Returns the current value of id.
diff --git a/lib/RT/ObjectCustomFields.pm b/lib/RT/ObjectCustomFields.pm
index 9864949..c863dab 100644
--- a/lib/RT/ObjectCustomFields.pm
+++ b/lib/RT/ObjectCustomFields.pm
@@ -45,48 +45,23 @@
 # those contributions and any derivatives thereof.
 #
 # END BPS TAGGED BLOCK }}}
-
-package RT::ObjectCustomFields;
-
 use strict;
 use warnings;
 
+package RT::ObjectCustomFields;
+use base 'RT::SearchBuilder::ApplyAndSort';
 
+use RT::CustomField;
 use RT::ObjectCustomField;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'ObjectCustomFields'}
 
-sub _Init {
-    my $self = shift;
-
-  # By default, order by SortOrder
-  $self->OrderByCols(
-	 { ALIAS => 'main',
-	   FIELD => 'SortOrder',
-	   ORDER => 'ASC' },
-	 { ALIAS => 'main',
-	   FIELD => 'id',
-	   ORDER => 'ASC' },
-     );
-
-    return ( $self->SUPER::_Init(@_) );
-}
-
-
 sub LimitToCustomField {
     my $self = shift;
     my $id = shift;
     $self->Limit( FIELD => 'CustomField', VALUE => $id );
 }
 
-sub LimitToObjectId {
-    my $self = shift;
-    my $id = shift || 0;
-    $self->Limit( FIELD => 'ObjectId', VALUE => $id );
-}
-
 sub LimitToLookupType {
     my $self = shift;
     my $lookup = shift;
@@ -139,17 +114,6 @@ sub _DoSearch {
     $self->SUPER::_DoSearch()
 }
 
-
-=head2 NewItem
-
-Returns an empty new RT::ObjectCustomField item
-
-=cut
-
-sub NewItem {
-    my $self = shift;
-    return(RT::ObjectCustomField->new($self->CurrentUser));
-}
 RT::Base->_ImportOverlays();
 
 1;
diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
new file mode 100644
index 0000000..4f71c29
--- /dev/null
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -0,0 +1,423 @@
+# 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 }}}
+
+use strict;
+use warnings;
+
+package RT::Record::ApplyAndSort;
+use base 'RT::Record';
+
+sub CollectionClass {
+    return (ref($_[0]) || $_[0]).'s';
+}
+
+sub ObjectCollectionClass { die "should be subclassed" }
+
+sub TargetField {
+    my $class = ref($_[0]) || $_[0];
+    $class =~ s/.*::Object// or return undef;
+    return $class;
+}
+
+sub Create {
+    my $self = shift;
+    my %args = (
+        ObjectId    => 0,
+        SortOrder   => undef,
+        @_
+    );
+
+    my $tfield = $self->TargetField;
+
+    my $target = $self->TargetObj( $args{ $tfield } );
+    unless ( $target->id ) {
+        $RT::Logger->error("Couldn't load ". ref($target) ." '$args{$tfield}'");
+        return 0;
+    }
+
+    my $exist = $self->new($self->CurrentUser);
+    $exist->LoadByCols( ObjectId => $args{'ObjectId'}, $tfield => $target->id );
+    if ( $exist->id ) {
+        $self->Load( $exist->id );
+        return $self->id;
+    }
+
+    unless ( defined $args{'SortOrder'} ) {
+        $args{'SortOrder'} = $self->NextSortOrder(
+            $tfield  => $target,
+            ObjectId => $args{'ObjectId'},
+        );
+    }
+
+    return $self->SUPER::Create(
+        %args,
+        $tfield   => $target->id,
+    );
+}
+
+sub Apply {
+    my $self = shift;
+    my %args = (@_);
+
+    my $field = $self->TargetField;
+
+    my $tid = $args{ $field };
+    $tid = $tid->id if ref $tid;
+    $tid ||= $self->TargetObj->id;
+
+    my $oid = $args{'ObjectId'};
+    $oid = $oid->id if ref $oid;
+    $oid ||= 0;
+
+    if ( $self->IsApplied( $tid => $oid ) ) {
+        return ( 0, $self->loc("Is already applied to the object") );
+    }
+
+    if ( $oid ) {
+        # applying locally
+        return (0, $self->loc("Couldn't apply as it's global already") )
+            if $self->IsApplied( $tid => 0 );
+    }
+    else {
+        $self->DeleteAll( $field => $tid );
+    }
+
+    return $self->Create(
+        $field => $tid, ObjectId => $oid,
+    );
+}
+
+sub IsApplied {
+    my $self = shift;
+    my ($tid, $oid) = @_;
+    my $record = $self->new( $self->CurrentUser );
+    $record->LoadByCols( $self->TargetField => $tid, ObjectId => $oid );
+    return $record->id;
+}
+
+=head1 ApplyTo
+
+Returns collection with objects this custom field is applied to.  Class of
+the collection depends on L</LookupType>.  See all L</NotAppliedTo> .
+
+Doesn't take into account if the object is applied globally.
+
+=cut
+
+sub AppliedTo {
+    my $self = shift;
+
+    my ($res, $alias) = $self->_AppliedTo( @_ );
+    return $res unless $res;
+
+    $res->Limit(
+        ALIAS     => $alias,
+        FIELD     => 'id',
+        OPERATOR  => 'IS NOT',
+        VALUE     => 'NULL',
+    );
+
+    return $res;
+}
+
+=head1 NotAppliedTo
+
+Returns collection with objects this custom field is not applied to.
+Class of the collection depends on L</LookupType>.  See all L</AppliedTo>.
+
+Doesn't take into account if the object is applied globally.
+
+=cut
+
+sub NotAppliedTo {
+    my $self = shift;
+
+    my ($res, $alias) = $self->_AppliedTo( @_ );
+    return $res unless $res;
+
+    $res->Limit(
+        ALIAS     => $alias,
+        FIELD     => 'id',
+        OPERATOR  => 'IS',
+        VALUE     => 'NULL',
+    );
+
+    return $res;
+}
+
+sub _AppliedTo {
+    my $self = shift;
+    my %args = (@_);
+
+    my $field = $self->TargetField;
+    my $target = $args{ $field } || $self->TargetObj;
+
+    my ($class) = $self->ObjectCollectionClass( $field => $target );
+    return undef unless $class;
+
+    my $res = $class->new( $self->CurrentUser );
+
+    # If target is applied to a Group, only display user-defined groups
+    $res->LimitToUserDefinedGroups if $class eq 'RT::Groups';
+
+    $res->OrderBy( FIELD => 'Name' );
+    my $alias = $res->Join(
+        TYPE   => 'LEFT',
+        ALIAS1 => 'main',
+        FIELD1 => 'id',
+        TABLE2 => $self->Table,
+        FIELD2 => 'ObjectId',
+    );
+    $res->Limit(
+        LEFTJOIN => $alias,
+        ALIAS    => $alias,
+        FIELD    => $field,
+        VALUE    => $target->id,
+    );
+    return ($res, $alias);
+}
+
+sub Delete {
+    my $self = shift;
+
+    return $self->SUPER::Delete if $self->IsSortOrderShared;
+
+    # Move everything below us up
+    my $siblings = $self->Neighbors;
+    $siblings->Limit( FIELD => 'SortOrder', OPERATOR => '>=', VALUE => $self->SortOrder );
+    $siblings->OrderBy( FIELD => 'SortOrder', ORDER => 'ASC' );
+    foreach my $record ( @{ $siblings->ItemsArrayRef } ) {
+        $record->SetSortOrder($record->SortOrder - 1);
+    }
+
+    return $self->SUPER::Delete;
+}
+
+sub DeleteAll {
+    my $self = shift;
+    my %args = (@_);
+
+    my $field = $self->TargetField;
+
+    my $id = $args{ $field };
+    $id = $id->id if ref $id;
+    $id ||= $self->TargetObj->id;
+
+    my $list = $self->CollectionClass->new( $self->CurrentUser );
+    $list->Limit( FIELD => $field, VALUE => $id );
+    $_->Delete foreach @{ $list->ItemsArrayRef };
+}
+
+=head3 MoveUp
+
+Moves the object up.
+
+=cut
+
+sub MoveUp { return shift->Move( Up => @_ ) }
+
+=head3 MoveDown
+
+Moves the object down.
+
+=cut
+
+sub MoveDown { return shift->Move( Down => @_ ) }
+
+sub Move {
+    my $self = shift;
+    my $dir = lc(shift || 'up');
+
+    my %meta;
+    if ( $dir eq 'down' ) {
+        %meta = qw(
+            next_op    >
+            next_order ASC
+            prev_op    <=
+            diff       +1
+        );
+    } else {
+        %meta = qw(
+            next_op    <
+            next_order DESC
+            prev_op    >=
+            diff       -1
+        );
+    }
+
+    my $siblings = $self->Siblings;
+    $siblings->Limit( FIELD => 'SortOrder', OPERATOR => $meta{'next_op'}, VALUE => $self->SortOrder );
+    $siblings->OrderBy( FIELD => 'SortOrder', ORDER => $meta{'next_order'} );
+
+    my @next = ($siblings->Next, $siblings->Next);
+    unless ($next[0]) {
+        return $dir eq 'down'
+            ? (0, "Can not move down. It's already at the bottom")
+            : (0, "Can not move up. It's already at the top")
+        ;
+    }
+
+    my ($new_sort_order, $move);
+
+    unless ( $self->ObjectId ) {
+        # moving global, it can not share sort order, so just move it
+        # on place of next global and move everything in between one number
+
+        $new_sort_order = $next[0]->SortOrder;
+        $move = $self->Neighbors;
+        $move->Limit(
+            FIELD => 'SortOrder', OPERATOR => $meta{'next_op'}, VALUE => $self->SortOrder,
+        );
+        $move->Limit(
+            FIELD => 'SortOrder', OPERATOR => $meta{'prev_op'}, VALUE => $next[0]->SortOrder,
+            ENTRYAGGREGATOR => 'AND',
+        );
+    }
+    elsif ( $next[0]->ObjectId == $self->ObjectId ) {
+        # moving two locals, just swap them, they should follow 'so = so+/-1' rule
+        $new_sort_order = $next[0]->SortOrder;
+        $move = $next[0];
+    }
+    else {
+        # moving local behind global
+        unless ( $self->IsSortOrderShared ) {
+            # not shared SO allows us to swap
+            $new_sort_order = $next[0]->SortOrder;
+            $move = $next[0];
+        }
+        elsif ( $next[1] ) {
+            # more records there and shared SO, we have to move everything
+            $new_sort_order = $next[0]->SortOrder;
+            $move = $self->Neighbors;
+            $move->Limit(
+                FIELD => 'SortOrder', OPERATOR => $meta{prev_op}, VALUE => $next[0]->SortOrder,
+            );
+        }
+        else {
+            # shared SO and place after is free, so just jump
+            $new_sort_order = $next[0]->SortOrder + $meta{'diff'};
+        }
+    }
+
+    if ( $move ) {
+        foreach my $record ( $move->isa('RT::Record')? ($move) : @{ $move->ItemsArrayRef } ) {
+            my ($status, $msg) = $record->SetSortOrder(
+                $record->SortOrder - $meta{'diff'}
+            );
+            return (0, "Couldn't move: $msg") unless $status;
+        }
+    }
+
+    my ($status, $msg) = $self->SetSortOrder( $new_sort_order );
+    unless ( $status ) {
+        return (0, "Couldn't move: $msg");
+    }
+
+    return (1,"Moved");
+}
+
+sub NextSortOrder {
+    my $self = shift;
+    my %args = (@_);
+
+    my $oid = $args{'ObjectId'};
+    $oid = $self->ObjectId unless defined $oid;
+    $oid ||= 0;
+
+    my $neighbors = $self->Neighbors( %args );
+    if ( $oid ) {
+        $neighbors->LimitToObjectId( $oid );
+        $neighbors->LimitToObjectId( 0 );
+    } elsif ( !$neighbors->_isLimited ) {
+        $neighbors->UnLimit;
+    }
+    $neighbors->OrderBy( FIELD => 'SortOrder', ORDER => 'DESC' );
+    return 0 unless my $first = $neighbors->First;
+    return $first->SortOrder + 1;
+}
+
+sub IsSortOrderShared {
+    my $self = shift;
+    return 0 unless $self->ObjectId;
+
+    my $neighbors = $self->Neighbors;
+    $neighbors->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->id );
+    $neighbors->Limit( FIELD => 'SortOrder', VALUE => $self->SortOrder );
+    return $neighbors->Count;
+}
+
+sub TargetObj {
+    my $self = shift;
+    my $id   = shift;
+
+    my $method = $self->TargetField .'Obj';
+    return $self->$method( $id );
+}
+
+sub Neighbors {
+    my $self = shift;
+    return $self->CollectionClass->new( $self->CurrentUser );
+}
+
+sub Siblings {
+    my $self = shift;
+    my %args = @_;
+
+    my $oid = $args{'ObjectId'};
+    $oid = $self->ObjectId unless defined $oid;
+    $oid ||= 0;
+
+    my $res = $self->Neighbors( %args );
+    $res->LimitToObjectId( $oid );
+    $res->LimitToObjectId( 0 ) if $oid;
+    return $res;
+}
+
+RT::Base->_ImportOverlays();
+
+1;
diff --git a/lib/RT/SearchBuilder/ApplyAndSort.pm b/lib/RT/SearchBuilder/ApplyAndSort.pm
new file mode 100644
index 0000000..2b3d4b8
--- /dev/null
+++ b/lib/RT/SearchBuilder/ApplyAndSort.pm
@@ -0,0 +1,189 @@
+# 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 }}}
+
+use strict;
+use warnings;
+
+package RT::SearchBuilder::ApplyAndSort;
+use base 'RT::SearchBuilder';
+
+sub RecordClass {
+    my $class = ref($_[0]) || $_[0];
+    $class =~ s/s$// or return undef;
+    return $class;
+}
+
+sub _Init {
+    my $self = shift;
+
+    # By default, order by SortOrder
+    $self->OrderByCols(
+         { ALIAS => 'main',
+           FIELD => 'SortOrder',
+           ORDER => 'ASC' },
+         { ALIAS => 'main',
+           FIELD => 'id',
+           ORDER => 'ASC' },
+    );
+
+    return $self->SUPER::_Init(@_);
+}
+
+sub LimitToObjectId {
+    my $self = shift;
+    my $id = shift || 0;
+    $self->Limit( FIELD => 'ObjectId', VALUE => $id );
+}
+
+=head2 LimitTargetToNotApplied
+
+Takes either list of object ids or nothing. Limits collection
+to custom fields to listed objects or any corespondingly. Use
+zero to mean global.
+
+=cut
+
+sub LimitTargetToNotApplied {
+    my $self = shift;
+    my $collection = shift;
+    my @ids = @_;
+
+    my $alias = $self->JoinTargetToApplied($collection => @ids);
+
+    $collection->Limit(
+        ENTRYAGGREGATOR => 'AND',
+        ALIAS    => $alias,
+        FIELD    => 'id',
+        OPERATOR => 'IS',
+        VALUE    => 'NULL',
+    );
+    return $alias;
+}
+
+=head2 LimitTargetToApplied
+
+Limits collection to custom fields to listed objects or any corespondingly. Use
+zero to mean global.
+
+=cut
+
+sub LimitTargetToApplied {
+    my $self = shift;
+    my $collection = shift;
+    my @ids = @_;
+
+    my $alias = $self->JoinTargetToApplied($collection => @ids);
+
+    $collection->Limit(
+        ENTRYAGGREGATOR => 'AND',
+        ALIAS    => $alias,
+        FIELD    => 'id',
+        OPERATOR => 'IS NOT',
+        VALUE    => 'NULL',
+    );
+    return $alias;
+}
+
+sub JoinTargetToApplied {
+    my $self = shift;
+    my $collection = shift;
+    my @ids = @_;
+
+    my $alias = $self->JoinTargetToThis( $collection, New => 0, Left => 1 );
+    return $alias unless @ids;
+
+    # XXX: we need different EA in join clause, but DBIx::SB
+    # doesn't support them, use IN (X) instead
+    my $dbh = $self->_Handle->dbh;
+    $collection->Limit(
+        LEFTJOIN   => $alias,
+        ALIAS      => $alias,
+        FIELD      => 'ObjectId',
+        OPERATOR   => 'IN',
+        QUOTEVALUE => 0,
+        VALUE      => "(". join( ',', map $dbh->quote($_), @ids ) .")",
+    );
+
+    return $alias;
+}
+
+sub JoinTargetToThis {
+    my $self = shift;
+    my $collection = shift;
+    my %args = ( New => 0, Left => 0, @_ );
+
+    my $table = $self->Table;
+    my $key = "_sql_${table}_alias";
+
+    return $collection->{ $key } if $collection->{ $key } && !$args{'New'};
+
+    my $alias = $collection->Join(
+        $args{'Left'} ? (TYPE => 'LEFT') : (),
+        ALIAS1 => 'main',
+        FIELD1 => 'id',
+        TABLE2 => $table,
+        FIELD2 => $self->RecordClass->TargetField,
+    );
+    return $alias if $args{'New'};
+    return $collection->{ $key } = $alias;
+}
+
+=head2 NewItem
+
+Returns an empty new collection's item
+
+=cut
+
+sub NewItem {
+    my $self = shift;
+    return $self->RecordClass->new( $self->CurrentUser );
+}
+
+RT::Base->_ImportOverlays();
+
+1;

commit 92bb1cddc7a0c3bbba0ceb6cc7dd85e3909c1e29
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Dec 12 19:05:31 2011 +0400

    Rename ApplyAndSort to AddAndSort, as "Apply" already has meaning in RT
    
    While the name ApplyAndSort might seem more intuitive, the verb "to
    Apply" already has a meaning in RT, in the context of Scrips.  As such,
    rename the class and methods to the more generic Add forms.

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 43618c5..c110302 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2575,7 +2575,7 @@ Set(%AdminSearchResultFormat,
     CustomFields =>
         q{'<a href="__WebPath__/Admin/CustomFields/Modify.html?id=__id__">__id__</a>/TITLE:#'}
         .q{,'<a href="__WebPath__/Admin/CustomFields/Modify.html?id=__id__">__Name__</a>/TITLE:Name'}
-        .q{,__AppliedTo__, __FriendlyType__, __FriendlyPattern__},
+        .q{,__AddedTo__, __FriendlyType__, __FriendlyPattern__},
 
     Scrips =>
         q{'<a href="__WebPath__/Admin/Queues/Scrip.html?id=__id__&Queue=__QueueId__">__id__</a>/TITLE:#'}
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 1a0c17a..e3731e2 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -870,9 +870,10 @@ sub ACLEquivalenceObjects {
 
 =head2 ContextObject and SetContextObject
 
-Set or get a context for this object. It can be ticket, queue or another object
-this CF applies to. Used for ACL control, for example SeeCustomField can be granted on
-queue level to allow people to see all fields applied to the queue.
+Set or get a context for this object. It can be ticket, queue or another
+object this CF added to. Used for ACL control, for example
+SeeCustomField can be granted on queue level to allow people to see all
+fields added to the queue.
 
 =cut
 
@@ -927,12 +928,13 @@ sub LoadContextObject {
 
 =head2 ValidateContextObject
 
-Ensure that a given ContextObject applies to this Custom Field.
-For custom fields that are assigned to Queues or to Classes, this checks that the Custom
-Field is actually applied to that objects.  For Global Custom Fields, it returns true
-as long as the Object is of the right type, because you may be using
-your permissions on a given Queue of Class to see a Global CF.
-For CFs that are only applied Globally, you don't need a ContextObject.
+Ensure that a given ContextObject applies to this Custom Field.  For
+custom fields that are assigned to Queues or to Classes, this checks
+that the Custom Field is actually added to that object.  For Global
+Custom Fields, it returns true as long as the Object is of the right
+type, because you may be using your permissions on a given Queue of
+Class to see a Global CF.  For CFs that are only added globally, you
+don't need a ContextObject.
 
 =cut
 
@@ -940,20 +942,20 @@ sub ValidateContextObject {
     my $self = shift;
     my $object = shift;
 
-    return 1 if $self->IsApplied(0);
+    return 1 if $self->IsAdded(0);
 
     # global only custom fields don't have objects
     # that should be used as context objects.
-    return if $self->ApplyGlobally;
+    return if $self->IsOnlyGlobal;
 
     # Otherwise, make sure we weren't passed a user object that we're
     # supposed to treat as a queue.
     return unless $self->ValidContextType(ref $object);
 
-    # Check that it is applied correctly
-    my ($applied_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
-    return unless $applied_to;
-    return $self->IsApplied($applied_to->id);
+    # Check that it is added correctly
+    my ($added_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
+    return unless $added_to;
+    return $self->IsAdded($added_to->id);
 }
 
 
@@ -1236,62 +1238,61 @@ sub CollectionClassFromLookupType {
     return $collection_class;
 }
 
-=head1 ApplyGlobally
+=head1 IsOnlyGlobal
 
-Certain custom fields (users, groups) should only be applied globally
-but rather than regexing in code for LookupType =~ RT::Queue, we'll codify
-the rules here.
+Certain custom fields (users, groups) should only be added globally;
+codify that set here for reference.
 
 =cut
 
-sub ApplyGlobally {
+sub IsOnlyGlobal {
     my $self = shift;
 
     return ($self->LookupType =~ /^RT::(?:Group|User)/io);
 
 }
 
-=head1 AppliedTo
+=head1 AddedTo
 
-Returns collection with objects this custom field is applied to.
+Returns collection with objects this custom field is added to.
 Class of the collection depends on L</LookupType>.
-See all L</NotAppliedTo> .
+See all L</NotAddedTo> .
 
-Doesn't takes into account if object is applied globally.
+Doesn't takes into account if object is added globally.
 
 =cut
 
-sub AppliedTo {
+sub AddedTo {
     my $self = shift;
     return RT::ObjectCustomField->new( $self->CurrentUser )
-        ->AppliedTo( CustomField => $self );
+        ->AddedTo( CustomField => $self );
 }
 
-=head1 NotAppliedTo
+=head1 NotAddedTo
 
-Returns collection with objects this custom field is not applied to.
+Returns collection with objects this custom field is not added to.
 Class of the collection depends on L</LookupType>.
-See all L</AppliedTo> .
+See all L</AddedTo> .
 
-Doesn't takes into account if object is applied globally.
+Doesn't take into account if the object is added globally.
 
 =cut
 
-sub NotAppliedTo {
+sub NotAddedTo {
     my $self = shift;
     return RT::ObjectCustomField->new( $self->CurrentUser )
-        ->NotAppliedTo( CustomField => $self );
+        ->NotAddedTo( CustomField => $self );
 }
 
-=head2 IsApplied
+=head2 IsAdded
 
 Takes object id and returns corresponding L<RT::ObjectCustomField>
-record if this custom field is applied to the object. Use 0 to check
-if custom field is applied globally.
+record if this custom field is added to the object. Use 0 to check
+if custom field is added globally.
 
 =cut
 
-sub IsApplied {
+sub IsAdded {
     my $self = shift;
     my $id = shift;
     my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
@@ -1308,7 +1309,6 @@ Takes an object
 
 =cut
 
-
 sub AddToObject {
     my $self  = shift;
     my $object = shift;
@@ -1323,7 +1323,7 @@ sub AddToObject {
     }
 
     my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
-    my ( $oid, $msg ) = $ocf->Apply(
+    my ( $oid, $msg ) = $ocf->Add(
         CustomField => $self->id, ObjectId => $id,
     );
     return ( $oid, $msg );
@@ -1351,9 +1351,9 @@ sub RemoveFromObject {
         return ( 0, $self->loc('Permission Denied') );
     }
 
-    my $ocf = $self->IsApplied( $id );
+    my $ocf = $self->IsAdded( $id );
     unless ( $ocf ) {
-        return ( 0, $self->loc("This custom field does not apply to that object") );
+        return ( 0, $self->loc("This custom field cannot be added to that object") );
     }
 
     # XXX: Delete doesn't return anything
diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index bf15b16..cb333ef 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -145,7 +145,7 @@ sub LimitToParentType  {
 =head2 LimitToGlobalOrObjectId
 
 Takes list of object IDs and limits collection to custom
-fields that are applied to these objects or globally.
+fields that are added to these objects or globally.
 
 =cut
 
@@ -170,7 +170,7 @@ sub LimitToGlobalOrObjectId {
                  ENTRYAGGREGATOR => 'OR' ) unless $global_only;
 }
 
-=head2 LimitToNotApplied
+=head2 LimitToNotAdded
 
 Takes either list of object ids or nothing. Limits collection
 to custom fields to listed objects or any corespondingly. Use
@@ -178,23 +178,23 @@ zero to mean global.
 
 =cut
 
-sub LimitToNotApplied {
+sub LimitToNotAdded {
     my $self = shift;
     return RT::ObjectCustomFields->new( $self->CurrentUser )
-        ->LimitTargetToNotApplied( $self => @_ );
+        ->LimitTargetToNotAdded( $self => @_ );
 }
 
-=head2 LimitToApplied
+=head2 LimitToAdded
 
 Limits collection to custom fields to listed objects or any corespondingly. Use
 zero to mean global.
 
 =cut
 
-sub LimitToApplied {
+sub LimitToAdded {
     my $self = shift;
     return RT::ObjectCustomFields->new( $self->CurrentUser )
-        ->LimitTargetToApplied( $self => @_ );
+        ->LimitTargetToAdded( $self => @_ );
 }
 
 =head2 LimitToGlobalOrQueue QUEUEID
diff --git a/lib/RT/ObjectCustomField.pm b/lib/RT/ObjectCustomField.pm
index f4d2483..9ecf36e 100644
--- a/lib/RT/ObjectCustomField.pm
+++ b/lib/RT/ObjectCustomField.pm
@@ -49,7 +49,7 @@ use strict;
 use warnings;
 
 package RT::ObjectCustomField;
-use base 'RT::Record::ApplyAndSort';
+use base 'RT::Record::AddAndSort';
 
 use RT::CustomField;
 use RT::ObjectCustomFields;
diff --git a/lib/RT/ObjectCustomFields.pm b/lib/RT/ObjectCustomFields.pm
index c863dab..f39adac 100644
--- a/lib/RT/ObjectCustomFields.pm
+++ b/lib/RT/ObjectCustomFields.pm
@@ -49,7 +49,7 @@ use strict;
 use warnings;
 
 package RT::ObjectCustomFields;
-use base 'RT::SearchBuilder::ApplyAndSort';
+use base 'RT::SearchBuilder::AddAndSort';
 
 use RT::CustomField;
 use RT::ObjectCustomField;
diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/AddAndSort.pm
similarity index 91%
rename from lib/RT/Record/ApplyAndSort.pm
rename to lib/RT/Record/AddAndSort.pm
index 4f71c29..73b03ba 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/AddAndSort.pm
@@ -49,7 +49,7 @@
 use strict;
 use warnings;
 
-package RT::Record::ApplyAndSort;
+package RT::Record::AddAndSort;
 use base 'RT::Record';
 
 sub CollectionClass {
@@ -100,7 +100,7 @@ sub Create {
     );
 }
 
-sub Apply {
+sub Add {
     my $self = shift;
     my %args = (@_);
 
@@ -114,14 +114,14 @@ sub Apply {
     $oid = $oid->id if ref $oid;
     $oid ||= 0;
 
-    if ( $self->IsApplied( $tid => $oid ) ) {
-        return ( 0, $self->loc("Is already applied to the object") );
+    if ( $self->IsAdded( $tid => $oid ) ) {
+        return ( 0, $self->loc("Is already added to the object") );
     }
 
     if ( $oid ) {
-        # applying locally
-        return (0, $self->loc("Couldn't apply as it's global already") )
-            if $self->IsApplied( $tid => 0 );
+        # adding locally
+        return (0, $self->loc("Couldn't add as it's global already") )
+            if $self->IsAdded( $tid => 0 );
     }
     else {
         $self->DeleteAll( $field => $tid );
@@ -132,7 +132,7 @@ sub Apply {
     );
 }
 
-sub IsApplied {
+sub IsAdded {
     my $self = shift;
     my ($tid, $oid) = @_;
     my $record = $self->new( $self->CurrentUser );
@@ -140,19 +140,19 @@ sub IsApplied {
     return $record->id;
 }
 
-=head1 ApplyTo
+=head1 AddedTo
 
-Returns collection with objects this custom field is applied to.  Class of
-the collection depends on L</LookupType>.  See all L</NotAppliedTo> .
+Returns collection with objects this custom field is added to.  Class of
+the collection depends on L</LookupType>.  See all L</NotAddedTo> .
 
-Doesn't take into account if the object is applied globally.
+Doesn't take into account if the object is added globally.
 
 =cut
 
-sub AppliedTo {
+sub AddedTo {
     my $self = shift;
 
-    my ($res, $alias) = $self->_AppliedTo( @_ );
+    my ($res, $alias) = $self->_AddedTo( @_ );
     return $res unless $res;
 
     $res->Limit(
@@ -165,19 +165,19 @@ sub AppliedTo {
     return $res;
 }
 
-=head1 NotAppliedTo
+=head1 NotAddedTo
 
-Returns collection with objects this custom field is not applied to.
-Class of the collection depends on L</LookupType>.  See all L</AppliedTo>.
+Returns collection with objects this custom field is not added to.
+Class of the collection depends on L</LookupType>.  See all L</AddedTo>.
 
-Doesn't take into account if the object is applied globally.
+Doesn't take into account if the object is added globally.
 
 =cut
 
-sub NotAppliedTo {
+sub NotAddedTo {
     my $self = shift;
 
-    my ($res, $alias) = $self->_AppliedTo( @_ );
+    my ($res, $alias) = $self->_AddedTo( @_ );
     return $res unless $res;
 
     $res->Limit(
@@ -190,7 +190,7 @@ sub NotAppliedTo {
     return $res;
 }
 
-sub _AppliedTo {
+sub _AddedTo {
     my $self = shift;
     my %args = (@_);
 
@@ -202,7 +202,7 @@ sub _AppliedTo {
 
     my $res = $class->new( $self->CurrentUser );
 
-    # If target is applied to a Group, only display user-defined groups
+    # If target added to a Group, only display user-defined groups
     $res->LimitToUserDefinedGroups if $class eq 'RT::Groups';
 
     $res->OrderBy( FIELD => 'Name' );
diff --git a/lib/RT/SearchBuilder/ApplyAndSort.pm b/lib/RT/SearchBuilder/AddAndSort.pm
similarity index 93%
rename from lib/RT/SearchBuilder/ApplyAndSort.pm
rename to lib/RT/SearchBuilder/AddAndSort.pm
index 2b3d4b8..7397f4f 100644
--- a/lib/RT/SearchBuilder/ApplyAndSort.pm
+++ b/lib/RT/SearchBuilder/AddAndSort.pm
@@ -49,7 +49,7 @@
 use strict;
 use warnings;
 
-package RT::SearchBuilder::ApplyAndSort;
+package RT::SearchBuilder::AddAndSort;
 use base 'RT::SearchBuilder';
 
 sub RecordClass {
@@ -80,7 +80,7 @@ sub LimitToObjectId {
     $self->Limit( FIELD => 'ObjectId', VALUE => $id );
 }
 
-=head2 LimitTargetToNotApplied
+=head2 LimitTargetToNotAdded
 
 Takes either list of object ids or nothing. Limits collection
 to custom fields to listed objects or any corespondingly. Use
@@ -88,12 +88,12 @@ zero to mean global.
 
 =cut
 
-sub LimitTargetToNotApplied {
+sub LimitTargetToNotAdded {
     my $self = shift;
     my $collection = shift;
     my @ids = @_;
 
-    my $alias = $self->JoinTargetToApplied($collection => @ids);
+    my $alias = $self->JoinTargetToAdded($collection => @ids);
 
     $collection->Limit(
         ENTRYAGGREGATOR => 'AND',
@@ -105,19 +105,19 @@ sub LimitTargetToNotApplied {
     return $alias;
 }
 
-=head2 LimitTargetToApplied
+=head2 LimitTargetToAdded
 
 Limits collection to custom fields to listed objects or any corespondingly. Use
 zero to mean global.
 
 =cut
 
-sub LimitTargetToApplied {
+sub LimitTargetToAdded {
     my $self = shift;
     my $collection = shift;
     my @ids = @_;
 
-    my $alias = $self->JoinTargetToApplied($collection => @ids);
+    my $alias = $self->JoinTargetToAdded($collection => @ids);
 
     $collection->Limit(
         ENTRYAGGREGATOR => 'AND',
@@ -129,7 +129,7 @@ sub LimitTargetToApplied {
     return $alias;
 }
 
-sub JoinTargetToApplied {
+sub JoinTargetToAdded {
     my $self = shift;
     my $collection = shift;
     my @ids = @_;
diff --git a/share/html/Admin/CustomFields/Modify.html b/share/html/Admin/CustomFields/Modify.html
index 0110b7d..b7ba114 100644
--- a/share/html/Admin/CustomFields/Modify.html
+++ b/share/html/Admin/CustomFields/Modify.html
@@ -277,10 +277,10 @@ if ( $CustomFieldObj->id && $CustomFieldObj->LookupType =~ /^RT::(?:User|Group)$
     my ( $ret, $msg );
     my $object = $CustomFieldObj->RecordClassFromLookupType->new( $session{'CurrentUser'} );
 
-    if ( $CustomFieldObj->Disabled && $CustomFieldObj->IsApplied(0) ) {
+    if ( $CustomFieldObj->Disabled && $CustomFieldObj->IsAdded(0) ) {
         ( $ret, $msg ) = $CustomFieldObj->RemoveFromObject($object);
     }
-    elsif ( !$CustomFieldObj->Disabled && !$CustomFieldObj->IsApplied(0) ) {
+    elsif ( !$CustomFieldObj->Disabled && !$CustomFieldObj->IsAdded(0) ) {
         ( $ret, $msg ) = $CustomFieldObj->AddToObject($object);
     }
 
diff --git a/share/html/Admin/CustomFields/Objects.html b/share/html/Admin/CustomFields/Objects.html
index 91dc5dc..1c6bbd4 100644
--- a/share/html/Admin/CustomFields/Objects.html
+++ b/share/html/Admin/CustomFields/Objects.html
@@ -63,13 +63,13 @@
 <input type="checkbox" name="AddCustomField-<% $CF->id %>" value="0" />
 <&|/l&>check this box to apply this Custom Field to all objects.</&>
 
-% unless ( $CF->ApplyGlobally ) {
+% unless ( $CF->IsOnlyGlobal ) {
 <h2><&|/l&>Selected objects</&></h2>
 <& /Elements/CollectionList,
     OrderBy => 'id',
     Order => 'ASC',
     %ARGS,
-    Collection => $applied,
+    Collection => $added,
     Rows => 0,
     Page => 1,
     Format        => $format,
@@ -86,7 +86,7 @@
     OrderBy => 'id',
     Order => 'ASC',
     %ARGS,
-    Collection => $not_applied,
+    Collection => $not_added,
     Rows => 50,
     Format        => $format,
     DisplayFormat => "'__CheckBox.{AddCustomField-". $CF->id ."}__',". $format,
@@ -141,12 +141,12 @@ if ( $UpdateObjs ) {
     }
 }
 
-my $is_global = $CF->IsApplied(0);
+my $is_global = $CF->IsAdded(0);
 
-my $applied = $CF->AppliedTo;
-my $not_applied = $CF->NotAppliedTo;
+my $added = $CF->AddedTo;
+my $not_added = $CF->NotAddedTo;
 
-my $collection_class = ref($applied);
+my $collection_class = ref($added);
 $collection_class =~ s/^RT:://;
 
 my $format = RT->Config->Get('AdminSearchResultFormat')->{$collection_class}
diff --git a/share/html/Admin/Elements/EditCustomFields b/share/html/Admin/Elements/EditCustomFields
index d9d9134..1ddd743 100644
--- a/share/html/Admin/Elements/EditCustomFields
+++ b/share/html/Admin/Elements/EditCustomFields
@@ -55,7 +55,7 @@
 <h2><&|/l&>Selected Custom Fields</&></h2>
 <& /Elements/CollectionList,
     %ARGS,
-    Collection    => $applied_cfs,
+    Collection    => $added_cfs,
     Rows          => 0,
     Page          => 1,
     Format        => $format,
@@ -73,7 +73,7 @@
     OrderBy       => 'Name',
     Order         => 'ASC',
     %ARGS,
-    Collection    => $not_applied_cfs,
+    Collection    => $not_added_cfs,
     Rows          => 50,
     Format        => $format,
     DisplayFormat => "'__CheckBox.{AddCustomField}__',". $format,
@@ -152,15 +152,15 @@ if ( $UpdateCFs ) {
 
 $m->callback(CallbackName => 'UpdateExtraFields', Results => \@results, Object => $Object, %ARGS);
 
-my $applied_cfs = RT::CustomFields->new( $session{'CurrentUser'} );
-$applied_cfs->LimitToLookupType($lookup);
-$applied_cfs->LimitToGlobalOrObjectId($id);
-$applied_cfs->SetContextObject( $Object );
-$applied_cfs->ApplySortOrder;
+my $added_cfs = RT::CustomFields->new( $session{'CurrentUser'} );
+$added_cfs->LimitToLookupType($lookup);
+$added_cfs->LimitToGlobalOrObjectId($id);
+$added_cfs->SetContextObject( $Object );
+$added_cfs->ApplySortOrder;
 
-my $not_applied_cfs = RT::CustomFields->new( $session{'CurrentUser'} );
-$not_applied_cfs->LimitToLookupType($lookup);
-$not_applied_cfs->LimitToNotApplied( $id ? ($id, 0) : (0) );
+my $not_added_cfs = RT::CustomFields->new( $session{'CurrentUser'} );
+$not_added_cfs->LimitToLookupType($lookup);
+$not_added_cfs->LimitToNotAdded( $id ? ($id, 0) : (0) );
 
 my $format = RT->Config->Get('AdminSearchResultFormat')->{'CustomFields'};
 
diff --git a/share/html/Elements/RT__CustomField/ColumnMap b/share/html/Elements/RT__CustomField/ColumnMap
index b043984..644bbb4 100644
--- a/share/html/Elements/RT__CustomField/ColumnMap
+++ b/share/html/Elements/RT__CustomField/ColumnMap
@@ -86,14 +86,14 @@ my $COLUMN_MAP = {
             return !$v ? $_[0]->loc('unlimited') : $v == 0 ? $_[0]->loc('one') : $v;
         },
     },
-    AppliedTo => {
-        title     => 'Applied', # loc
+    AddedTo => {
+        title     => 'Added', # loc
 	value     => sub {
-            if ( $_[0]->IsApplied ) {
+            if ( $_[0]->IsAdded ) {
                 return $_[0]->loc('Global');
             }
 
-            my $collection = $_[0]->AppliedTo;
+            my $collection = $_[0]->AddedTo;
             return '' unless $collection;
 
             $collection->RowsPerPage(10);
@@ -127,7 +127,7 @@ my $COLUMN_MAP = {
         },
         value => sub {
             my $id = $_[0]->id;
-            return '' if $_[0]->IsApplied;
+            return '' if $_[0]->IsAdded;
 
             my $name = 'RemoveCustomField';
             my $arg = $DECODED_ARGS->{ $name };
@@ -148,7 +148,7 @@ my $COLUMN_MAP = {
             my $id = $_[0]->id;
             
             my $context = $_[2] || 0;
-            return '' unless $_[0]->IsApplied( $context );
+            return '' unless $_[0]->IsAdded( $context );
 
             my $name = 'MoveCustomField';
             my $args = $m->caller_args( 1 );
@@ -173,6 +173,8 @@ my $COLUMN_MAP = {
     },
 };
 
+$COLUMN_MAP->{'AppliedTo'} = $COLUMN_MAP->{'AddedTo'};
+
 </%ONCE>
 <%INIT>
 $m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 );

commit d5e6b6460349faec5cc3b61bff1cec166d176083
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sun Dec 18 02:36:15 2011 +0400

    Unify global and queue-specific scrip administration pages

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index c110302..e317304 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2578,13 +2578,8 @@ Set(%AdminSearchResultFormat,
         .q{,__AddedTo__, __FriendlyType__, __FriendlyPattern__},
 
     Scrips =>
-        q{'<a href="__WebPath__/Admin/Queues/Scrip.html?id=__id__&Queue=__QueueId__">__id__</a>/TITLE:#'}
-        .q{,'<a href="__WebPath__/Admin/Queues/Scrip.html?id=__id__&Queue=__QueueId__">__Description__</a>/TITLE:Description'}
-        .q{,__Stage__, __Condition__, __Action__, __Template__},
-
-    GlobalScrips =>
-        q{'<a href="__WebPath__/Admin/Global/Scrip.html?id=__id__">__id__</a>/TITLE:#'}
-        .q{,'<a href="__WebPath__/Admin/Global/Scrip.html?id=__id__">__Description__</a>/TITLE:Description'}
+        q{'<a href="__WebPath__/Admin/Scrips/Modify.html?id=__id__">__id__</a>/TITLE:#'}
+        .q{,'<a href="__WebPath__/Admin/Scrips/Modify.html?id=__id__">__Description__</a>/TITLE:Description'}
         .q{,__Stage__, __Condition__, __Action__, __Template__},
 
     Templates =>
diff --git a/share/html/Admin/Elements/EditScrip b/share/html/Admin/Elements/EditScrip
index e4c49ab..bdd9106 100644
--- a/share/html/Admin/Elements/EditScrip
+++ b/share/html/Admin/Elements/EditScrip
@@ -47,7 +47,7 @@
 %# END BPS TAGGED BLOCK }}}
 <& /Elements/ListActions, actions => \@actions &>
   
-<form method="post" action="Scrip.html" id="ModifyScrip" name="ModifyScrip">
+<form method="post" action="Modify.html" id="ModifyScrip" name="ModifyScrip">
 <input type="hidden" class="hidden" name="id" value="<% $id %>" />
 <input type="hidden" class="hidden" name="Queue" value="<% $Queue %>" />
 
diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index 2fdcae6..734177a 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -87,13 +87,12 @@ if ( $id ) {
     }
 }
 
+$Format ||= RT->Config->Get('AdminSearchResultFormat')->{'Scrips'};
 if ($QueueObj->id) {
     $Scrips->LimitToQueue($id);
-    $Format ||= RT->Config->Get('AdminSearchResultFormat')->{'Scrips'};
 }
 else {
     $Scrips->LimitToGlobal();
-    $Format ||= RT->Config->Get('AdminSearchResultFormat')->{'GlobalScrips'};
 }
 
 # deal with modifying and deleting existing scrips
diff --git a/share/html/Admin/Elements/ListGlobalScrips b/share/html/Admin/Elements/ListGlobalScrips
index 701ffbf..fdc94db 100644
--- a/share/html/Admin/Elements/ListGlobalScrips
+++ b/share/html/Admin/Elements/ListGlobalScrips
@@ -61,7 +61,7 @@
 % }
 
 <%init>
-my $Format = RT->Config->Get('AdminSearchResultFormat')->{'GlobalScrips'};
+my $Format = RT->Config->Get('AdminSearchResultFormat')->{'Scrips'};
 
 my $Scrips = RT::Scrips->new( $session{'CurrentUser'} );
 $Scrips->LimitToGlobal;
diff --git a/share/html/Admin/Queues/Scrip.html b/share/html/Admin/Queues/Scrip.html
deleted file mode 100644
index ac0a783..0000000
--- a/share/html/Admin/Queues/Scrip.html
+++ /dev/null
@@ -1,77 +0,0 @@
-%# 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 }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Elements/Tabs &>
-
-<& /Elements/ListActions, actions => \@results &>
-<& /Admin/Elements/EditScrip, title => $title,  %ARGS, id => $id &>
-
-<%init>
-my $QueueObj = RT::Queue->new( $session{'CurrentUser'} );
-$QueueObj->Load( $Queue );
-unless( $QueueObj->id ) {
-    Abort(loc("Queue [_1] not found", $id));
-}
-
-my ($title);
-
-($id, my @results) = $m->comp( '/Admin/Elements/EditScrip:Process', %ARGS );
-
-if ( $id ) {
-    $title = loc("Modify a scrip for queue [_1]", $QueueObj->Name);
-} else {
-    $title = loc("Create a scrip for queue [_1]", $QueueObj->Name);
-}
-
-
-</%init>
-
-<%ARGS>
-$id => undef
-$Queue => undef
-</%ARGS>
diff --git a/share/html/Admin/Global/Scrip.html b/share/html/Admin/Scrips/Modify.html
similarity index 100%
rename from share/html/Admin/Global/Scrip.html
rename to share/html/Admin/Scrips/Modify.html
diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index 015c2e3..f2cfdf2 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -109,7 +109,7 @@ my $build_admin_menu = sub {
         path        => '/Admin/Global/Scrips.html',
     );
     $scrips->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" );
-    $scrips->child( create => title => loc('Create'), path => "/Admin/Global/Scrip.html?Create=1" );
+    $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Modify.html?Create=1" );
 
     my $templates = $admin_global->child( templates =>
         title       => loc('Templates'),
@@ -284,7 +284,7 @@ my $build_admin_menu = sub {
 
             my $scrips = $queue->child( scrips => title => loc('Scrips'), path => "/Admin/Queues/Scrips.html?id=" . $id);
             $scrips->child( select => title => loc('Select'), path => "/Admin/Queues/Scrips.html?id=" . $id );
-            $scrips->child( create => title => loc('Create'), path => "/Admin/Queues/Scrip.html?Create=1;Queue=" . $id);
+            $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Modify.html?Create=1;Queue=" . $id);
 
             my $ticket_cfs = $queue->child( 'ticket-custom-fields' => title => loc('Ticket Custom Fields'),
                 path => '/Admin/Queues/CustomFields.html?SubType=RT::Ticket&id=' . $id );
@@ -352,15 +352,16 @@ my $build_admin_menu = sub {
         }
     }
 
-    if ( $request_path =~ m{^/Admin/Global/(Scrip|Template)s?\.html} ) {
-        my $type = $1;
+    if ( $request_path =~ m{^/Admin/Global/Scrips\.html} ) {
         my $tabs = PageMenu();
+        $tabs->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" );
+        $tabs->child( create => title => loc('Create'), path => "/Admin/Scrips/Modify.html?Create=1" );
+    }
 
-        # With only two elements, swapping between dropdown and menu is kinda dumb
-        # In the glorious future this should be cleaner.
-
-        $tabs->child( select => title => loc('Select'), path => "/Admin/Global/${type}s.html" );
-        $tabs->child( create => title => loc('Create'), path => "/Admin/Global/${type}.html?Create=1" );
+    if ( $request_path =~ m{^/Admin/Global/Templates?\.html} ) {
+        my $tabs = PageMenu();
+        $tabs->child( select => title => loc('Select'), path => "/Admin/Global/Templates.html" );
+        $tabs->child( create => title => loc('Create'), path => "/Admin/Global/Template.html?Create=1" );
     }
 
     if ( $request_path =~ m{^/Admin/Articles/Classes/} ) {
diff --git a/t/web/walk.t b/t/web/walk.t
index 97aa36e..2f72727 100644
--- a/t/web/walk.t
+++ b/t/web/walk.t
@@ -53,7 +53,7 @@ my @links = (
     '/Admin/Groups/Modify.html?id=' . $group->id,
     '/Admin/Queues/Modify.html?id=' . $queue->id,
     '/Admin/CustomFields/Modify.html?id=' . $cf->id,
-    '/Admin/Global/Scrip.html?id=1',
+    '/Admin/Scrips/Modify.html?id=1',
     '/Admin/Global/Template.html?Template=1',
     '/Admin/Articles/Classes/Modify.html?id=' . $class->id,
     '/Search/Build.html?Query=id<10',

commit aff115d3595c1cdf3c922f82865b590ae7a423db
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Dec 14 23:36:40 2011 +0400

    Use new AddAndSort interface to apply scrips to multiple queues
    
    This adds a new record type (and corresponding table and classes) to
    serve as the linkage between scrips and queues, leveraging the new
    AddAndSort record and collection classes.

diff --git a/etc/acl.Pg b/etc/acl.Pg
index 9da28db..461dbfd 100755
--- a/etc/acl.Pg
+++ b/etc/acl.Pg
@@ -23,6 +23,8 @@ sub acl {
         Transactions 
         scrips_id_seq
         Scrips 
+        objectscrips_id_seq
+        ObjectScrips
         acl_id_seq
         ACL 
         groupmembers_id_seq
diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index 4bcae6c..2b6bc1a 100755
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -142,7 +142,6 @@ CREATE TABLE Scrips (
 	CustomPrepareCode	CLOB,
 	CustomCommitCode	CLOB,
 	Stage		VARCHAR2(32),
-	Queue		NUMBER(11,0) DEFAULT 0 NOT NULL,
 	Template	NUMBER(11,0) DEFAULT 0 NOT NULL,
   	Creator 	NUMBER(11,0) DEFAULT 0 NOT NULL,
   	Created 	DATE,
@@ -150,6 +149,19 @@ CREATE TABLE Scrips (
   	LastUpdated 	DATE  
 );
 
+CREATE SEQUENCE OBJECTSCRIPS_seq;
+CREATE TABLE ObjectScrips (
+	id		NUMBER(11,0)
+                 CONSTRAINT ObjectScrips_Key PRIMARY KEY,
+        Scrip       NUMBER(11,0)  NOT NULL,
+        ObjectId              NUMBER(11,0)  NOT NULL,
+	SortOrder	NUMBER(11,0) DEFAULT 0 NOT NULL,
+	Creator		NUMBER(11,0) DEFAULT 0 NOT NULL,
+	Created		DATE,
+	LastUpdatedBy	NUMBER(11,0) DEFAULT 0 NOT NULL,
+	LastUpdated	DATE
+);
+CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
 
 CREATE SEQUENCE ACL_seq;
 CREATE TABLE ACL (
diff --git a/etc/schema.Pg b/etc/schema.Pg
index 565f76b..e909927 100755
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -231,7 +231,6 @@ CREATE TABLE Scrips (
   CustomPrepareCode text NULL  ,
   CustomCommitCode text NULL  ,
   Stage varchar(32) NULL  ,
-  Queue integer NOT NULL DEFAULT 0  ,
   Template integer NOT NULL DEFAULT 0  ,
   Creator integer NOT NULL DEFAULT 0  ,
   Created TIMESTAMP NULL  ,
@@ -242,7 +241,23 @@ CREATE TABLE Scrips (
 );
 
 
+CREATE SEQUENCE objectscrips_id_seq;
+
+CREATE TABLE ObjectScrips (
+  id INTEGER DEFAULT nextval('objectscrips_id_seq'),
+  Scrip integer NOT NULL,
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created TIMESTAMP NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated TIMESTAMP NULL  ,
+  PRIMARY KEY (id)
+
+);
 
+CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
 
 
 
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index 6897be2..e1f72bc 100755
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -151,7 +151,6 @@ CREATE TABLE Scrips (
   CustomPrepareCode text NULL  ,
   CustomCommitCode text NULL  ,
   Stage varchar(32) NULL  ,
-  Queue integer NULL DEFAULT 0 ,
   Template integer NULL DEFAULT 0 ,
   Creator integer NULL DEFAULT 0 ,
   Created DATETIME NULL  ,
@@ -162,6 +161,20 @@ CREATE TABLE Scrips (
 
 --- }}}
 
+CREATE TABLE ObjectScrips (
+  id INTEGER NOT NULL  ,
+  Scrip int NOT NULL  ,
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+);
+CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
+
 --- {{{ ACL
 CREATE TABLE ACL (
   id INTEGER PRIMARY KEY  ,
diff --git a/etc/schema.mysql b/etc/schema.mysql
index 9ed0337..b8e2045 100755
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -143,7 +143,6 @@ CREATE TABLE Scrips (
   CustomPrepareCode text NULL  ,
   CustomCommitCode text NULL  ,
   Stage varchar(32) CHARACTER SET ascii NULL  ,
-  Queue integer NOT NULL DEFAULT 0  ,
   Template integer NOT NULL DEFAULT 0  ,
   Creator integer NOT NULL DEFAULT 0  ,
   Created DATETIME NULL  ,
@@ -152,6 +151,20 @@ CREATE TABLE Scrips (
   PRIMARY KEY (id)
 ) ENGINE=InnoDB CHARACTER SET utf8;
 
+CREATE TABLE ObjectScrips (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Scrip integer NOT NULL  ,
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) ENGINE=InnoDB CHARACTER SET utf8;
+
+CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
 
 CREATE TABLE ACL (
   id INTEGER NOT NULL  AUTO_INCREMENT,
diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
new file mode 100644
index 0000000..d6855a6
--- /dev/null
+++ b/lib/RT/ObjectScrip.pm
@@ -0,0 +1,208 @@
+# 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 }}}
+
+use strict;
+use warnings;
+
+package RT::ObjectScrip;
+use base 'RT::Record::AddAndSort';
+
+use RT::Scrip;
+use RT::ObjectScrips;
+
+sub Table {'ObjectScrips'}
+sub ObjectCollectionClass {'RT::Queues'}
+
+=head2 ScripObj
+
+Returns the Scrip Object which has the id returned by Scrip
+
+=cut
+
+sub ScripObj {
+    my $self = shift;
+    my $id = shift || $self->Scrip;
+    my $obj = RT::Scrip->new( $self->CurrentUser );
+    $obj->Load( $id );
+    return $obj;
+}
+
+sub Neighbors {
+    my $self = shift;
+    my %args = @_;
+
+    my $res = $self->CollectionClass->new( $self->CurrentUser );
+    return $res;
+}
+
+=head2 id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=head2 Scrip
+
+Returns the current value of Scrip.
+(In the database, Scrip is stored as int(11).)
+
+
+
+=head2 SetScrip VALUE
+
+
+Set Scrip to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Scrip will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 ObjectId
+
+Returns the current value of ObjectId.
+(In the database, ObjectId is stored as int(11).)
+
+
+
+=head2 SetObjectId VALUE
+
+
+Set ObjectId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectId will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 SortOrder
+
+Returns the current value of SortOrder.
+(In the database, SortOrder is stored as int(11).)
+
+
+
+=head2 SetSortOrder VALUE
+
+
+Set SortOrder to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, SortOrder will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=head2 Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=head2 LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=head2 LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+
+sub _CoreAccessible {
+    {
+
+        id =>
+		{read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+        Scrip =>
+		{read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+        ObjectId =>
+		{read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+        SortOrder =>
+		{read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+        Creator =>
+		{read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+        Created =>
+		{read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+        LastUpdatedBy =>
+		{read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+        LastUpdated =>
+		{read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+
+ }
+};
+
+RT::Base->_ImportOverlays();
+
+1;
diff --git a/lib/RT/ObjectScrips.pm b/lib/RT/ObjectScrips.pm
new file mode 100644
index 0000000..0843219
--- /dev/null
+++ b/lib/RT/ObjectScrips.pm
@@ -0,0 +1,68 @@
+# 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 }}}
+
+use strict;
+use warnings;
+
+package RT::ObjectScrips;
+use base 'RT::SearchBuilder::AddAndSort';
+
+use RT::Scrips;
+use RT::ObjectScrip;
+
+sub Table { 'ObjectScrips'}
+
+sub LimitToScrip {
+    my $self = shift;
+    my $id = shift;
+    $self->Limit( FIELD => 'Scrip', VALUE => $id );
+}
+
+RT::Base->_ImportOverlays();
+
+1;
diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 40b030c..71b6728 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -73,6 +73,9 @@ use RT::Queue;
 use RT::Template;
 use RT::ScripCondition;
 use RT::ScripAction;
+use RT::Scrips;
+use RT::ObjectScrip;
+
 use base 'RT::Record';
 
 sub Table {'Scrips'}
@@ -170,7 +173,6 @@ sub Create {
         unless $condition->Id;
 
     my ( $id, $msg ) = $self->SUPER::Create(
-        Queue                  => $args{'Queue'},
         Template               => $template->Id,
         ScripCondition         => $condition->id,
         Stage                  => $args{'Stage'},
@@ -180,12 +182,15 @@ sub Create {
         CustomCommitCode       => $args{'CustomCommitCode'},
         CustomIsApplicableCode => $args{'CustomIsApplicableCode'},
     );
-    if ( $id ) {
-        return ( $id, $self->loc('Scrip Created') );
-    }
-    else {
-        return ( $id, $msg );
+    return ( $id, $msg ) unless $id;
+
+    unless ( $args{'Stage'} eq 'Disabled' ) {
+        my ($status, $msg) = RT::ObjectScrip->new( $self->CurrentUser )
+            ->Add( Scrip => $self, ObjectId => $args{'Queue'} );
+        $RT::Logger->error( "Couldn't add scrip: $msg" ) unless $status;
     }
+
+    return ( $id, $self->loc('Scrip Created') );
 }
 
 
@@ -203,29 +208,48 @@ sub Delete {
         return ( 0, $self->loc('Permission Denied') );
     }
 
+    RT::ObjectScrip->new( $self->CurrentUser )->DeleteAll( Scrip => $self );
+
     return ( $self->SUPER::Delete(@_) );
 }
 
+sub IsAdded {
+    my $self = shift;
+    my $record = RT::ObjectScrip->new( $self->CurrentUser );
+    $record->LoadByCols( Scrip => $self->id, ObjectId => shift || 0 );
+    return undef unless $record->id;
+    return $record;
+}
 
+sub AddedTo {
+    my $self = shift;
+    return RT::ObjectScrip->new( $self->CurrentUser )
+        ->AddedTo( Scrip => $self );
+}
 
-=head2 QueueObj
-
-Retuns an RT::Queue object with this Scrip's queue
-
-=cut
+sub NotAddedTo {
+    my $self = shift;
+    return RT::ObjectScrip->new( $self->CurrentUser )
+        ->NotAddedTo( Scrip => $self );
+}
 
-sub QueueObj {
+sub AddToObject {
     my $self = shift;
+    my $object = shift;
 
-    if ( !$self->{'QueueObj'} ) {
-        require RT::Queue;
-        $self->{'QueueObj'} = RT::Queue->new( $self->CurrentUser );
-        $self->{'QueueObj'}->Load( $self->__Value('Queue') );
-    }
-    return ( $self->{'QueueObj'} );
+    my $rec = RT::ObjectScrip->new( $self->CurrentUser );
+    return $rec->Add( Scrip => $self, ObjectId => $object );
 }
 
+sub RemoveFromObject {
+    my $self = shift;
+    my $object = shift;
 
+    my $rec = RT::ObjectScrip->new( $self->CurrentUser );
+    $rec->LoadByCols( Scrip => $self->id, ObjectId => $object );
+    return (0, $self->loc('Scrip is not added') ) unless $rec->id;
+    return $rec->Delete;
+}
 
 =head2 ActionObj
 
@@ -504,8 +528,7 @@ sub _Set {
     );
 
     unless ( $self->CurrentUserHasRight('ModifyScrips') ) {
-        $RT::Logger->debug(
-                 "CurrentUser can't modify Scrips for " . $self->Queue . "\n" );
+        $RT::Logger->debug( "CurrentUser can't modify Scrips" );
         return ( 0, $self->loc('Permission Denied') );
     }
 
@@ -550,9 +573,7 @@ sub _Value {
     my $self = shift;
 
     unless ( $self->CurrentUserHasRight('ShowScrips') ) {
-        $RT::Logger->debug( "CurrentUser can't modify Scrips for "
-                            . $self->__Value('Queue')
-                            . "\n" );
+        $RT::Logger->debug( "CurrentUser can't see scrip #". $self->__Value('id') );
         return (undef);
     }
 
@@ -592,18 +613,20 @@ sub HasRight {
                  Principal => undef,
                  @_ );
 
-    if ( $self->SUPER::_Value('Queue') ) {
-        return $args{'Principal'}->HasRight(
-            Right  => $args{'Right'},
-            Object => $self->QueueObj
-        );
-    }
-    else {
-        return $args{'Principal'}->HasRight(
-            Object => $RT::System,
+    my $queues = $self->AddedTo;
+    my $found = 0;
+    while ( my $queue = $queues->Next ) {
+        return 1 if $args{'Principal'}->HasRight(
             Right  => $args{'Right'},
+            Object => $queue,
         );
+        $found = 1;
     }
+    return $args{'Principal'}->HasRight(
+        Object => $RT::System,
+        Right  => $args{'Right'},
+    ) unless $found;
+    return 0;
 }
 
 
@@ -904,24 +927,6 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Queue
-
-Returns the current value of Queue.
-(In the database, Queue is stored as int(11).)
-
-
-
-=head2 SetQueue VALUE
-
-
-Set Queue to VALUE.
-Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Queue will be stored as a int(11).)
-
-
-=cut
-
-
 =head2 Template
 
 Returns the current value of Template.
@@ -1000,8 +1005,6 @@ sub _CoreAccessible {
 		{read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
         Stage =>
 		{read => 1, write => 1, sql_type => 12, length => 32,  is_blob => 0,  is_numeric => 0,  type => 'varchar(32)', default => ''},
-        Queue =>
-		{read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Template =>
 		{read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Creator =>
diff --git a/lib/RT/Scrips.pm b/lib/RT/Scrips.pm
index fa33f7e..bac7359 100644
--- a/lib/RT/Scrips.pm
+++ b/lib/RT/Scrips.pm
@@ -70,6 +70,7 @@ use strict;
 use warnings;
 
 use RT::Scrip;
+use RT::ObjectScrips;
 
 use base 'RT::SearchBuilder';
 
@@ -85,14 +86,17 @@ another call to this method
 =cut
 
 sub LimitToQueue  {
-   my $self = shift;
-  my $queue = shift;
- 
-  $self->Limit (ENTRYAGGREGATOR => 'OR',
-		FIELD => 'Queue',
-		VALUE => "$queue")
-      if defined $queue;
-  
+    my $self = shift;
+    my $queue = shift;
+    return unless defined $queue;
+
+    my $alias = RT::ObjectScrips->new( $self->CurrentUser )
+        ->JoinTargetToThis( $self );
+    $self->Limit(
+        ALIAS => $alias,
+        FIELD => 'ObjectId',
+        VALUE => int $queue,
+    );
 }
 
 
@@ -106,12 +110,20 @@ another call to this method or LimitToQueue
 
 
 sub LimitToGlobal  {
-   my $self = shift;
- 
-  $self->Limit (ENTRYAGGREGATOR => 'OR',
-		FIELD => 'Queue',
-		VALUE => 0);
-  
+    my $self = shift;
+    return $self->LimitToQueue(0);
+}
+
+sub LimitToAdded {
+    my $self = shift;
+    return RT::ObjectScrips->new( $self->CurrentUser )
+        ->LimitTargetToAdded( $self => @_ );
+}
+
+sub LimitToNotAdded {
+    my $self = shift;
+    return RT::ObjectScrips->new( $self->CurrentUser )
+        ->LimitTargetToNotAdded( $self => @_ );
 }
 
 # {{{ sub Next 
diff --git a/share/html/Admin/Elements/EditScrip b/share/html/Admin/Elements/EditScrip
index bdd9106..1613fab 100644
--- a/share/html/Admin/Elements/EditScrip
+++ b/share/html/Admin/Elements/EditScrip
@@ -190,7 +190,7 @@ else {
     return (undef, loc("Couldn't load scrip #[_1]", $id))
         unless $scrip->id;
 
-    my @attribs = qw(Queue ScripAction ScripCondition Template Stage
+    my @attribs = qw(ScripAction ScripCondition Template Stage
         Description CustomPrepareCode CustomCommitCode CustomIsApplicableCode);
     my @results = UpdateRecordObject(
         AttributesRef   => \@attribs,

commit 9f37573060d690c659cefbfa35521a1fb64f889e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 20 03:45:28 2012 +0400

    Update the validator to understand ObjectScrips

diff --git a/sbin/rt-validator.in b/sbin/rt-validator.in
index fce7b55..a06f53b 100644
--- a/sbin/rt-validator.in
+++ b/sbin/rt-validator.in
@@ -130,6 +130,7 @@ my @models = qw(
     ScripAction
     ScripCondition
     Scrip
+    ObjectScrip
     Template
     Ticket
     Transaction
@@ -152,6 +153,7 @@ $redo_on{'Delete'} = {
     Queues => [],
 
     Scrips => [],
+    ObjectScrips => [],
     ScripActions => [],
     ScripConditions => [],
     Templates => [],
@@ -781,9 +783,6 @@ push @CHECKS, Templates => sub {
 
 push @CHECKS, Scrips => sub {
     check_integrity(
-        'Scrips', 'Queue' => 'Queues', 'id',
-    );
-    check_integrity(
         'Scrips', 'ScripCondition' => 'ScripConditions', 'id',
     );
     check_integrity(
@@ -792,6 +791,12 @@ push @CHECKS, Scrips => sub {
     check_integrity(
         'Scrips', 'Template' => 'Templates', 'id',
     );
+    check_integrity(
+        'ObjectScrips', 'Scrip' => 'Scrips', 'id',
+    );
+    check_integrity(
+        'ObjectScrips', 'ObjectId' => 'Queues', 'id',
+    );
 };
 
 push @CHECKS, Attributes => sub {
@@ -1073,6 +1078,15 @@ while ( my $check = shift @do_check ) {
     }
 }
 
+=head2 check_integrity
+
+Takes two (table name, column(s)) pairs. First pair
+is reference we check and second is destination that
+must exist. Array reference can be used for multiple
+columns.
+
+=cut
+
 sub check_integrity {
     my ($stable, @scols) = (shift, shift);
     my ($ttable, @tcols) = (shift, shift);

commit 6c550b0460f08c4a8bbd142eda28c68c748a89c9
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Nov 2 15:24:54 2012 -0700

    Shred ObjectScrip records when shredding a Scrip
    
    Shredder tests now pass.

diff --git a/lib/RT/Shredder/Scrip.pm b/lib/RT/Shredder/Scrip.pm
index 8828e5b..0b4ce5f 100644
--- a/lib/RT/Shredder/Scrip.pm
+++ b/lib/RT/Shredder/Scrip.pm
@@ -57,4 +57,29 @@ use RT::Shredder::Constants;
 use RT::Shredder::Exceptions;
 use RT::Shredder::Dependencies;
 
+sub __DependsOn
+{
+    my $self = shift;
+    my %args = (
+            Shredder => undef,
+            Dependencies => undef,
+            @_,
+           );
+    my $deps = $args{'Dependencies'};
+    my $list = [];
+
+    my $objs = RT::ObjectScrips->new( $self->CurrentUser );
+    $objs->LimitToScrip( $self->Id );
+    push @$list, $objs;
+
+    $deps->_PushDependencies(
+        BaseObject    => $self,
+        Flags         => DEPENDS_ON,
+        TargetObjects => $list,
+        Shredder      => $args{'Shredder'}
+    );
+
+    return $self->SUPER::__DependsOn( %args );
+}
+
 1;

commit cc818bd4b9bf4fe3791f39e42226864f982c5723
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sun Nov 11 19:03:31 2012 -0800

    Sort scrips based on ObjectScrips' SortOrder, not description
    
    This allows differing order of execution of the same set of scrips from
    one queue to another.

diff --git a/lib/RT/Scrips.pm b/lib/RT/Scrips.pm
index bac7359..9b6d79f 100644
--- a/lib/RT/Scrips.pm
+++ b/lib/RT/Scrips.pm
@@ -126,6 +126,18 @@ sub LimitToNotAdded {
         ->LimitTargetToNotAdded( $self => @_ );
 }
 
+sub ApplySortOrder {
+    my $self = shift;
+    my $order = shift || 'ASC';
+    $self->OrderByCols( {
+        ALIAS => RT::ObjectScrips->new( $self->CurrentUser )
+            ->JoinTargetToThis( $self => @_ )
+        ,
+        FIELD => 'SortOrder',
+        ORDER => $order,
+    } );
+}
+
 # {{{ sub Next 
 
 =head2 Next
@@ -379,8 +391,7 @@ sub _FindScrips {
         ENTRYAGGREGATOR => 'OR',
     );
 
-    # Promise some kind of ordering
-    $self->OrderBy( FIELD => 'Description' );
+    $self->ApplySortOrder;
 
     # we call Count below, but later we always do search
     # so just do search and get count from results
diff --git a/t/api/scrip_order.t b/t/api/scrip_order.t
index 6fba7e5..caa6d95 100644
--- a/t/api/scrip_order.t
+++ b/t/api/scrip_order.t
@@ -2,52 +2,271 @@
 use strict;
 use warnings;
 
-use RT;
-use RT::Test tests => 7;
-
-
-
-my $scrip_queue = RT::Queue->new(RT->SystemUser);
-my ($queue_id, $msg) = $scrip_queue->Create( Name => "ScripOrdering-$$", 
-    Description => 'Test scrip ordering by description' );
-ok($queue_id, "Created scrip-ordering test queue? ".$msg);
-
-my $priority_ten_scrip = RT::Scrip->new(RT->SystemUser);
-(my $id, $msg) = $priority_ten_scrip->Create( 
-    Description => "10 set priority $$",
-    Queue => $queue_id, 
-    ScripCondition => 'On Create',
-    ScripAction => 'User Defined', 
-    CustomPrepareCode => '$RT::Logger->debug("Setting priority to 10..."); return 1;',
-    CustomCommitCode => '$self->TicketObj->SetPriority(10);',
-    Template => 'Blank',
-    Stage => 'TransactionCreate',
-);
-ok($id, "Created priority-10 scrip? ".$msg);
-
-my $priority_five_scrip = RT::Scrip->new(RT->SystemUser);
-($id, $msg) = $priority_ten_scrip->Create( 
-    Description => "05 set priority $$",
-    Queue => $queue_id, 
-    ScripCondition => 'On Create',
-    ScripAction => 'User Defined', 
-    CustomPrepareCode => '$RT::Logger->debug("Setting priority to 5..."); return 1;',
-    CustomCommitCode => '$self->TicketObj->SetPriority(5);', 
-    Template => 'Blank',
-    Stage => 'TransactionCreate',
-);
-ok($id, "Created priority-5 scrip? ".$msg);
-
-my $ticket = RT::Ticket->new(RT->SystemUser);
-($id, $msg) = $ticket->Create( 
-    Queue => $queue_id, 
-    Requestor => 'order at example.com',
-    Subject => "Scrip order test $$",
-);
-ok($ticket->id, "Created ticket? id=$id");
-
-isnt($ticket->Priority , 0, "Ticket shouldn't be priority 0");
-isnt($ticket->Priority , 5, "Ticket shouldn't be priority 5");
-is  ($ticket->Priority , 10, "Ticket should be priority 10");
+use RT::Test tests => 204;
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+ok $queue && $queue->id, 'loaded or created queue';
+
+note "check that execution order reflects sort order";
+{
+    my $ten = main->create_scrip_ok(
+        Description => "Set priority to 10",
+        Queue => $queue->id, 
+        CustomCommitCode => '$self->TicketObj->SetPriority(10);',
+    );
+
+    my $five = main->create_scrip_ok(
+        Description => "Set priority to 5",
+        Queue => $queue->id,
+        CustomCommitCode => '$self->TicketObj->SetPriority(5);', 
+    );
+
+    my $ticket = RT::Ticket->new(RT->SystemUser);
+    my ($id, $msg) = $ticket->Create( 
+        Queue => $queue->id, 
+        Subject => "Scrip order test $$",
+    );
+    ok($ticket->id, "Created ticket? id=$id");
+    is($ticket->Priority , 5, "By default newer scrip is last");
+
+    main->move_scrip_ok( $five, $queue->id, 'up' );
+
+    $ticket = RT::Ticket->new(RT->SystemUser);
+    ($id, $msg) = $ticket->Create(
+        Queue => $queue->id,
+        Subject => "Scrip order test $$",
+    );
+    ok($ticket->id, "Created ticket? id=$id");
+    is($ticket->Priority , 10, "Moved scrip and result is different");
+}
+
+my $queue_B = RT::Test->load_or_create_queue( Name => 'Other' );
+ok $queue_B && $queue_B->id, 'loaded or created queue';
+
+note "move around two local scrips";
+{
+    main->delete_all_scrips();
+
+    my @scrips;
+    push @scrips, main->create_scrip_ok( Queue => $queue->id );
+    push @scrips, main->create_scrip_ok( Queue => $queue->id );
+    main->check_scrips_order(\@scrips, [$queue]);
+
+    main->move_scrip_ok( $scrips[0], $queue->id, 'down' );
+    @scrips = @scrips[1, 0];
+    main->check_scrips_order(\@scrips, [$queue]);
+
+    main->move_scrip_ok( $scrips[0], $queue->id, 'down' );
+    @scrips = @scrips[1, 0];
+    main->check_scrips_order(\@scrips, [$queue]);
+
+    main->move_scrip_ok( $scrips[1], $queue->id, 'up' );
+    @scrips = @scrips[1, 0];
+    main->check_scrips_order(\@scrips, [$queue]);
+
+    main->move_scrip_ok( $scrips[1], $queue->id, 'up' );
+    @scrips = @scrips[1, 0];
+    main->check_scrips_order(\@scrips, [$queue]);
+}
+
+note "move around two global scrips";
+{
+    main->delete_all_scrips();
+
+    my @scrips;
+    push @scrips, main->create_scrip_ok( Queue => 0 );
+    push @scrips, main->create_scrip_ok( Queue => 0 );
+    main->check_scrips_order(\@scrips, [$queue]);
+
+    main->move_scrip_ok( $scrips[0], 0, 'down' );
+    @scrips = @scrips[1, 0];
+    main->check_scrips_order(\@scrips, [$queue]);
+
+    main->move_scrip_ok( $scrips[0], 0, 'down' );
+    @scrips = @scrips[1, 0];
+    main->check_scrips_order(\@scrips, [$queue]);
+
+    main->move_scrip_ok( $scrips[1], 0, 'up' );
+    @scrips = @scrips[1, 0];
+    main->check_scrips_order(\@scrips, [$queue]);
+
+    main->move_scrip_ok( $scrips[1], 0, 'up' );
+    @scrips = @scrips[1, 0];
+    main->check_scrips_order(\@scrips, [$queue]);
+}
+
+note "move local scrip below global";
+{
+    main->delete_all_scrips();
+    my @scrips;
+    push @scrips, main->create_scrip_ok( Queue => $queue->id );
+    push @scrips, main->create_scrip_ok( Queue => $queue_B->id );
+    push @scrips, main->create_scrip_ok( Queue => 0 );
+    push @scrips, main->create_scrip_ok( Queue => $queue->id );
+    main->check_scrips_order(\@scrips, [$queue, $queue_B]);
+
+    main->move_scrip_ok( $scrips[0], $queue->id, 'down' );
+    @scrips = @scrips[1, 2, 0, 3];
+    main->check_scrips_order(\@scrips, [$queue, $queue_B]);
+}
+
+note "move local scrip above global";
+{
+    main->delete_all_scrips();
+    my @scrips;
+    push @scrips, main->create_scrip_ok( Queue => $queue_B->id );
+    push @scrips, main->create_scrip_ok( Queue => 0 );
+    push @scrips, main->create_scrip_ok( Queue => $queue->id );
+    push @scrips, main->create_scrip_ok( Queue => $queue_B->id );
+    main->check_scrips_order(\@scrips, [$queue, $queue_B]);
+
+    main->move_scrip_ok( $scrips[-1], $queue_B->id, 'up' );
+    @scrips = @scrips[0, 3, 1, 2];
+    main->check_scrips_order(\@scrips, [$queue, $queue_B]);
+}
+
+note "move global scrip down with local in between";
+{
+    main->delete_all_scrips();
+    my @scrips;
+    push @scrips, main->create_scrip_ok( Queue => 0 );
+    push @scrips, main->create_scrip_ok( Queue => $queue_B->id );
+    push @scrips, main->create_scrip_ok( Queue => $queue->id );
+    push @scrips, main->create_scrip_ok( Queue => 0 );
+    push @scrips, main->create_scrip_ok( Queue => $queue->id );
+    main->check_scrips_order(\@scrips, [$queue, $queue_B]);
+
+    main->move_scrip_ok( $scrips[0], 0, 'down' );
+    @scrips = @scrips[1, 2, 3, 0, 4];
+    main->check_scrips_order(\@scrips, [$queue, $queue_B]);
+}
+
+note "move global scrip up with local in between";
+{
+    main->delete_all_scrips();
+    my @scrips;
+    push @scrips, main->create_scrip_ok( Queue => $queue->id );
+    push @scrips, main->create_scrip_ok( Queue => 0 );
+    push @scrips, main->create_scrip_ok( Queue => $queue_B->id );
+    push @scrips, main->create_scrip_ok( Queue => $queue->id );
+    push @scrips, main->create_scrip_ok( Queue => 0 );
+    main->check_scrips_order(\@scrips, [$queue, $queue_B]);
+
+    main->move_scrip_ok( $scrips[-1], 0, 'up' );
+    @scrips = @scrips[0, 4, 1, 2, 3];
+    main->check_scrips_order(\@scrips, [$queue, $queue_B]);
+}
+
+note "delete scrips one by one";
+{
+    main->delete_all_scrips();
+    my @scrips;
+    push @scrips, main->create_scrip_ok( Queue => $queue->id );
+    push @scrips, main->create_scrip_ok( Queue => $queue_B->id );
+    push @scrips, main->create_scrip_ok( Queue => 0 );
+    push @scrips, main->create_scrip_ok( Queue => $queue_B->id );
+    push @scrips, main->create_scrip_ok( Queue => $queue->id );
+    push @scrips, main->create_scrip_ok( Queue => 0 );
+    main->check_scrips_order(\@scrips, [$queue, $queue_B]);
+
+    foreach my $idx (3, 2, 0 ) {
+        $_->Delete foreach splice @scrips, $idx, 1;
+        main->check_scrips_order(\@scrips, [$queue, $queue_B]);
+    }
+}
+
+sub create_scrip_ok {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    my $self = shift;
+    my %args = (
+        ScripCondition => 'On Create',
+        ScripAction => 'User Defined', 
+        CustomPrepareCode => 'return 1',
+        CustomCommitCode => 'return 1', 
+        Template => 'Blank',
+        Stage => 'TransactionCreate',
+        @_
+    );
+
+    my $scrip = RT::Scrip->new( RT->SystemUser );
+    my ($id, $msg) = $scrip->Create( %args );
+    ok($id, "Created scrip") or diag "error: $msg";
+
+    return $scrip;
+}
+
+sub delete_all_scrips {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    my $self = shift;
+    my $scrips = RT::Scrips->new( RT->SystemUser );
+    $scrips->UnLimit;
+    $_->Delete foreach @{ $scrips->ItemsArrayRef };
+}
+
+sub move_scrip_ok {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    my $self = shift;
+    my ($scrip, $queue, $dir) = @_;
+
+    my $rec = RT::ObjectScrip->new( RT->SystemUser );
+    $rec->LoadByCols( Scrip => $scrip->id, ObjectId => $queue );
+    ok $rec->id, 'found application of the scrip';
+
+    my $method = 'Move'. ucfirst lc $dir;
+    my ($status, $msg) = $rec->$method();
+    ok $status, "moved scrip $dir" or diag "error: $msg";
+}
+
+sub check_scrips_order {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    my $self = shift;
+    my $scrips = shift;
+    my $queues = shift;
+
+    foreach my $qid ( 0, map $_->id, @$queues ) {
+        my $list = RT::Scrips->new( RT->SystemUser );
+        $list->LimitToGlobal;
+        $list->LimitToQueue( $qid ) if $qid;
+        $list->ApplySortOrder;
+        is_deeply(
+            [map $_->id, @{ $list->ItemsArrayRef } ],
+            [map $_->id, grep $_->IsAdded( $qid ) || $_->IsAdded( 0 ), @$scrips],
+            'list of scrips match expected'
+        )
+    }
+
+    foreach my $qid ( map $_->id, @$queues ) {
+        my $list = RT::ObjectScrips->new( RT->SystemUser );
+        $list->LimitToObjectId( 0 );
+        $list->LimitToObjectId( $qid );
+
+        my %so;
+        $so{ $_->SortOrder }++ foreach @{ $list->ItemsArrayRef };
+        ok( !grep( {$_ != 1} values %so), 'no dublicate order' );
+    }
+    {
+        my $list = RT::ObjectScrips->new( RT->SystemUser );
+        $list->UnLimit;
+        $list->OrderBy( FIELD => 'SortOrder', ORDER => 'ASC' );
+
+        my $prev;
+        foreach my $rec ( @{ $list->ItemsArrayRef } ) {
+            my $so = $rec->SortOrder;
+            do { $prev = $so; next } unless defined $prev;
+
+            ok $so == $prev || $so == $prev+1, "sequential order";
+            $prev = $so;
+        }
+    }
+}
+
+sub dump_sort_order {
+
+    diag " id oid so";
+    diag join "\n", map { join "\t", @$_ } map @$_, $RT::Handle->dbh->selectall_arrayref(
+        "select Scrip, ObjectId, SortOrder from ObjectScrips ORDER BY SortOrder"
+    );
+
+}
 
 
diff --git a/t/approval/admincc.t b/t/approval/admincc.t
index 3062ce5..55c2c9f 100644
--- a/t/approval/admincc.t
+++ b/t/approval/admincc.t
@@ -84,7 +84,12 @@ mail_ok {
         Requestor => 'minion',
         Queue     => $q->Id,
     );
-} { from => qr/RT System/,
+} { from => qr/PO via RT/,
+    to => 'minion at company.com',
+    subject => qr/PO for stationary/,
+    body => qr/automatically generated in response/
+},
+{ from => qr/RT System/,
     bcc => qr/ceo.*coo|coo.*ceo/i,
     subject => qr/PO for stationary/i,
 },
@@ -92,12 +97,8 @@ mail_ok {
     to => 'cto at company.com',
     subject => qr/New Pending Approval: CTO Approval/,
     body => qr/pending your approval.*Your approval is requested.*Blah/s
-},
-{ from => qr/PO via RT/,
-    to => 'minion at company.com',
-    subject => qr/PO for stationary/,
-    body => qr/automatically generated in response/
-};
+}
+;
 
 ok ($tid,$tmsg);
 
diff --git a/t/approval/basic.t b/t/approval/basic.t
index 92ffc42..931556f 100644
--- a/t/approval/basic.t
+++ b/t/approval/basic.t
@@ -86,14 +86,14 @@ mail_ok {
         $t->Create(Subject => "PO for stationary",
                    Owner => "root", Requestor => 'minion',
                    Queue => $q->Id);
-} { from => qr/RT System/,
-    to => 'cfo at company.com',
-    subject => qr/New Pending Approval: CFO Approval/,
-    body => qr/pending your approval.*Your approval is requested.*Blah/s
-},{ from => qr/PO via RT/,
+} { from => qr/PO via RT/,
     to => 'minion at company.com',
     subject => qr/PO for stationary/,
     body => qr/automatically generated in response/
+}, { from => qr/RT System/,
+    to => 'cfo at company.com',
+    subject => qr/New Pending Approval: CFO Approval/,
+    body => qr/pending your approval.*Your approval is requested.*Blah/s
 };
 
 ok ($tid,$tmsg);

commit 7d2937a39c58fa20de798520d32d3c6b50cd78ff
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 24 00:57:58 2012 +0400

    sort scrips in initialdata by description
    
    this keeps new and upgraded installations on
    the same page

diff --git a/etc/initialdata b/etc/initialdata
index 2c63555..96867e0 100755
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -511,14 +511,30 @@ Hour:         { $SubscriptionObj->SubValue('Hour') }
 );
 
 @Scrips = (
+    {  Description    => 'On Comment Notify AdminCcs as Comment',
+       ScripCondition => 'On Comment',
+       ScripAction    => 'Notify AdminCcs As Comment',
+       Template       => 'Admin Comment' },
+    {  Description    => 'On Comment Notify Other Recipients as Comment',
+       ScripCondition => 'On Comment',
+       ScripAction    => 'Notify Other Recipients As Comment',
+       Template       => 'Correspondence' },
+    {  Description    => 'On Correspond Notify AdminCcs',
+       ScripCondition => 'On Correspond',
+       ScripAction    => 'Notify AdminCcs',
+       Template       => 'Admin Correspondence' },
+    {  Description    => 'On Correspond Notify Other Recipients',
+       ScripCondition => 'On Correspond',
+       ScripAction    => 'Notify Other Recipients',
+       Template       => 'Correspondence' },
+    {  Description    => 'On Correspond Notify Requestors and Ccs',
+       ScripCondition => 'On Correspond',
+       ScripAction    => 'Notify Requestors And Ccs',
+       Template       => 'Correspondence' },
     {  Description    => 'On Correspond Open Tickets',
        ScripCondition => 'On Correspond',
        ScripAction    => 'Open Tickets',
        Template       => 'Blank' },
-    {  Description    => 'On Owner Change Notify Owner',
-       ScripCondition => 'On Owner Change',
-       ScripAction    => 'Notify Owner',
-       Template       => 'Transaction' },
     {  Description    => 'On Create Autoreply To Requestors',
        ScripCondition => 'On Create',
        ScripAction    => 'AutoReply To Requestors',
@@ -527,26 +543,10 @@ Hour:         { $SubscriptionObj->SubValue('Hour') }
        ScripCondition => 'On Create',
        ScripAction    => 'Notify AdminCcs',
        Template       => 'Transaction' },
-    {  Description    => 'On Correspond Notify AdminCcs',
-       ScripCondition => 'On Correspond',
-       ScripAction    => 'Notify AdminCcs',
-       Template       => 'Admin Correspondence' },
-    {  Description    => 'On Correspond Notify Requestors and Ccs',
-       ScripCondition => 'On Correspond',
-       ScripAction    => 'Notify Requestors And Ccs',
-       Template       => 'Correspondence' },
-    {  Description    => 'On Correspond Notify Other Recipients',
-       ScripCondition => 'On Correspond',
-       ScripAction    => 'Notify Other Recipients',
-       Template       => 'Correspondence' },
-    {  Description    => 'On Comment Notify AdminCcs as Comment',
-       ScripCondition => 'On Comment',
-       ScripAction    => 'Notify AdminCcs As Comment',
-       Template       => 'Admin Comment' },
-    {  Description    => 'On Comment Notify Other Recipients as Comment',
-       ScripCondition => 'On Comment',
-       ScripAction    => 'Notify Other Recipients As Comment',
-       Template       => 'Correspondence' },
+    {  Description    => 'On Owner Change Notify Owner',
+       ScripCondition => 'On Owner Change',
+       ScripAction    => 'Notify Owner',
+       Template       => 'Transaction' },
     {  Description    => 'On Resolve Notify Requestors',
        ScripCondition => 'On Resolve',
        ScripAction    => 'Notify Requestors',

commit 75c0e00096fe78a160e51d4eaa8b165ceba273b7
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 02:24:59 2011 +0400

    We don't need to separate out the list of global scrips
    
    Global scrips are already intermixed with queue-specific scrips in one
    list for sorting purposes.

diff --git a/share/html/Admin/Elements/ListGlobalScrips b/share/html/Admin/Elements/ListGlobalScrips
deleted file mode 100644
index fdc94db..0000000
--- a/share/html/Admin/Elements/ListGlobalScrips
+++ /dev/null
@@ -1,68 +0,0 @@
-%# 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 }}}
-% unless ( $Scrips->Count ) {
-<p><i><&|/l&>(No scrips)</&></i></p>
-% } else {
-
-<& /Elements/CollectionList,
-    OrderBy => 'Description',
-    Order => 'ASC',
-    Rows  => 0,
-    %ARGS,
-    Format => $Format,
-    Collection => $Scrips,
-&>
-
-% }
-
-<%init>
-my $Format = RT->Config->Get('AdminSearchResultFormat')->{'Scrips'};
-
-my $Scrips = RT::Scrips->new( $session{'CurrentUser'} );
-$Scrips->LimitToGlobal;
-</%INIT>
diff --git a/share/html/Admin/Queues/Scrips.html b/share/html/Admin/Queues/Scrips.html
index 56e4466..c24ba11 100644
--- a/share/html/Admin/Queues/Scrips.html
+++ b/share/html/Admin/Queues/Scrips.html
@@ -47,12 +47,6 @@
 %# END BPS TAGGED BLOCK }}}
 <& /Admin/Elements/Header, Title => $title &>
 <& /Elements/Tabs &>
-
-% unless ( $QueueObj->Disabled ) { # Global scrips does not apply to disabled queues
-<h2><&|/l&>Scrips which apply to all queues</&></h2>
-<& /Admin/Elements/ListGlobalScrips &>
-<br />
-% }
 <& /Admin/Elements/EditScrips, title => $title, %ARGS &>
 <%init>
 my $QueueObj = RT::Queue->new($session{'CurrentUser'});

commit 68c6a126319881b342d89c94fdc6f8210109b34c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sun Dec 18 02:38:10 2011 +0400

    Allow scrips to be reordered and deleted in the web UI

diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index 734177a..39df72d 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -50,69 +50,144 @@
 <form action="Scrips.html" method="post">
 <input type="hidden" class="hidden" name="id" value="<% $id %>" />
 
-<h2><&|/l&>Current Scrips</&></h2>
+<h2><&|/l&>On create scrips</&></h2>
+% my $scrips = $find_scrips->(Stage => 'TransactionCreate');
+<& /Elements/CollectionList, %common_applied_args, Collection => $scrips &>
+% unless ( $scrips->Count ) {
+<p><i><&|/l&>(No scrips)</&></i></p>
+% }
+
+<h2><&|/l&>Batch scrips</&></h2>
+% $scrips = $find_scrips->(Stage => 'TransactionBatch');
+<& /Elements/CollectionList, %common_applied_args, Collection => $scrips &>
+% unless ( $scrips->Count ) {
+<p><i><&|/l&>(No scrips)</&></i></p>
+% }
+
+<& /Elements/Submit,
+    Name => 'RemoveScrips',
+    Caption => loc("Un-apply selected scrips"),
+    Label => loc("Update"),
+&>
+
+<h2><&|/l&>Not applied scrips</&></h2>
+% $scrips = $find_scrips->(Added => 0);
 <& /Elements/CollectionList,
-    OrderBy => 'Description',
-    Order => 'ASC',
-    Rows  => 100,
+    Rows => 50,
+    Page => 1,
     %ARGS,
-    Format => $Format,
-    DisplayFormat => "__CheckBox.{DeleteScrip}__, $Format",
-    Collection => $Scrips,
+    Collection => $scrips,
+    Format     => $Format,
+    DisplayFormat => "__CheckBox.{AddScrip}__, $Format",
     AllowSorting => 1,
-    PassArguments => [ qw(Query Format Rows Page Order OrderBy id) ],
+    PassArguments => [ qw(Format Rows Page Order OrderBy id) ],
 &>
-
-% if ( $Scrips->Count ) {
-<p><i><&|/l&>(Check box to delete)</&></i></p>
-% } else  {
+% unless ( $scrips->Count ) {
 <p><i><&|/l&>(No scrips)</&></i></p>
 % }
+
 <& /Elements/Submit,
-    Caption => loc("Delete selected scrips"),
-    Label => loc("Delete")
+    Name => 'AddScrips',
+    Caption => loc("Apply selected scrips"),
+    Label => loc("Update"),
 &>
+
 </form>
 
 <%init>
 my (@actions);
 
-my $Scrips = RT::Scrips->new($session{'CurrentUser'});
-
-my $QueueObj = RT::Queue->new($session{'CurrentUser'});
 if ( $id ) {
+    my $QueueObj = RT::Queue->new($session{'CurrentUser'});
     $QueueObj->Load( $id );
-    unless ( $QueueObj->id ) {
-        push @actions, loc("Couldn't load queue #[_1]", $id)
-    }
+    Abort(loc("Couldn't load queue #[_1]", $id)) unless $QueueObj->id;
 }
+$id ||= 0;
+
+my $find_scrips = sub {
+    my %args = (Added => 1, @_);
+    my $scrips = RT::Scrips->new($session{'CurrentUser'});
+    $scrips->Limit( FIELD => 'Stage', VALUE => $args{'Stage'} )
+        if $args{'Stage'};
+    my $method = $args{'Added'}? 'LimitToAdded' : 'LimitToNotAdded';
+    $scrips->$method(0, $id);
+    $scrips->ApplySortOrder if $args{'Added'};
+    return $scrips;
+};
 
 $Format ||= RT->Config->Get('AdminSearchResultFormat')->{'Scrips'};
-if ($QueueObj->id) {
-    $Scrips->LimitToQueue($id);
+my $DisplayFormat = $Format;
+if ( $id ) {
+    $DisplayFormat = "__RemoveCheckBox__, $DisplayFormat";
+} else {
+    $DisplayFormat = "__CheckBox.{RemoveScrip}__, $DisplayFormat";
 }
-else {
-    $Scrips->LimitToGlobal();
+$DisplayFormat .= ", __Move.{$id}__";
+
+my %common_applied_args = (
+    %ARGS,
+    Format => $Format,
+    DisplayFormat => $DisplayFormat,
+    Rows => 0,
+    Page => 1,
+    AllowSorting => 0,
+    PassArguments => [ qw(Format id) ],
+);
+
+if ( $RemoveScrips ) {
+    foreach my $sid ( @RemoveScrip ) {
+        my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
+        $scrip->Load( $sid );
+        next unless $scrip->id;
+
+        my ($status, $msg) = $scrip->RemoveFromObject( $id );
+        push @actions, $msg;
+    }
+}
+
+if ( $AddScrips ) {
+    foreach my $sid ( @AddScrip ) {
+        my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
+        $scrip->Load( $sid );
+        next unless $scrip->id;
+
+        my ($status, $msg) = $scrip->AddToObject( $id );
+        push @actions, $msg;
+    }
 }
 
-# deal with modifying and deleting existing scrips
-# we still support DeleteScrip-id format but array is preferred
-foreach my $id ( grep $_, @DeleteScrip, map /^DeleteScrip-(\d+)/, keys %ARGS ) {
-    my $scrip = RT::Scrip->new($session{'CurrentUser'});
-    $scrip->Load( $id );
-    my ($retval, $msg) = $scrip->Delete;
-    if ($retval) {
-        push @actions, loc("Scrip deleted");
+if ( $MoveScripUp ) {
+    my $scrip = RT::ObjectScrip->new( $session{'CurrentUser'} );
+    $scrip->LoadByCols( Scrip => $MoveScripUp, ObjectId => $id );
+    if ( $scrip->id ) {
+        my ($status, $msg) = $scrip->MoveUp;
+        push @actions, $msg;
     }
-    else {
+}
+
+if ( $MoveScripDown ) {
+    my $scrip = RT::ObjectScrip->new( $session{'CurrentUser'} );
+    $scrip->LoadByCols( Scrip => $MoveScripDown, ObjectId => $id );
+    if ( $scrip->id ) {
+        my ($status, $msg) = $scrip->MoveDown;
         push @actions, $msg;
     }
 }
+
 </%init>
 
 <%ARGS>
 $id => undef
 $title => undef
 $Format => undef
- at DeleteScrip => ()
+
+ at RemoveScrip => ()
+$RemoveScrips => undef
+
+ at AddScrip => ()
+$AddScrips => undef
+
+$MoveScripUp => undef
+$MoveScripDown => undef
+
 </%ARGS>
diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index 776a2d8..86c0ec6 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -98,6 +98,60 @@ my $COLUMN_MAP = {
 	attribute => 'Stage',
 	value     => sub { return $_[0]->Stage() },
     },
+    RemoveCheckBox => {
+        title => sub {
+            my $name = 'RemoveScrip';
+            my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': '';
+
+            return \qq{<input type="checkbox" name="${name}All" value="1" $checked
+                              onclick="setCheckbox(this.form, '$name', this.checked)" />};
+        },
+        value => sub {
+            my $id = $_[0]->id;
+            return '' if $_[0]->IsAdded;
+
+            my $name = 'RemoveScrip';
+            my $arg = $m->request_args->{ $name };
+
+            my $checked = '';
+            if ( $arg && ref $arg ) {
+                $checked = 'checked="checked"' if grep $_ == $id, @$arg;
+            }
+            elsif ( $arg ) {
+                $checked = 'checked="checked"' if $arg == $id;
+            }
+            return \qq{<input type="checkbox" name="$name" value="$id" $checked />}
+        },
+    },
+    Move => {
+        title => 'Move',
+        value => sub {
+            my $id = $_[0]->id;
+
+            my $context = $_[2] || 0;
+            return '' unless $_[0]->IsAdded( $context );
+
+            my $name = 'MoveScrip';
+            my $args = $m->caller_args( 1 );
+            my @pass = ref $args->{'PassArguments'}
+                ? @{$args->{'PassArguments'}}
+                : ($args->{'PassArguments'});
+            my %pass = map { $_ => $args->{$_} } grep exists $args->{$_}, @pass;
+
+            my $uri = RT->Config->Get('WebPath') . $m->request_path;
+
+            my @res = (
+                \'<a href="',
+                $uri .'?'. $m->comp("/Elements/QueryString", %pass, MoveScripUp => $id ),
+                \'">', loc('[Up]'), \'</a>',
+                \' <a href="',
+                $uri .'?'. $m->comp("/Elements/QueryString", %pass, MoveScripDown => $id ),
+                \'">', loc('[Down]'), \'</a>'
+            );
+
+            return @res;
+        },
+    },
 };
 
 </%ONCE>

commit 7accd962f0c4e57a4aeb12f02d0f4ef0b2c5bab5
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 18:17:13 2011 +0400

    Pass all arguments through for tables with additional columns
    
    This allows subclasses to contain additional properties than those
    specified by AddAndSort.

diff --git a/lib/RT/Record/AddAndSort.pm b/lib/RT/Record/AddAndSort.pm
index 73b03ba..b02ab49 100644
--- a/lib/RT/Record/AddAndSort.pm
+++ b/lib/RT/Record/AddAndSort.pm
@@ -89,8 +89,8 @@ sub Create {
 
     unless ( defined $args{'SortOrder'} ) {
         $args{'SortOrder'} = $self->NextSortOrder(
+            %args,
             $tfield  => $target,
-            ObjectId => $args{'ObjectId'},
         );
     }
 
@@ -128,7 +128,7 @@ sub Add {
     }
 
     return $self->Create(
-        $field => $tid, ObjectId => $oid,
+        %args, $field => $tid, ObjectId => $oid,
     );
 }
 

commit 715367f8c14e16d26604bd1e3bdfc5e38fd413bd
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 22 16:06:28 2011 +0400

    Move Stage to being a property of ObjectScrips, not Scrips
    
    The Stage of the scrip is now declared to be part of the association
    between the scrip and the queue, and not a property of the scrip itself.
    This, in conjunction with the ability to sort scrip execution order on
    a per-queue basis, allows for much greater reuse of a single scrip in
    multiple contexts.
    
    As 'Disabled' was previously a stage, but should remain a property of
    the scrip, the Scrip table gains a Disabled flag.  While this makes it
    more consistent with other records' Disabled bits, it does necessitate
    additional calls to ->FindAllRows in some call sites to keep the
    previous functionality.

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index e317304..79770ea 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2580,7 +2580,7 @@ Set(%AdminSearchResultFormat,
     Scrips =>
         q{'<a href="__WebPath__/Admin/Scrips/Modify.html?id=__id__">__id__</a>/TITLE:#'}
         .q{,'<a href="__WebPath__/Admin/Scrips/Modify.html?id=__id__">__Description__</a>/TITLE:Description'}
-        .q{,__Stage__, __Condition__, __Action__, __Template__},
+        .q{,__Condition__, __Action__, __Template__},
 
     Templates =>
         q{'<a href="__WebPath__/__WebRequestPathDir__/Template.html?Queue=__QueueId__&Template=__id__">__id__</a>/TITLE:#'}
diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index 2b6bc1a..d99fee0 100755
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -141,7 +141,7 @@ CREATE TABLE Scrips (
 	CustomIsApplicableCode	CLOB,
 	CustomPrepareCode	CLOB,
 	CustomCommitCode	CLOB,
-	Stage		VARCHAR2(32),
+	Disabled 	NUMBER(11,0) DEFAULT 0 NOT NULL,
 	Template	NUMBER(11,0) DEFAULT 0 NOT NULL,
   	Creator 	NUMBER(11,0) DEFAULT 0 NOT NULL,
   	Created 	DATE,
@@ -154,6 +154,7 @@ CREATE TABLE ObjectScrips (
 	id		NUMBER(11,0)
                  CONSTRAINT ObjectScrips_Key PRIMARY KEY,
         Scrip       NUMBER(11,0)  NOT NULL,
+	Stage		VARCHAR2(32) DEFAULT 'TransactionCreate' NOT NULL,
         ObjectId              NUMBER(11,0)  NOT NULL,
 	SortOrder	NUMBER(11,0) DEFAULT 0 NOT NULL,
 	Creator		NUMBER(11,0) DEFAULT 0 NOT NULL,
diff --git a/etc/schema.Pg b/etc/schema.Pg
index e909927..5dc5bcc 100755
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -230,7 +230,7 @@ CREATE TABLE Scrips (
   CustomIsApplicableCode text NULL  ,
   CustomPrepareCode text NULL  ,
   CustomCommitCode text NULL  ,
-  Stage varchar(32) NULL  ,
+  Disabled integer NOT NULL DEFAULT 0 ,
   Template integer NOT NULL DEFAULT 0  ,
   Creator integer NOT NULL DEFAULT 0  ,
   Created TIMESTAMP NULL  ,
@@ -246,6 +246,7 @@ CREATE SEQUENCE objectscrips_id_seq;
 CREATE TABLE ObjectScrips (
   id INTEGER DEFAULT nextval('objectscrips_id_seq'),
   Scrip integer NOT NULL,
+  Stage varchar(32) NOT NULL DEFAULT 'TransactionCreate' ,
   ObjectId integer NOT NULL,
   SortOrder integer NOT NULL DEFAULT 0  ,
 
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index e1f72bc..f06abb6 100755
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -150,7 +150,7 @@ CREATE TABLE Scrips (
   CustomIsApplicableCode text NULL  ,
   CustomPrepareCode text NULL  ,
   CustomCommitCode text NULL  ,
-  Stage varchar(32) NULL  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,
   Template integer NULL DEFAULT 0 ,
   Creator integer NULL DEFAULT 0 ,
   Created DATETIME NULL  ,
@@ -164,6 +164,7 @@ CREATE TABLE Scrips (
 CREATE TABLE ObjectScrips (
   id INTEGER NOT NULL  ,
   Scrip int NOT NULL  ,
+  Stage varchar(32) NOT NULL DEFAULT 'TransactionCreate' ,
   ObjectId integer NOT NULL,
   SortOrder integer NOT NULL DEFAULT 0  ,
 
diff --git a/etc/schema.mysql b/etc/schema.mysql
index b8e2045..6113334 100755
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -142,7 +142,7 @@ CREATE TABLE Scrips (
   CustomIsApplicableCode text NULL  ,
   CustomPrepareCode text NULL  ,
   CustomCommitCode text NULL  ,
-  Stage varchar(32) CHARACTER SET ascii NULL  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,
   Template integer NOT NULL DEFAULT 0  ,
   Creator integer NOT NULL DEFAULT 0  ,
   Created DATETIME NULL  ,
@@ -154,6 +154,7 @@ CREATE TABLE Scrips (
 CREATE TABLE ObjectScrips (
   id INTEGER NOT NULL  AUTO_INCREMENT,
   Scrip integer NOT NULL  ,
+  Stage varchar(32) CHARACTER SET ascii NOT NULL DEFAULT 'TransactionCreate',
   ObjectId integer NOT NULL,
   SortOrder integer NOT NULL DEFAULT 0  ,
 
diff --git a/lib/RT.pm b/lib/RT.pm
index 7e3da22..5c37d72 100644
--- a/lib/RT.pm
+++ b/lib/RT.pm
@@ -464,7 +464,6 @@ sub InitClasses {
     if ( $args{'Heavy'} ) {
         # load scrips' modules
         my $scrips = RT::Scrips->new(RT->SystemUser);
-        $scrips->Limit( FIELD => 'Stage', OPERATOR => '!=', VALUE => 'Disabled' );
         while ( my $scrip = $scrips->Next ) {
             local $@;
             eval { $scrip->LoadModules } or
diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index d6855a6..11757c4 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -77,6 +77,7 @@ sub Neighbors {
     my %args = @_;
 
     my $res = $self->CollectionClass->new( $self->CurrentUser );
+    $res->Limit( FIELD => 'Stage', VALUE => $args{'Stage'} || $self->Stage );
     return $res;
 }
 
@@ -103,9 +104,16 @@ Set Scrip to VALUE.
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 (In the database, Scrip will be stored as a int(11).)
 
+=head2 Stage
 
-=cut
+Returns the current value of Stage.
+(In the database, Stage is stored as varchar(32).)
+
+=head2 SetStage VALUE
 
+Set Stage to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Stage will be stored as a varchar(32).)
 
 =head2 ObjectId
 
@@ -187,6 +195,8 @@ sub _CoreAccessible {
 		{read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         Scrip =>
 		{read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+        Stage =>
+		{read => 1, write => 1, sql_type => 12, length => 32,  is_blob => 0,  is_numeric => 0,  type => 'varchar(32)', default => 'TransactionCreate'},
         ObjectId =>
 		{read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         SortOrder =>
diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 71b6728..766b77a 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -172,11 +172,18 @@ sub Create {
     return ( 0, $self->loc( "Condition '[_1]' not found", $args{'ScripCondition'} ) )
         unless $condition->Id;
 
+    if ( $args{'Stage'} eq 'Disabled' ) {
+        $RT::Logger->warning("Disabled Stage is deprecated");
+        $args{'Stage'} = 'TransactionCreate';
+        $args{'Disabled'} = 1;
+    }
+    $args{'Disabled'} ||= 0;
+
     my ( $id, $msg ) = $self->SUPER::Create(
         Template               => $template->Id,
         ScripCondition         => $condition->id,
-        Stage                  => $args{'Stage'},
         ScripAction            => $action->Id,
+        Disabled               => $args{'Disabled'},
         Description            => $args{'Description'},
         CustomPrepareCode      => $args{'CustomPrepareCode'},
         CustomCommitCode       => $args{'CustomCommitCode'},
@@ -184,11 +191,12 @@ sub Create {
     );
     return ( $id, $msg ) unless $id;
 
-    unless ( $args{'Stage'} eq 'Disabled' ) {
-        my ($status, $msg) = RT::ObjectScrip->new( $self->CurrentUser )
-            ->Add( Scrip => $self, ObjectId => $args{'Queue'} );
-        $RT::Logger->error( "Couldn't add scrip: $msg" ) unless $status;
-    }
+    (my $status, $msg) = RT::ObjectScrip->new( $self->CurrentUser )->Add(
+        Scrip    => $self,
+        Stage    => $args{'Stage'},
+        ObjectId => $args{'Queue'},
+    );
+    $RT::Logger->error( "Couldn't add scrip: $msg" ) unless $status;
 
     return ( $id, $self->loc('Scrip Created') );
 }
@@ -320,7 +328,27 @@ sub TemplateObj {
     return ( $self->{'TemplateObj'} );
 }
 
+=head2 Stage
 
+Takes TicketObj named argument and returns scrip's stage when
+added to ticket's queue.
+
+=cut
+
+sub Stage {
+    my $self = shift;
+    my %args = ( TicketObj => undef, @_ );
+
+    my $queue = $args{'TicketObj'}->Queue;
+    my $rec = RT::ObjectScrip->new( $self->CurrentUser );
+    $rec->LoadByCols( Scrip => $self->id, ObjectId => $queue );
+    return $rec->Stage if $rec->id;
+
+    $rec->LoadByCols( Scrip => $self->id, ObjectId => 0 );
+    return $rec->Stage if $rec->id;
+
+    return undef;
+}
 
 
 =head2 Apply { TicketObj => undef, TransactionObj => undef}
@@ -407,16 +435,24 @@ sub IsApplicable {
 
 	my @Transactions;
 
-        if ( $self->Stage eq 'TransactionCreate') {
+        my $stage = $self->Stage( TicketObj => $args{'TicketObj'} );
+        unless ( $stage ) {
+	    $RT::Logger->error(
+                "Scrip #". $self->id ." is not applied to"
+                ." queue #". $args{'TicketObj'}->Queue
+            );
+	    return (undef);
+        }
+        elsif ( $stage eq 'TransactionCreate') {
 	    # Only look at our current Transaction
 	    @Transactions = ( $args{'TransactionObj'} );
         }
-        elsif ( $self->Stage eq 'TransactionBatch') {
+        elsif ( $stage eq 'TransactionBatch') {
 	    # Look at all Transactions in this Batch
             @Transactions = @{ $args{'TicketObj'}->TransactionBatch || [] };
         }
 	else {
-	    $RT::Logger->error( "Unknown Scrip stage:" . $self->Stage );
+	    $RT::Logger->error( "Unknown Scrip stage: '$stage'" );
 	    return (undef);
 	}
 	my $ConditionObj = $self->ConditionObj;
@@ -909,19 +945,19 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Stage
+=head2 Disabled
 
-Returns the current value of Stage.
-(In the database, Stage is stored as varchar(32).)
+Returns the current value of Disabled.
+(In the database, Disabled is stored as smallint(6).)
 
 
 
-=head2 SetStage VALUE
+=head2 SetDisabled VALUE
 
 
-Set Stage to VALUE.
+Set Disabled to VALUE.
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Stage will be stored as a varchar(32).)
+(In the database, Disabled will be stored as a smallint(6).)
 
 
 =cut
@@ -1003,8 +1039,8 @@ sub _CoreAccessible {
 		{read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
         CustomCommitCode =>
 		{read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
-        Stage =>
-		{read => 1, write => 1, sql_type => 12, length => 32,  is_blob => 0,  is_numeric => 0,  type => 'varchar(32)', default => ''},
+        Disabled =>
+                {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
         Template =>
 		{read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Creator =>
diff --git a/lib/RT/Scrips.pm b/lib/RT/Scrips.pm
index 9b6d79f..f314fcc 100644
--- a/lib/RT/Scrips.pm
+++ b/lib/RT/Scrips.pm
@@ -76,6 +76,13 @@ use base 'RT::SearchBuilder';
 
 sub Table { 'Scrips'}
 
+sub _Init {
+    my $self = shift;
+
+    $self->{'with_disabled_column'} = 1;
+
+    return ( $self->SUPER::_Init(@_) );
+}
 
 =head2 LimitToQueue
 
@@ -126,6 +133,20 @@ sub LimitToNotAdded {
         ->LimitTargetToNotAdded( $self => @_ );
 }
 
+sub LimitByStage  {
+    my $self = shift;
+    my %args = @_%2? (Stage => @_) : @_;
+    return unless defined $args{'Stage'};
+
+    my $alias = RT::ObjectScrips->new( $self->CurrentUser )
+        ->JoinTargetToThis( $self, %args );
+    $self->Limit(
+        ALIAS => $alias,
+        FIELD => 'Stage',
+        VALUE => $args{'Stage'},
+    );
+}
+
 sub ApplySortOrder {
     my $self = shift;
     my $order = shift || 'ASC';
@@ -354,12 +375,9 @@ sub _FindScrips {
                  @_ );
 
 
-    $self->LimitToQueue( $self->{'TicketObj'}->QueueObj->Id )
-      ;    #Limit it to  $Ticket->QueueObj->Id
-    $self->LimitToGlobal();
-      # or to "global"
-
-    $self->Limit( FIELD => "Stage", VALUE => $args{'Stage'} );
+    $self->LimitToQueue( $self->{'TicketObj'}->QueueObj->Id );
+    $self->LimitToGlobal;
+    $self->LimitByStage( $args{'Stage'} );
 
     my $ConditionsAlias = $self->NewAlias('ScripConditions');
 
diff --git a/sbin/rt-email-group-admin.in b/sbin/rt-email-group-admin.in
index 5589bf7..7f20834 100644
--- a/sbin/rt-email-group-admin.in
+++ b/sbin/rt-email-group-admin.in
@@ -373,6 +373,7 @@ sub delete {
     require RT::Scrips;
     my $scrips = RT::Scrips->new( RT->SystemUser );
     $scrips->Limit( FIELD => 'ScripAction', VALUE => $action->id );
+    $scrips->FindAllRows;
     if ( $scrips->Count ) {
         my @sid;
         while( my $s = $scrips->Next ) {
diff --git a/share/html/Admin/Elements/EditScrip b/share/html/Admin/Elements/EditScrip
index 1613fab..9392cfb 100644
--- a/share/html/Admin/Elements/EditScrip
+++ b/share/html/Admin/Elements/EditScrip
@@ -79,11 +79,13 @@
     Queue => $Queue,
 &></td></tr>
 
+% if ( $id eq 'new' ) {
 <tr><td class="label"><&|/l&>Stage</&>:</td><td class="value">\
 <& /Admin/Elements/SelectStage,
     Name => "Scrip-$id-Stage",
-    Default => $ARGS{"Scrip-$id-Stage"} || $scrip->Stage,
+    Default => $ARGS{"Scrip-$id-Stage"},
 &></td></tr>
+% }
 
 </table>
 </&>
diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index 39df72d..665d6af 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -107,11 +107,12 @@ $id ||= 0;
 my $find_scrips = sub {
     my %args = (Added => 1, @_);
     my $scrips = RT::Scrips->new($session{'CurrentUser'});
-    $scrips->Limit( FIELD => 'Stage', VALUE => $args{'Stage'} )
+    $scrips->LimitByStage( $args{'Stage'} )
         if $args{'Stage'};
     my $method = $args{'Added'}? 'LimitToAdded' : 'LimitToNotAdded';
     $scrips->$method(0, $id);
     $scrips->ApplySortOrder if $args{'Added'};
+    $scrips->FindAllRows;
     return $scrips;
 };
 
diff --git a/share/html/Admin/Elements/SelectStage b/share/html/Admin/Elements/SelectStage
index 369dee9..dc2746b 100644
--- a/share/html/Admin/Elements/SelectStage
+++ b/share/html/Admin/Elements/SelectStage
@@ -66,8 +66,6 @@ my @stages = 'TransactionCreate';
 push @stages, RT->Config->Get('UseTransactionBatch')
             ? 'TransactionBatch'
             : ['TransactionBatch', 'TransactionBatch (DISABLED)'];
-
-push @stages, 'Disabled';
 </%INIT>
 <%ARGS>
 $Default => 'TransactionCreate'
diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index 86c0ec6..ec9f7a2 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -93,11 +93,6 @@ my $COLUMN_MAP = {
 	attribute => 'Description',
 	value     => sub { return $_[0]->Description() },
     },
-    Stage => {
-        title     => 'Stage', # loc
-	attribute => 'Stage',
-	value     => sub { return $_[0]->Stage() },
-    },
     RemoveCheckBox => {
         title => sub {
             my $name = 'RemoveScrip';
diff --git a/t/ticket/scrips_batch.t b/t/ticket/scrips_batch.t
index 44d7f8e..a6d22b5 100644
--- a/t/ticket/scrips_batch.t
+++ b/t/ticket/scrips_batch.t
@@ -37,7 +37,12 @@ my $sid;
     is value_name($form, "Scrip-$sid-ScripCondition"), 'On Transaction', 'correct condition';
     is value_name($form, "Scrip-$sid-ScripAction"), 'User Defined', 'correct action';
     is value_name($form, "Scrip-$sid-Template"), 'Global template: Blank', 'correct template';
-    is value_name($form, "Scrip-$sid-Stage"), 'TransactionBatch', 'correct stage';
+
+    {
+        my $rec = RT::ObjectScrip->new( RT->SystemUser );
+        $rec->LoadByCols( Scrip => $sid, ObjectId => $queue->id );
+        is $rec->Stage, 'TransactionBatch', "correct stage";
+    }
 
     my $tmp_fn = File::Spec->catfile( RT::Test->temp_directory, 'transactions' );
     open my $tmp_fh, '+>', $tmp_fn or die $!;

commit 31384b0181bc7c7c19bce39727e95b65a4e9ad18
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 4 00:25:50 2012 +0400

    Disabled column map for scrips

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 79770ea..5c5bcee 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2580,7 +2580,7 @@ Set(%AdminSearchResultFormat,
     Scrips =>
         q{'<a href="__WebPath__/Admin/Scrips/Modify.html?id=__id__">__id__</a>/TITLE:#'}
         .q{,'<a href="__WebPath__/Admin/Scrips/Modify.html?id=__id__">__Description__</a>/TITLE:Description'}
-        .q{,__Condition__, __Action__, __Template__},
+        .q{,__Condition__, __Action__, __Template__, __Disabled__},
 
     Templates =>
         q{'<a href="__WebPath__/__WebRequestPathDir__/Template.html?Queue=__QueueId__&Template=__id__">__id__</a>/TITLE:#'}
diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index ec9f7a2..b2bbdd9 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -93,6 +93,11 @@ my $COLUMN_MAP = {
 	attribute => 'Description',
 	value     => sub { return $_[0]->Description() },
     },
+    Disabled => {
+        title     => \' ',
+        attribute => 'Disabled',
+        value     => sub { return $_[0]->Disabled? $_[0]->loc('Disabled'): $_[0]->loc('Enabled') },
+    },
     RemoveCheckBox => {
         title => sub {
             my $name = 'RemoveScrip';

commit fc54dc2555d24adff2807c32f2406cc61006811b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 02:30:35 2011 +0400

    shrink number of lines of code

diff --git a/share/html/Admin/Queues/Scrips.html b/share/html/Admin/Queues/Scrips.html
index c24ba11..42b2c64 100644
--- a/share/html/Admin/Queues/Scrips.html
+++ b/share/html/Admin/Queues/Scrips.html
@@ -51,14 +51,9 @@
 <%init>
 my $QueueObj = RT::Queue->new($session{'CurrentUser'});
 $QueueObj->Load($id);
+Abort(loc("Queue [_1] not found",$id)) unless $QueueObj->id;
 
-my $title;
-
-if ($QueueObj->id) {
-    $title = loc("Modify scrips for queue [_1]", $QueueObj->Name);
-} else {
-    Abort(loc("Queue [_1] not found",$id));
-}
+my $title = loc("Modify scrips for queue [_1]", $QueueObj->Name);
 </%init>
 
 <%ARGS>

commit 546ef47e5d61d89a3b5c6953080152454cfe2cc9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 18:18:21 2011 +0400

    Add missing closing tag (</select>)
    
    This tag has been missing since the element was first added.

diff --git a/share/html/Admin/Elements/SelectStage b/share/html/Admin/Elements/SelectStage
index dc2746b..7bbbf3b 100644
--- a/share/html/Admin/Elements/SelectStage
+++ b/share/html/Admin/Elements/SelectStage
@@ -57,6 +57,7 @@
 </option>
 
 % }
+</select>
 <%INIT>
 if ( !defined $Default || $Default eq '') {
     $Default = 'TransactionCreate';

commit 591f2c5b891b367f23b963a5b7401da6c62bf84e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 18:19:42 2011 +0400

    process stage when we apply scrips to queues
    
    now stage is property of a link between queue and scrip

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 766b77a..2ffa2f7 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -243,10 +243,10 @@ sub NotAddedTo {
 
 sub AddToObject {
     my $self = shift;
-    my $object = shift;
+    my %args = @_%2? (ObjectId => @_) : (@_);
 
     my $rec = RT::ObjectScrip->new( $self->CurrentUser );
-    return $rec->Add( Scrip => $self, ObjectId => $object );
+    return $rec->Add( %args, Scrip => $self );
 }
 
 sub RemoveFromObject {
diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index 665d6af..a5d0b84 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -86,6 +86,8 @@
 <p><i><&|/l&>(No scrips)</&></i></p>
 % }
 
+<& SelectStageForAdded &>
+
 <& /Elements/Submit,
     Name => 'AddScrips',
     Caption => loc("Apply selected scrips"),
@@ -152,7 +154,7 @@ if ( $AddScrips ) {
         $scrip->Load( $sid );
         next unless $scrip->id;
 
-        my ($status, $msg) = $scrip->AddToObject( $id );
+        my ($status, $msg) = $scrip->AddToObject( $id, Stage => $Stage );
         push @actions, $msg;
     }
 }
@@ -187,6 +189,7 @@ $RemoveScrips => undef
 
 @AddScrip => ()
 $AddScrips => undef
+$Stage     => 'TransactionCreate'
 
 $MoveScripUp => undef
 $MoveScripDown => undef
diff --git a/share/html/Admin/Elements/SelectStageForAdded b/share/html/Admin/Elements/SelectStageForAdded
new file mode 100644
index 0000000..d137bb4
--- /dev/null
+++ b/share/html/Admin/Elements/SelectStageForAdded
@@ -0,0 +1,51 @@
+%# 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 }}}
+<div style="text-align:right">
+<% loc('Select scrip stage for newly added queues:') %>
+<& SelectStage, %ARGS &>
+</div>

commit 8a2d783d4cb9f932f303f6b3867360aa45b84e4d
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jan 11 16:58:18 2012 -0500

    Better heading for TransactionCreate scrips and short explanatory text
    
    Finally we'll have some description of the difference in the interface!

diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index a5d0b84..f347aca 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -50,7 +50,8 @@
 <form action="Scrips.html" method="post">
 <input type="hidden" class="hidden" name="id" value="<% $id %>" />
 
-<h2><&|/l&>On create scrips</&></h2>
+<h2><&|/l&>Scrips</&></h2>
+<div class="admin-hint">Scrips normally run after each individual change to a ticket.</div>
 % my $scrips = $find_scrips->(Stage => 'TransactionCreate');
 <& /Elements/CollectionList, %common_applied_args, Collection => $scrips &>
 % unless ( $scrips->Count ) {
@@ -58,6 +59,7 @@
 % }
 
 <h2><&|/l&>Batch scrips</&></h2>
+<div class="admin-hint">Batch scrips run after a set of related changes to a ticket.</div>
 % $scrips = $find_scrips->(Stage => 'TransactionBatch');
 <& /Elements/CollectionList, %common_applied_args, Collection => $scrips &>
 % unless ( $scrips->Count ) {
diff --git a/share/html/NoAuth/css/base/admin.css b/share/html/NoAuth/css/base/admin.css
index 5f5d6fc..f68d1f3 100644
--- a/share/html/NoAuth/css/base/admin.css
+++ b/share/html/NoAuth/css/base/admin.css
@@ -95,3 +95,10 @@ ul.list-menu ul li {
     padding-bottom: 0;
 }
 
+.admin-hint {
+    font-style: italic;
+}
+
+h2 + .admin-hint {
+    margin-top: -1em;
+}

commit 115a8c135b462cd51223c8205cb380688fcc5b2c
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jan 11 17:15:10 2012 -0500

    Display Normal and Batch instead of TransactionCreate and TransactionBatch
    
    Additionally, disable the displayed Batch option if UseTransactionBatch
    is turned off by RT's configuration.

diff --git a/share/html/Admin/Elements/SelectStage b/share/html/Admin/Elements/SelectStage
index 7bbbf3b..0b700bc 100644
--- a/share/html/Admin/Elements/SelectStage
+++ b/share/html/Admin/Elements/SelectStage
@@ -53,6 +53,7 @@
 
 <option value="<%$value%>"
 <% ($value eq $Default) && qq[ selected="selected"] |n %>
+<% ($value eq 'TransactionBatch' and not RT->Config->Get('UseTransactionBatch')) && qq[ disabled ] %>
 ><% loc($display) %>
 </option>
 
@@ -62,11 +63,11 @@
 if ( !defined $Default || $Default eq '') {
     $Default = 'TransactionCreate';
 }
-my @stages = 'TransactionCreate';
+my @stages = ['TransactionCreate', loc('Normal')];
 
 push @stages, RT->Config->Get('UseTransactionBatch')
-            ? 'TransactionBatch'
-            : ['TransactionBatch', 'TransactionBatch (DISABLED)'];
+            ? ['TransactionBatch', loc('Batch')]
+            : ['TransactionBatch', loc('Batch (disabled by config)')];
 </%INIT>
 <%ARGS>
 $Default => 'TransactionCreate'

commit a091c3b99101cb9d9a76253fc6290c1a4850e77e
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Nov 7 13:51:07 2012 -0500

    As Disabled is no longer a Stage on Scrips, provide a UI for altering it

diff --git a/share/html/Admin/Elements/EditScrip b/share/html/Admin/Elements/EditScrip
index 9392cfb..3bc254e 100644
--- a/share/html/Admin/Elements/EditScrip
+++ b/share/html/Admin/Elements/EditScrip
@@ -87,6 +87,29 @@
 &></td></tr>
 % }
 
+% if ( $id ne 'new' && $EnabledChecked ) {
+<tr><td class="label"><&|/l&>Added</&>:</td><td class="value">\
+% if ( $scrip->IsAdded(0) ) {
+<% loc('Global') %>
+% } else {
+% my $added_to = $scrip->AddedTo;
+% my $found = 0;
+% while ( my $queue = $added_to->Next ) {
+% $m->out(', ') if $found++;
+<% $queue->Name %>
+% last if $found == 10;
+% }
+% $m->out(', ...') if $found == 10;
+% }
+<td></tr>
+% }
+
+<tr><td class="label"> </td><td>
+<input type="hidden" class="hidden" name="SetEnabled" value="1" />
+<input type="checkbox" class="checkbox" name="Scrip-<% $id %>-Enabled" value="1" <% $EnabledChecked |n%> />
+<label for="Scrip-<% $id %>-Enabled"><&|/l&>Enabled (Unchecking this box disables this scrip)</&></label>
+</td></tr>
+
 </table>
 </&>
 
@@ -147,10 +170,15 @@ if ( $id ) {
     $SubmitLabel = loc('Save Changes');
 }
 
+my $EnabledChecked = qq[checked="checked"];
+
 unless ( $id ) {
     $id = 'new';
     $SubmitLabel = loc('Create');
 }
+else {
+    $EnabledChecked = '' if $scrip->Disabled;
+}
 
 my $min_lines = 10;
 
@@ -185,6 +213,7 @@ if ( $id eq 'new' ) {
         CustomCommitCode       => $ARGS{"Scrip-new-CustomCommitCode"},
         CustomIsApplicableCode => $ARGS{"Scrip-new-CustomIsApplicableCode"},
         Stage                  => $ARGS{"Scrip-new-Stage"},
+        Disabled               => $ARGS{"Scrip-new-Enabled"} ? 0 : 1,
     );
 }
 else {
@@ -192,8 +221,11 @@ else {
     return (undef, loc("Couldn't load scrip #[_1]", $id))
         unless $scrip->id;
 
+    $ARGS{"Scrip-$id-Disabled"} = $ARGS{"Scrip-$id-Enabled"} ? 0 : 1
+        if $ARGS{"SetEnabled"};
+
     my @attribs = qw(ScripAction ScripCondition Template Stage
-        Description CustomPrepareCode CustomCommitCode CustomIsApplicableCode);
+        Description CustomPrepareCode CustomCommitCode CustomIsApplicableCode Disabled);
     my @results = UpdateRecordObject(
         AttributesRef   => \@attribs,
         AttributePrefix => 'Scrip-'.$scrip->Id,

commit 6ab828b2eda1d1615474a6adbc5ca02c53ff4321
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 9 16:32:11 2012 +0400

    check rights when we add/remove scrips to/from queues

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 2ffa2f7..487b26c 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -245,16 +245,42 @@ sub AddToObject {
     my $self = shift;
     my %args = @_%2? (ObjectId => @_) : (@_);
 
+    my $queue;
+    if ( $args{'ObjectId'} ) {
+        $queue = RT::Queue->new( $self->CurrentUser );
+        $queue->Load( $args{'ObjectId'} );
+        return (0, $self->loc('Invalid queue'))
+            unless $queue->id;
+    }
+    return ( 0, $self->loc('Permission Denied') )
+        unless $self->CurrentUser->PrincipalObj->HasRight(
+            Object => $queue || $RT::System, Right => 'ModifyScrips',
+        )
+    ;
+
     my $rec = RT::ObjectScrip->new( $self->CurrentUser );
     return $rec->Add( %args, Scrip => $self );
 }
 
 sub RemoveFromObject {
     my $self = shift;
-    my $object = shift;
+    my %args = @_%2? (ObjectId => @_) : (@_);
+
+    my $queue;
+    if ( $args{'ObjectId'} ) {
+        $queue = RT::Queue->new( $self->CurrentUser );
+        $queue->Load( $args{'ObjectId'} );
+        return (0, $self->loc('Invalid queue id'))
+            unless $queue->id;
+    }
+    return ( 0, $self->loc('Permission Denied') )
+        unless $self->CurrentUser->PrincipalObj->HasRight(
+            Object => $queue || $RT::System, Right => 'ModifyScrips',
+        )
+    ;
 
     my $rec = RT::ObjectScrip->new( $self->CurrentUser );
-    $rec->LoadByCols( Scrip => $self->id, ObjectId => $object );
+    $rec->LoadByCols( Scrip => $self->id, ObjectId => $args{'ObjectId'} );
     return (0, $self->loc('Scrip is not added') ) unless $rec->id;
     return $rec->Delete;
 }

commit 74f399be6f0386a504fc7b0e267df29ae9242f75
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Sep 6 01:10:10 2012 +0400

    make sure we pass id further, not a string
    
    If caller passes in queue name then we don't complain,
    but later it's INTifyed to zero. We shouldn't let
    this happen.

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 487b26c..33e1a21 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -251,6 +251,8 @@ sub AddToObject {
         $queue->Load( $args{'ObjectId'} );
         return (0, $self->loc('Invalid queue'))
             unless $queue->id;
+
+        $args{'ObjectId'} = $queue->id;
     }
     return ( 0, $self->loc('Permission Denied') )
         unless $self->CurrentUser->PrincipalObj->HasRight(

commit 5721df81dbc81cf9e7c62ebf8a50b906090a7e6d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 9 17:55:04 2012 +0400

    check that template exists when we add scrip

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 33e1a21..d7d215d 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -260,6 +260,18 @@ sub AddToObject {
         )
     ;
 
+    my $tname = $self->TemplateObj->Name;
+    my $template = RT::Template->new( $self->CurrentUser );
+    $template->LoadQueueTemplate( Queue => $queue? $queue->id : 0, Name => $tname );
+    $template->LoadGlobalTemplate( $tname ) if $queue && !$template->id;
+    unless ( $template->id ) {
+        if ( $queue ) {
+            return (0, $self->loc('No template [_1] in the queue', $tname));
+        } else {
+            return (0, $self->loc('No global template [_1]', $tname));
+        }
+    }
+
     my $rec = RT::ObjectScrip->new( $self->CurrentUser );
     return $rec->Add( %args, Scrip => $self );
 }

commit fed1d4263b2d581644485f91efa6c02756bcde3d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 9 17:54:21 2012 +0400

    tidy test file

diff --git a/t/api/scrip.t b/t/api/scrip.t
index eb54347..1bbed83 100644
--- a/t/api/scrip.t
+++ b/t/api/scrip.t
@@ -1,49 +1,41 @@
 
 use strict;
 use warnings;
-use RT;
 use RT::Test tests => 25;
 
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+ok $queue && $queue->id, 'loaded or created queue';
 
+note 'basic scrips functionality test: create+execute';
 {
-
-ok (require RT::Scrip);
-
-
-my $q = RT::Queue->new(RT->SystemUser);
-$q->Create(Name => 'ScripTest');
-ok($q->Id, "Created a scriptest queue");
-
-my $s1 = RT::Scrip->new(RT->SystemUser);
-my ($val, $msg) =$s1->Create( Queue => $q->Id,
-             ScripAction => 'User Defined',
-             ScripCondition => 'User Defined',
-             CustomIsApplicableCode => 'if ($self->TicketObj->Subject =~ /fire/) { return (1);} else { return(0)}',
-             CustomPrepareCode => 'return 1',
-             CustomCommitCode => '$self->TicketObj->SetPriority("87");',
-             Template => 'Blank'
+    my $s1 = RT::Scrip->new(RT->SystemUser);
+    my ($val, $msg) = $s1->Create(
+        Queue => $queue->Id,
+        ScripAction => 'User Defined',
+        ScripCondition => 'User Defined',
+        CustomIsApplicableCode => '$self->TicketObj->Subject =~ /fire/? 1 : 0',
+        CustomPrepareCode => 'return 1',
+        CustomCommitCode => '$self->TicketObj->SetPriority("87");',
+        Template => 'Blank'
     );
-ok($val,$msg);
-
-my $ticket = RT::Ticket->new(RT->SystemUser);
-my ($tv,$ttv,$tm) = $ticket->Create(Queue => $q->Id,
-                                    Subject => "hair on fire",
-                                    );
-ok($tv, $tm);
-
-is ($ticket->Priority , '87', "Ticket priority is set right");
-
-
-my $ticket2 = RT::Ticket->new(RT->SystemUser);
-my ($t2v,$t2tv,$t2m) = $ticket2->Create(Queue => $q->Id,
-                                    Subject => "hair in water",
-                                    );
-ok($t2v, $t2m);
-
-isnt ($ticket2->Priority , '87', "Ticket priority is set right");
+    ok($val,$msg);
 
+    my $ticket = RT::Ticket->new(RT->SystemUser);
+    my ($tv,$ttv,$tm) = $ticket->Create(
+        Queue => $queue->Id,
+        Subject => "hair on fire",
+    );
+    ok($tv, $tm);
 
+    is ($ticket->Priority , '87', "Ticket priority is set right");
 
+    my $ticket2 = RT::Ticket->new(RT->SystemUser);
+    my ($t2v,$t2tv,$t2m) = $ticket2->Create(
+        Queue => $queue->Id,
+        Subject => "hair in water",
+    );
+    ok($t2v, $t2m);
+    isnt ($ticket2->Priority , '87', "Ticket priority is set right");
 }
 
 

commit 01e1fc9a01d3230cd7d195ae303a4c55d30a6b44
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Nov 5 16:19:48 2012 -0800

    Tests: Add a helper method to check a scrip's ObjectScrips records

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 2e3e8b3..91fc7f4 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -58,6 +58,7 @@ use Socket;
 use File::Temp qw(tempfile);
 use File::Path qw(mkpath);
 use File::Spec;
+use Scalar::Util qw(blessed);
 
 our @EXPORT = qw(is_empty diag parse_mail works fails);
 
@@ -1097,6 +1098,52 @@ sub clean_caught_mails {
     unlink $tmp{'mailbox'};
 }
 
+=head2 object_scrips_are
+
+Takes an L<RT::Scrip> object or ID as the first argument and an arrayref of
+L<RT::Queue> objects and/or Queue IDs as the second argument.
+
+The scrip's applications (L<RT::ObjectScrip> records) are tested to ensure they
+exactly match the arrayref.
+
+An optional third arrayref may be passed to enumerate and test the queues the
+scrip is B<not> added to.  This is most useful for testing the API returns the
+correct results.
+
+=cut
+
+sub object_scrips_are {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    my $self    = shift;
+    my $scrip   = shift;
+    my $to      = shift || [];
+    my $not_to  = shift;
+
+    unless (blessed($scrip)) {
+        my $id = $scrip;
+        $scrip = RT::Scrip->new( RT->SystemUser );
+        $scrip->Load($id);
+    }
+
+    $to = [ map { blessed($_) ? $_->id : $_ } @$to ];
+    Test::More::ok($scrip->IsAdded($_), "added to queue $_" ) foreach @$to;
+    Test::More::is_deeply(
+        [sort map $_->id, @{ $scrip->AddedTo->ItemsArrayRef }],
+        [sort grep $_, @$to ],
+        'correct list of added to queues',
+    );
+
+    if ($not_to) {
+        $not_to = [ map { blessed($_) ? $_->id : $_ } @$not_to ];
+        Test::More::ok(!$scrip->IsAdded($_), "not added to queue $_" ) foreach @$not_to;
+        Test::More::is_deeply(
+            [sort map $_->id, @{ $scrip->NotAddedTo->ItemsArrayRef }],
+            [sort grep $_, @$not_to ],
+            'correct list of not added to queues',
+        );
+    }
+}
+
 =head2 get_relocatable_dir
 
 Takes a path relative to the location of the test file that is being

commit d4452f816a4d8e91a5ef314b66079675dfe64516
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 9 17:55:34 2012 +0400

    test adding/removing scrips from queues

diff --git a/t/api/scrip.t b/t/api/scrip.t
index 1bbed83..e409a30 100644
--- a/t/api/scrip.t
+++ b/t/api/scrip.t
@@ -1,7 +1,7 @@
 
 use strict;
 use warnings;
-use RT::Test tests => 25;
+use RT::Test tests => 61;
 
 my $queue = RT::Test->load_or_create_queue( Name => 'General' );
 ok $queue && $queue->id, 'loaded or created queue';
@@ -38,7 +38,7 @@ note 'basic scrips functionality test: create+execute';
     isnt ($ticket2->Priority , '87', "Ticket priority is set right");
 }
 
-
+note 'modify properties of a scrip';
 {
     my $scrip = RT::Scrip->new($RT::SystemUser);
     my ( $val, $msg ) = $scrip->Create(
@@ -109,3 +109,63 @@ note 'basic scrips functionality test: create+execute';
 
     ok( $scrip->Delete, 'delete the scrip' );
 }
+
+my $queue_B = RT::Test->load_or_create_queue( Name => 'B' );
+ok $queue_B && $queue_B->id, 'loaded or created queue';
+
+note 'check applications vs. templates';
+{
+    my $template = RT::Template->new( RT->SystemUser );
+    my ($status, $msg) = $template->Create( Queue => $queue->id, Name => 'foo' );
+    ok $status, 'created a template';
+
+    my $scrip = RT::Scrip->new(RT->SystemUser);
+    ($status, $msg) = $scrip->Create(
+        Queue          => $queue->Id,
+        ScripAction    => 'User Defined',
+        ScripCondition => 'User Defined',
+        Template       => 'bar',
+    );
+    ok(!$status, "couldn't create scrip, incorrect template");
+
+    ($status, $msg) = $scrip->Create(
+        Queue          => $queue->Id,
+        ScripAction    => 'User Defined',
+        ScripCondition => 'User Defined',
+        Template       => 'foo',
+        CustomIsApplicableCode  => "1;",
+        CustomPrepareCode       => "1;",
+        CustomCommitCode        => "1;",
+    );
+    ok($status, 'created a scrip') or diag "error: $msg";
+    RT::Test->object_scrips_are($scrip, [$queue], [0, $queue_B]);
+
+    ($status, $msg) = $scrip->AddToObject( $queue_B->id );
+    ok(!$status, $msg);
+    RT::Test->object_scrips_are($scrip, [$queue], [0, $queue_B]);
+
+    $template = RT::Template->new( RT->SystemUser );
+    ($status, $msg) = $template->Create( Queue => $queue_B->id, Name => 'foo' );
+    ok $status, 'created a template';
+
+    ($status, $msg) = $scrip->AddToObject( $queue_B->id );
+    ok($status, 'added scrip to another queue');
+    RT::Test->object_scrips_are($scrip, [$queue, $queue_B], [0]);
+
+    ($status, $msg) = $scrip->RemoveFromObject( $queue_B->id );
+    ok($status, 'removed scrip from queue');
+
+    ($status, $msg) = $template->Delete;
+    ok $status, 'deleted template foo in queue B';
+
+    ($status, $msg) = $scrip->AddToObject( $queue_B->id );
+    ok(!$status, $msg);
+    RT::Test->object_scrips_are($scrip, [$queue], [0, $queue_B]);
+
+    ($status, $msg) = $template->Create( Queue => 0, Name => 'foo' );
+    ok $status, 'created a global template';
+
+    ($status, $msg) = $scrip->AddToObject( $queue_B->id );
+    ok($status, 'added scrip');
+    RT::Test->object_scrips_are($scrip, [$queue, $queue_B], [0]);
+}

commit 5f8e3026659a3297b60e05222a0aa0e5f791eac1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 9 18:03:53 2012 +0400

    Add ->IsGlobal method on Scrips and CFs, as an alias for ->IsAdded(0)

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index e3731e2..a5547c4 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -942,7 +942,7 @@ sub ValidateContextObject {
     my $self = shift;
     my $object = shift;
 
-    return 1 if $self->IsAdded(0);
+    return 1 if $self->IsGlobal;
 
     # global only custom fields don't have objects
     # that should be used as context objects.
@@ -1301,6 +1301,8 @@ sub IsAdded {
     return $ocf;
 }
 
+sub IsGlobal { return shift->IsAdded(0) }
+
 =head2 AddToObject OBJECT
 
 Add this custom field as a custom field for a single object, such as a queue or group.
diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index d7d215d..9f883a2 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -221,6 +221,8 @@ sub Delete {
     return ( $self->SUPER::Delete(@_) );
 }
 
+sub IsGlobal { return shift->IsAdded(0) }
+
 sub IsAdded {
     my $self = shift;
     my $record = RT::ObjectScrip->new( $self->CurrentUser );
diff --git a/share/html/Admin/CustomFields/Modify.html b/share/html/Admin/CustomFields/Modify.html
index b7ba114..3c8efbb 100644
--- a/share/html/Admin/CustomFields/Modify.html
+++ b/share/html/Admin/CustomFields/Modify.html
@@ -273,14 +273,14 @@ CustomFieldObj => $CustomFieldObj, CustomFieldValueObj => $cfv, ARGSRef => \%ARG
     }
 }
 
-if ( $CustomFieldObj->id && $CustomFieldObj->LookupType =~ /^RT::(?:User|Group)$/ ) {
+if ( $CustomFieldObj->id && $CustomFieldObj->IsOnlyGlobal ) {
     my ( $ret, $msg );
     my $object = $CustomFieldObj->RecordClassFromLookupType->new( $session{'CurrentUser'} );
 
-    if ( $CustomFieldObj->Disabled && $CustomFieldObj->IsAdded(0) ) {
+    if ( $CustomFieldObj->Disabled && $CustomFieldObj->IsGlobal ) {
         ( $ret, $msg ) = $CustomFieldObj->RemoveFromObject($object);
     }
-    elsif ( !$CustomFieldObj->Disabled && !$CustomFieldObj->IsAdded(0) ) {
+    elsif ( !$CustomFieldObj->Disabled && !$CustomFieldObj->IsGlobal ) {
         ( $ret, $msg ) = $CustomFieldObj->AddToObject($object);
     }
 
diff --git a/share/html/Admin/CustomFields/Objects.html b/share/html/Admin/CustomFields/Objects.html
index 1c6bbd4..771f886 100644
--- a/share/html/Admin/CustomFields/Objects.html
+++ b/share/html/Admin/CustomFields/Objects.html
@@ -53,7 +53,7 @@
 <form action="Objects.html" method="post">
 <input type="hidden" class="hidden" name="id" value="<% $id %>" />
 
-% if ( $is_global ) {
+% if ( $CF->IsGlobal ) {
 <h2><&|/l&>Applies to all objects</&></h2>
 <input type="checkbox" name="RemoveCustomField-<% $CF->id %>" value="0" />
 <&|/l&>check this box to remove this Custom Field from all objects and be able to choose specific objects.</&>
@@ -141,8 +141,6 @@ if ( $UpdateObjs ) {
     }
 }
 
-my $is_global = $CF->IsAdded(0);
-
 my $added = $CF->AddedTo;
 my $not_added = $CF->NotAddedTo;
 
diff --git a/share/html/Admin/Elements/EditScrip b/share/html/Admin/Elements/EditScrip
index 3bc254e..cf8bcd5 100644
--- a/share/html/Admin/Elements/EditScrip
+++ b/share/html/Admin/Elements/EditScrip
@@ -89,7 +89,7 @@
 
 % if ( $id ne 'new' && $EnabledChecked ) {
 <tr><td class="label"><&|/l&>Added</&>:</td><td class="value">\
-% if ( $scrip->IsAdded(0) ) {
+% if ( $scrip->IsGlobal ) {
 <% loc('Global') %>
 % } else {
 % my $added_to = $scrip->AddedTo;
diff --git a/share/html/Elements/RT__CustomField/ColumnMap b/share/html/Elements/RT__CustomField/ColumnMap
index 644bbb4..84ad3df 100644
--- a/share/html/Elements/RT__CustomField/ColumnMap
+++ b/share/html/Elements/RT__CustomField/ColumnMap
@@ -89,7 +89,7 @@ my $COLUMN_MAP = {
     AddedTo => {
         title     => 'Added', # loc
 	value     => sub {
-            if ( $_[0]->IsAdded ) {
+            if ( $_[0]->IsGlobal ) {
                 return $_[0]->loc('Global');
             }
 
@@ -127,7 +127,7 @@ my $COLUMN_MAP = {
         },
         value => sub {
             my $id = $_[0]->id;
-            return '' if $_[0]->IsAdded;
+            return '' if $_[0]->IsGlobal;
 
             my $name = 'RemoveCustomField';
             my $arg = $DECODED_ARGS->{ $name };
diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index b2bbdd9..999453a 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -108,7 +108,7 @@ my $COLUMN_MAP = {
         },
         value => sub {
             my $id = $_[0]->id;
-            return '' if $_[0]->IsAdded;
+            return '' if $_[0]->IsGlobal;
 
             my $name = 'RemoveScrip';
             my $arg = $m->request_args->{ $name };
diff --git a/t/api/scrip_order.t b/t/api/scrip_order.t
index caa6d95..689b559 100644
--- a/t/api/scrip_order.t
+++ b/t/api/scrip_order.t
@@ -230,7 +230,7 @@ sub check_scrips_order {
         $list->ApplySortOrder;
         is_deeply(
             [map $_->id, @{ $list->ItemsArrayRef } ],
-            [map $_->id, grep $_->IsAdded( $qid ) || $_->IsAdded( 0 ), @$scrips],
+            [map $_->id, grep $_->IsAdded( $qid ) || $_->IsGlobal, @$scrips],
             'list of scrips match expected'
         )
     }

commit 435ba8268ae468fc99ffa52ec87257b2ef6672d9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 13 16:18:46 2012 +0400

    add Admin/Scrips/ dir with index

diff --git a/share/html/Admin/Scrips/index.html b/share/html/Admin/Scrips/index.html
new file mode 100644
index 0000000..08574ca
--- /dev/null
+++ b/share/html/Admin/Scrips/index.html
@@ -0,0 +1,71 @@
+%# 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 }}}
+<& /Admin/Elements/Header, Title => loc('Select a Scrip') &>
+<& /Elements/Tabs &>
+
+<& /Elements/CollectionList,
+    OrderBy       => 'Description',
+    Order         => 'ASC',
+    Rows          => 50,
+    %ARGS,
+    Collection    => $scrips,
+    Format        => $Format,
+    AllowSorting  => 1,
+&>
+<%args>
+$Format => undef
+</%args>
+<%INIT>
+my $scrips = RT::Scrips->new( $session{'CurrentUser'} );
+$scrips->FindAllRows;
+$scrips->UnLimit;
+
+$m->callback(CallbackName => 'Massage', Scrips => $scrips);
+
+$Format ||= RT->Config->Get('AdminSearchResultFormat')->{'Scrips'};
+</%INIT>
diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index f2cfdf2..465f6f2 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -97,6 +97,16 @@ my $build_admin_menu = sub {
         $cfs->child( create => title => loc('Create'), path => "/Admin/CustomFields/Modify.html?Create=1" );
     }
 
+    if ( $session{'CurrentUser'}->HasRight( Object => RT->System, Right => 'ModifyScrips' ) ) {
+        my $scrips = $admin->child( 'scrips' =>
+            title       => loc('Scrips'),
+            description => loc('Manage scrips'),
+            path        => '/Admin/Scrips/',
+        );
+        $scrips->child( select => title => loc('Select'), path => "/Admin/Scrips/" );
+        $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Modify.html?Create=1" );
+    }
+
     my $admin_global = $admin->child( global =>
         title       => loc('Global'),
         description => loc('Manage properties and configuration which apply to all queues'),

commit fee8a9b4f09e02c4ddd93a364b9d5bfbc1b647e8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sun Jan 15 03:38:46 2012 +0400

    Split turn EditScrip into Create and Modify pages
    
    Create and Modify are different for scrips, so we
    split them up and use smaller components for common
    parts.

diff --git a/share/html/Admin/Elements/EditScrip b/share/html/Admin/Elements/EditScrip
deleted file mode 100644
index cf8bcd5..0000000
--- a/share/html/Admin/Elements/EditScrip
+++ /dev/null
@@ -1,238 +0,0 @@
-%# 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 }}}
-<& /Elements/ListActions, actions => \@actions &>
-  
-<form method="post" action="Modify.html" id="ModifyScrip" name="ModifyScrip">
-<input type="hidden" class="hidden" name="id" value="<% $id %>" />
-<input type="hidden" class="hidden" name="Queue" value="<% $Queue %>" />
-
-<&| /Widgets/TitleBox, title => loc('Scrip Fields') &>
-<table>
-
-<tr><td class="label"><&|/l&>Description</&>:</td><td class="value">\
-<input name="Scrip-<% $id %>-Description" \
-    size="60" \
-    value="<% $ARGS{"Scrip-$id-Description"} || $scrip->Description || '' %>" />
-</td></tr>
-
-<tr><td class="label"><&|/l&>Condition</&>:</td><td class="value">\
-<& /Admin/Elements/SelectScripCondition,
-    Name => "Scrip-$id-ScripCondition",
-    Default => $ARGS{"Scrip-$id-ScripCondition"} || $scrip->ConditionObj->Id,
-&></td></tr>
-
-<tr><td class="label"><&|/l&>Action</&>:</td><td class="value">\
-<& /Admin/Elements/SelectScripAction,
-    Name => "Scrip-$id-ScripAction",
-    Default => $ARGS{"Scrip-$id-ScripAction"} || $scrip->ActionObj->Id,
-&></td></tr>
-
-<tr><td class="label"><&|/l&>Template</&>:</td><td class="value">\
-<& /Admin/Elements/SelectTemplate,
-    Name => "Scrip-$id-Template",
-    Default => $ARGS{"Scrip-$id-Template"} || $scrip->TemplateObj->Id,
-    Queue => $Queue,
-&></td></tr>
-
-% if ( $id eq 'new' ) {
-<tr><td class="label"><&|/l&>Stage</&>:</td><td class="value">\
-<& /Admin/Elements/SelectStage,
-    Name => "Scrip-$id-Stage",
-    Default => $ARGS{"Scrip-$id-Stage"},
-&></td></tr>
-% }
-
-% if ( $id ne 'new' && $EnabledChecked ) {
-<tr><td class="label"><&|/l&>Added</&>:</td><td class="value">\
-% if ( $scrip->IsGlobal ) {
-<% loc('Global') %>
-% } else {
-% my $added_to = $scrip->AddedTo;
-% my $found = 0;
-% while ( my $queue = $added_to->Next ) {
-% $m->out(', ') if $found++;
-<% $queue->Name %>
-% last if $found == 10;
-% }
-% $m->out(', ...') if $found == 10;
-% }
-<td></tr>
-% }
-
-<tr><td class="label"> </td><td>
-<input type="hidden" class="hidden" name="SetEnabled" value="1" />
-<input type="checkbox" class="checkbox" name="Scrip-<% $id %>-Enabled" value="1" <% $EnabledChecked |n%> />
-<label for="Scrip-<% $id %>-Enabled"><&|/l&>Enabled (Unchecking this box disables this scrip)</&></label>
-</td></tr>
-
-</table>
-</&>
-
-% if ($session{CurrentUser}->HasRight(Object => $RT::System, Right => 'ExecuteCode')) {
-<& /Elements/Submit,
-    Label => $SubmitLabel,
-    Reset => 1,
-&><br />
-
-<&| /Widgets/TitleBox, title => loc('User Defined conditions and actions') &>
-<table>
-<tr><td colspan="2">
-<i><&|/l&>(Use these fields when you choose 'User Defined' for a condition or action)</&></i>
-</td></tr>
-
-<tr><td class="labeltop"><&|/l&>Custom condition</&>:</td><td class="value">
-% my $code = $ARGS{"Scrip-$id-CustomIsApplicableCode"} || $scrip->CustomIsApplicableCode || '';
-% my $lines = @{[ $code =~ /\n/gs ]} + 3;
-% $lines = $min_lines if $lines < $min_lines;
-<textarea cols="80" rows="<% $lines %>" name="Scrip-<% $id %>-CustomIsApplicableCode"><% $code %></textarea>
-</td></tr>
-
-<tr><td class="labeltop"><&|/l&>Custom action preparation code</&>:</td><td class="value">
-% $code = $ARGS{"Scrip-$id-CustomPrepareCode"} || $scrip->CustomPrepareCode || '';
-% $lines = @{[ $code =~ /\n/gs ]} + 3;
-% $lines = $min_lines if $lines < $min_lines;
-<textarea cols="80" rows="<% $lines %>" name="Scrip-<% $id %>-CustomPrepareCode"><% $code %></textarea>
-</td></tr>
-
-<tr><td class="labeltop"><&|/l&>Custom action cleanup code</&>:</td><td class="value">
-% $code = $ARGS{"Scrip-$id-CustomCommitCode"} || $scrip->CustomCommitCode || '';
-% $lines = @{[ $code =~ /\n/gs ]} + 3;
-% $lines = $min_lines if $lines < $min_lines;
-<textarea cols="80" rows="<% $lines %>" name="Scrip-<% $id || '' %>-CustomCommitCode"><% $code %></textarea>
-</td></tr>
-
-</table>
-</&>
-
-% }
-
-<& /Elements/Submit,
-    Label => $SubmitLabel,
-    Reset => 1,
-&>
-
-</form>
-<%init>
-
-my (@actions, $SubmitLabel);
-my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
-
-if ( $id ) {
-    $scrip->Load( $id );
-    unless ( $id = $scrip->id ) {
-        push @actions, loc("Couldn't load scrip #[_1]", $id);
-    }
-    $SubmitLabel = loc('Save Changes');
-}
-
-my $EnabledChecked = qq[checked="checked"];
-
-unless ( $id ) {
-    $id = 'new';
-    $SubmitLabel = loc('Create');
-}
-else {
-    $EnabledChecked = '' if $scrip->Disabled;
-}
-
-my $min_lines = 10;
-
-my ($ok, $msg) = $scrip->CompileCheck;
-push @actions, $msg if !$ok;
-
-</%init>
-
-<%ARGS>
-$id => undef
-$title => undef
-$Queue => 0
-</%ARGS>
-
-<%METHOD Process>
-<%ARGS>
-$id => undef
-$Queue => undef
-</%ARGS>
-<%INIT>
-return ($id) unless $id;
-
-my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
-if ( $id eq 'new' ) {
-    return $scrip->Create(
-        Queue                  => $Queue,
-        ScripAction            => $ARGS{"Scrip-new-ScripAction"},
-        ScripCondition         => $ARGS{"Scrip-new-ScripCondition"},
-        Template               => $ARGS{"Scrip-new-Template"},
-        Description            => $ARGS{"Scrip-new-Description"},
-        CustomPrepareCode      => $ARGS{"Scrip-new-CustomPrepareCode"},
-        CustomCommitCode       => $ARGS{"Scrip-new-CustomCommitCode"},
-        CustomIsApplicableCode => $ARGS{"Scrip-new-CustomIsApplicableCode"},
-        Stage                  => $ARGS{"Scrip-new-Stage"},
-        Disabled               => $ARGS{"Scrip-new-Enabled"} ? 0 : 1,
-    );
-}
-else {
-    $scrip->Load( $id );
-    return (undef, loc("Couldn't load scrip #[_1]", $id))
-        unless $scrip->id;
-
-    $ARGS{"Scrip-$id-Disabled"} = $ARGS{"Scrip-$id-Enabled"} ? 0 : 1
-        if $ARGS{"SetEnabled"};
-
-    my @attribs = qw(ScripAction ScripCondition Template Stage
-        Description CustomPrepareCode CustomCommitCode CustomIsApplicableCode Disabled);
-    my @results = UpdateRecordObject(
-        AttributesRef   => \@attribs,
-        AttributePrefix => 'Scrip-'.$scrip->Id,
-        Object          => $scrip,
-        ARGSRef         => \%ARGS
-    );
-    return ($scrip->id, @results);
-}
-</%INIT>
-</%METHOD>
diff --git a/share/html/Admin/Scrips/Create.html b/share/html/Admin/Scrips/Create.html
new file mode 100644
index 0000000..8473c06
--- /dev/null
+++ b/share/html/Admin/Scrips/Create.html
@@ -0,0 +1,138 @@
+%# 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 }}}
+<& /Admin/Elements/Header, Title => $title &>
+<& /Elements/Tabs &>
+<& /Elements/ListActions, actions => \@results &>
+
+<form method="post" action="Create.html" id="CreateScrip" name="CreateScrip">
+<input type="hidden" class="hidden" name="Queue" value="<% $Queue %>" />
+
+<&| /Widgets/TitleBox, title => loc('Basics') &>
+<table>
+
+<& Elements/EditBasics, %ARGS, Scrip => $scrip, Queue => $queue_obj &>
+
+<tr><td class="label"><&|/l&>Stage</&>:</td><td class="value">\
+<& /Admin/Elements/SelectStage, Default => $ARGS{"Stage"} &></td></tr>
+
+<tr><td class="label"> </td><td>
+<input type="hidden" class="hidden" name="SetEnabled" value="1" />
+<input type="checkbox" class="checkbox" name="Enabled" value="1" <% $ARGS{'Enabled'}? 'checked="checked"': '' |n%> />
+<label for="Enabled"><&|/l&>Enabled (Unchecking this box disables this scrip)</&></label>
+</td></tr>
+
+</table>
+</&>
+
+<& /Elements/Submit,
+    Label => loc('Create'),
+    Name => 'Create',
+&>
+
+% if ($session{CurrentUser}->HasRight(Object => $RT::System, Right => 'ExecuteCode')) {
+<& Elements/EditCustomCode, %ARGS, Scrip => $scrip &>
+<& /Elements/Submit,
+    Label => loc('Create'),
+    Name => 'Create',
+&>
+% }
+
+</form>
+<%ARGS>
+$Queue => 0
+$Create => undef
+</%ARGS>
+<%INIT>
+my @results;
+
+$ARGS{'Enabled'} = 1 unless $ARGS{'SetEnabled'};
+
+my $queue_obj;
+if ( $Queue ) {
+    $queue_obj = RT::Queue->new( $session{'CurrentUser'} );
+    $queue_obj->Load( $Queue );
+    Abort( loc("Couldn't load queue [_1]", $Queue) )
+        unless $queue_obj->id;
+}
+
+my $title;
+if ( $queue_obj ) {
+    $title = loc('Create a scrip and add to queue [_1]', $queue_obj->Name );
+} else {
+    $title = loc('Create a global scrip');
+}
+
+my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
+if ( $Create ) {
+    my ($status, $msg) = $scrip->Create(
+        Description            => $ARGS{"Description"},
+
+        Queue                  => $Queue || 0,
+        Stage                  => $ARGS{"Stage"},
+        Disabled               => $ARGS{"Disabled"},
+
+        ScripAction            => $ARGS{"ScripAction"},
+        ScripCondition         => $ARGS{"ScripCondition"},
+        Template               => $ARGS{"Template"},
+
+        CustomPrepareCode      => $ARGS{"CustomPrepareCode"},
+        CustomCommitCode       => $ARGS{"CustomCommitCode"},
+        CustomIsApplicableCode => $ARGS{"CustomIsApplicableCode"},
+    );
+
+    MaybeRedirectForResults(
+        Force     => 1,
+        Actions   => [ $msg ],
+        Path      => 'Admin/Scrips/Modify.html',
+        Arguments => { id => $scrip->id },
+    ) if $status;
+
+    push @results, $msg;
+}
+
+</%INIT>
diff --git a/share/html/Admin/Scrips/Modify.html b/share/html/Admin/Scrips/Elements/EditBasics
similarity index 72%
copy from share/html/Admin/Scrips/Modify.html
copy to share/html/Admin/Scrips/Elements/EditBasics
index 3a6ed10..d7746b2 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Elements/EditBasics
@@ -45,19 +45,29 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Elements/Tabs &>
-<& /Elements/ListActions, actions => \@results &>
-<& /Admin/Elements/EditScrip, title => $title,  %ARGS, id => $id &>
+<tr><td class="label"><&|/l&>Description</&>:</td><td class="value">\
+<input name="Description" \
+    size="60" \
+    value="<% $ARGS{"Description"} || $Scrip->Description || '' %>" />
+</td></tr>
 
-<%init>
-my ($id, @results) = $m->comp( '/Admin/Elements/EditScrip:Process', %ARGS );
+<tr><td class="label"><&|/l&>Condition</&>:</td><td class="value">\
+<& /Admin/Elements/SelectScripCondition,
+    Default => $ARGS{"ScripCondition"} || $Scrip->ConditionObj->Id,
+&></td></tr>
 
-my ($title);
-if ( $id ) {
-    $title = loc("Modify a scrip that applies to all queues");
-}
-else {
-    $title = loc("Add a scrip which will apply to all queues");
-}
-</%init>
+<tr><td class="label"><&|/l&>Action</&>:</td><td class="value">\
+<& /Admin/Elements/SelectScripAction,
+    Default => $ARGS{"ScripAction"} || $Scrip->ActionObj->Id,
+&></td></tr>
+
+<tr><td class="label"><&|/l&>Template</&>:</td><td class="value">\
+<& /Admin/Elements/SelectTemplate,
+    Default => $ARGS{"Template"} || $Scrip->TemplateObj->Id,
+&></td></tr>
+
+<%ARGS>
+$Scrip
+</%ARGS>
+<%INIT>
+</%INIT>
diff --git a/share/html/Admin/Scrips/Modify.html b/share/html/Admin/Scrips/Elements/EditCustomCode
similarity index 70%
copy from share/html/Admin/Scrips/Modify.html
copy to share/html/Admin/Scrips/Elements/EditCustomCode
index 3a6ed10..775dd51 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Elements/EditCustomCode
@@ -45,19 +45,33 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Elements/Tabs &>
-<& /Elements/ListActions, actions => \@results &>
-<& /Admin/Elements/EditScrip, title => $title,  %ARGS, id => $id &>
+<&| /Widgets/TitleBox, title => loc('User Defined conditions and results') &>
 
-<%init>
-my ($id, @results) = $m->comp( '/Admin/Elements/EditScrip:Process', %ARGS );
+<table>
+<tr><td colspan="2" class="comment">
+<i><&|/l&>(Use these fields when you choose 'User Defined' for a condition or action)</&></i>
+</td></tr>
 
-my ($title);
-if ( $id ) {
-    $title = loc("Modify a scrip that applies to all queues");
-}
-else {
-    $title = loc("Add a scrip which will apply to all queues");
-}
-</%init>
+% while ( my ($method, $desc) = splice @list, 0, 2 ) {
+<tr><td class="labeltop"><% $desc %>:</td><td class="value">
+% my $code = $ARGS{ $method } || $Scrip->$method() || '';
+% my $lines = @{[ $code =~ /\n/gs ]} + 3;
+% $lines = $min_lines if $lines < $min_lines;
+<textarea cols="80" rows="<% $lines %>" name="<% $method %>"><% $code %></textarea>
+</td></tr>
+% }
+
+</table>
+</&>
+<%ARGS>
+$Scrip
+</%ARGS>
+<%INIT>
+my @list = (
+    CustomIsApplicableCode => loc('Custom condition'),
+    CustomPrepareCode      => loc('Custom action preparation code'),
+    CustomCommitCode       => loc('Custom action cleanup code'),
+);
+
+my $min_lines = 10;
+</%INIT>
diff --git a/share/html/Admin/Scrips/Modify.html b/share/html/Admin/Scrips/Modify.html
index 3a6ed10..ba948b1 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Modify.html
@@ -45,19 +45,90 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/Header, Title => loc("Modify scrip #[_1]", $id) &>
 <& /Elements/Tabs &>
 <& /Elements/ListActions, actions => \@results &>
-<& /Admin/Elements/EditScrip, title => $title,  %ARGS, id => $id &>
 
-<%init>
-my ($id, @results) = $m->comp( '/Admin/Elements/EditScrip:Process', %ARGS );
+<form method="post" action="Modify.html" id="ModifyScrip" name="ModifyScrip">
+<input type="hidden" class="hidden" name="id" value="<% $id %>" />
 
-my ($title);
-if ( $id ) {
-    $title = loc("Modify a scrip that applies to all queues");
-}
-else {
-    $title = loc("Add a scrip which will apply to all queues");
+<&| /Widgets/TitleBox, title => loc('Basics') &>
+<table>
+
+<& Elements/EditBasics, %ARGS, Scrip => $scrip &>
+
+% if ( not $disabled ) {
+<tr><td class="label"><&|/l&>Applies to</&>:</td><td class="value">\
+% if ( $scrip->IsGlobal ) {
+<a href="<% RT->Config->Get('WebPath') %>/Admin/Global/Scrips.html"><% loc('Global') %></a>
+% } else {
+% my $added_to = $scrip->AddedTo;
+% my $found = 0;
+% while ( my $queue = $added_to->Next ) {
+% $m->out(', ') if $found++;
+<a href="<% RT->Config->Get('WebPath') %>/Admin/Queues/Scrips.html?id=<% $queue->id %>">\
+<% $queue->Name %></a>\
+% last if $found == 10;
+% }
+% $m->out(', ...') if $found == 10;
+% }
+<td></tr>
+% }
+
+<tr><td class="label"> </td><td>
+<input type="hidden" class="hidden" name="SetEnabled" value="1" />
+<input type="checkbox" class="checkbox" name="Enabled" value="1" <% $EnabledChecked |n%> />
+<label for="Enabled"><&|/l&>Enabled (Unchecking this box disables this scrip)</&></label>
+</td></tr>
+
+</table>
+</&>
+
+<& /Elements/Submit, Label => loc('Save Changes'), Name => 'Update', Reset => 1 &>
+
+% if ($session{CurrentUser}->HasRight(Object => $RT::System, Right => 'ExecuteCode')) {
+<& Elements/EditCustomCode, %ARGS, Scrip => $scrip &>
+<& /Elements/Submit, Label => loc('Save Changes'), Name => 'Update', Reset => 1 &>
+% }
+
+</form>
+<%ARGS>
+$id     => undef
+$Update => undef
+</%ARGS>
+<%INIT>
+my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
+$scrip->Load( $id );
+Abort(loc("Couldn't load scrip #[_1]", $id))
+    unless $scrip->id;
+
+my $disabled = $scrip->Disabled;
+
+if ( $Update ) {
+    my @attribs = qw(
+        Description
+        ScripAction ScripCondition Template
+        CustomPrepareCode CustomCommitCode CustomIsApplicableCode
+    );
+    if ($ARGS{"SetEnabled"}) {
+        push @attribs, "Disabled";
+        $ARGS{"Disabled"} = not $ARGS{"Enabled"};
+    }
+    my @results = UpdateRecordObject(
+        AttributesRef   => \@attribs,
+        Object          => $scrip,
+        ARGSRef         => \%ARGS
+    );
+    MaybeRedirectForResults(
+        Actions   => \@results,
+        Arguments => { id => $scrip->id },
+    );
 }
-</%init>
+
+my $EnabledChecked = qq[checked="checked"];
+$EnabledChecked = '' if $disabled;
+
+my @results;
+my ($ok, $msg) = $scrip->CompileCheck;
+push @results, $msg if !$ok;
+</%INIT>
diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index 465f6f2..aa84a76 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -104,7 +104,7 @@ my $build_admin_menu = sub {
             path        => '/Admin/Scrips/',
         );
         $scrips->child( select => title => loc('Select'), path => "/Admin/Scrips/" );
-        $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Modify.html?Create=1" );
+        $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html" );
     }
 
     my $admin_global = $admin->child( global =>
@@ -119,7 +119,7 @@ my $build_admin_menu = sub {
         path        => '/Admin/Global/Scrips.html',
     );
     $scrips->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" );
-    $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Modify.html?Create=1" );
+    $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html" );
 
     my $templates = $admin_global->child( templates =>
         title       => loc('Templates'),
@@ -294,7 +294,7 @@ my $build_admin_menu = sub {
 
             my $scrips = $queue->child( scrips => title => loc('Scrips'), path => "/Admin/Queues/Scrips.html?id=" . $id);
             $scrips->child( select => title => loc('Select'), path => "/Admin/Queues/Scrips.html?id=" . $id );
-            $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Modify.html?Create=1;Queue=" . $id);
+            $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Queue=" . $id);
 
             my $ticket_cfs = $queue->child( 'ticket-custom-fields' => title => loc('Ticket Custom Fields'),
                 path => '/Admin/Queues/CustomFields.html?SubType=RT::Ticket&id=' . $id );
@@ -365,7 +365,7 @@ my $build_admin_menu = sub {
     if ( $request_path =~ m{^/Admin/Global/Scrips\.html} ) {
         my $tabs = PageMenu();
         $tabs->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" );
-        $tabs->child( create => title => loc('Create'), path => "/Admin/Scrips/Modify.html?Create=1" );
+        $tabs->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html" );
     }
 
     if ( $request_path =~ m{^/Admin/Global/Templates?\.html} ) {
diff --git a/t/ticket/scrips_batch.t b/t/ticket/scrips_batch.t
index a6d22b5..625eabe 100644
--- a/t/ticket/scrips_batch.t
+++ b/t/ticket/scrips_batch.t
@@ -19,24 +19,24 @@ my $sid;
     $m->follow_link_ok( { id => 'tools-config-queues' } );
     $m->follow_link_ok( { text => $queue->Name } );
     $m->follow_link_ok( { id => 'page-scrips-create'});
-    $m->form_name('ModifyScrip');
-    $m->field('Scrip-new-Description' => 'test');
-    $m->select('Scrip-new-ScripCondition' => 'On Transaction');
-    $m->select('Scrip-new-ScripAction' => 'User Defined');
-    $m->select('Scrip-new-Template' => 'Global template: Blank');
-    $m->select('Scrip-new-Stage' => 'TransactionBatch');
-    $m->field('Scrip-new-CustomPrepareCode' => 'return 1;');
-    $m->field('Scrip-new-CustomCommitCode' => 'return 1;');
-    $m->submit;
-    $m->content_contains("Scrip Created");
 
+    $m->form_name('CreateScrip');
+    $m->field('Description' => 'test');
+    $m->select('ScripCondition' => 'On Transaction');
+    $m->select('ScripAction' => 'User Defined');
+    $m->select('Template' => 'Blank');
+    $m->select('Stage' => 'Batch');
+    $m->field('CustomPrepareCode' => 'return 1;');
+    $m->field('CustomCommitCode' => 'return 1;');
+    $m->click('Create');
+    $m->content_contains("Scrip Created");
 
     my $form = $m->form_name('ModifyScrip');
     $sid = $form->value('id');
-    is $m->value("Scrip-$sid-Description"), 'test', 'correct description';
-    is value_name($form, "Scrip-$sid-ScripCondition"), 'On Transaction', 'correct condition';
-    is value_name($form, "Scrip-$sid-ScripAction"), 'User Defined', 'correct action';
-    is value_name($form, "Scrip-$sid-Template"), 'Global template: Blank', 'correct template';
+    is $m->value("Description"), 'test', 'correct description';
+    is value_name($form, "ScripCondition"), 'On Transaction', 'correct condition';
+    is value_name($form, "ScripAction"), 'User Defined', 'correct action';
+    is value_name($form, "Template"), 'Blank', 'correct template';
 
     {
         my $rec = RT::ObjectScrip->new( RT->SystemUser );
@@ -61,8 +61,8 @@ foreach my \$txn ( \@\$batch ) {
 return 1;
 END
 
-    $m->field( "Scrip-$sid-CustomCommitCode" => $code );
-    $m->submit;
+    $m->field( "CustomCommitCode" => $code );
+    $m->click('Update');
 
     $m->goto_create_ticket( $queue );
     $m->form_name('TicketCreate');
diff --git a/t/web/scrips.t b/t/web/scrips.t
index 0ff46bf..855029a 100644
--- a/t/web/scrips.t
+++ b/t/web/scrips.t
@@ -49,14 +49,14 @@ sub prepare_code_with_value {
         diag "Create Scrip (Cond #$condition)" if $ENV{TEST_VERBOSE};
         $m->follow_link_ok({id => 'tools-config-global-scrips-create'});
         my $prepare_code = prepare_code_with_value($prepare_code_value);
-        $m->form_name('ModifyScrip');
+        $m->form_name('CreateScrip');
         $m->set_fields(
-            'Scrip-new-ScripCondition'    => $condition,
-            'Scrip-new-ScripAction'       => 15, # User Defined
-            'Scrip-new-Template'          => 1,  # Blank
-            'Scrip-new-CustomPrepareCode' => $prepare_code,
+            'ScripCondition'    => $condition,
+            'ScripAction'       => 15, # User Defined
+            'Template'          => 1,  # Blank
+            'CustomPrepareCode' => $prepare_code,
         );
-        $m->submit;
+        $m->click('Create');
     }
 
     my $ticket_obj = RT::Test->create_ticket(

commit 86fcd866165c547557c0faa52ded6dee55b28a4c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 20 21:20:21 2012 +0400

    Admin/Scrips/Objects.html page

diff --git a/share/html/Admin/Scrips/Modify.html b/share/html/Admin/Scrips/Modify.html
index ba948b1..6cb2239 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Modify.html
@@ -58,7 +58,8 @@
 <& Elements/EditBasics, %ARGS, Scrip => $scrip &>
 
 % if ( not $disabled ) {
-<tr><td class="label"><&|/l&>Applies to</&>:</td><td class="value">\
+<tr><td class="label"><a href="Objects.html?id=<% $id %>"><&|/l&>Applies to</&></a>:</td>
+<td class="value">\
 % if ( $scrip->IsGlobal ) {
 <a href="<% RT->Config->Get('WebPath') %>/Admin/Global/Scrips.html"><% loc('Global') %></a>
 % } else {
diff --git a/share/html/Admin/Scrips/Modify.html b/share/html/Admin/Scrips/Objects.html
similarity index 50%
copy from share/html/Admin/Scrips/Modify.html
copy to share/html/Admin/Scrips/Objects.html
index ba948b1..c4a1011 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Objects.html
@@ -45,90 +45,99 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => loc("Modify scrip #[_1]", $id) &>
+<& /Admin/Elements/Header, Title => $title &>
 <& /Elements/Tabs &>
-<& /Elements/ListActions, actions => \@results &>
+<& /Elements/ListActions &>
 
-<form method="post" action="Modify.html" id="ModifyScrip" name="ModifyScrip">
+<form action="Objects.html" method="post">
 <input type="hidden" class="hidden" name="id" value="<% $id %>" />
 
-<&| /Widgets/TitleBox, title => loc('Basics') &>
-<table>
-
-<& Elements/EditBasics, %ARGS, Scrip => $scrip &>
-
-% if ( not $disabled ) {
-<tr><td class="label"><&|/l&>Applies to</&>:</td><td class="value">\
-% if ( $scrip->IsGlobal ) {
-<a href="<% RT->Config->Get('WebPath') %>/Admin/Global/Scrips.html"><% loc('Global') %></a>
+% if ( $is_global ) {
+<h2><&|/l&>Applies to all objects</&></h2>
+<input type="checkbox" name="RemoveScrip-<% $id %>" value="0" />
+<&|/l&>check this box to remove this scrip from all objects and be able to choose specific objects.</&>
 % } else {
-% my $added_to = $scrip->AddedTo;
-% my $found = 0;
-% while ( my $queue = $added_to->Next ) {
-% $m->out(', ') if $found++;
-<a href="<% RT->Config->Get('WebPath') %>/Admin/Queues/Scrips.html?id=<% $queue->id %>">\
-<% $queue->Name %></a>\
-% last if $found == 10;
-% }
-% $m->out(', ...') if $found == 10;
-% }
-<td></tr>
-% }
+<h2><&|/l&>Apply globally</&></h2>
 
-<tr><td class="label"> </td><td>
-<input type="hidden" class="hidden" name="SetEnabled" value="1" />
-<input type="checkbox" class="checkbox" name="Enabled" value="1" <% $EnabledChecked |n%> />
-<label for="Enabled"><&|/l&>Enabled (Unchecking this box disables this scrip)</&></label>
-</td></tr>
+<input type="checkbox" name="AddScrip-<% $id %>" value="0" />
+<&|/l&>check this box to apply this scrip to all objects.</&>
 
-</table>
-</&>
+<h2><&|/l&>Selected objects</&></h2>
+<& /Elements/CollectionList,
+    OrderBy => 'id',
+    Order => 'ASC',
+    %ARGS,
+    Collection => $added,
+    Rows => 0,
+    Page => 1,
+    Format        => $format,
+    DisplayFormat => "'__CheckBox.{RemoveScrip-$id}__','__ScripStage.{$id}__',". $format,
+    AllowSorting => 0,
+    ShowEmpty    => 0,
+    PassArguments => [
+        qw(id Stage Format Rows Page Order OrderBy),
+    ],
+&>
 
-<& /Elements/Submit, Label => loc('Save Changes'), Name => 'Update', Reset => 1 &>
+<h2><&|/l&>Unselected objects</&></h2>
+<& /Elements/CollectionList,
+    OrderBy => 'Name',
+    Order   => 'ASC',
+    %ARGS,
+    Collection    => $not_added,
+    Rows          => 50,
+    Format        => $format,
+    DisplayFormat => "'__CheckBox.{AddScrip-". $id ."}__',". $format,
+    AllowSorting  => 1,
+    ShowEmpty     => 0,
+    PassArguments => [
+        qw(id Stage Format Rows Page Order OrderBy),
+    ],
+&>
 
-% if ($session{CurrentUser}->HasRight(Object => $RT::System, Right => 'ExecuteCode')) {
-<& Elements/EditCustomCode, %ARGS, Scrip => $scrip &>
-<& /Elements/Submit, Label => loc('Save Changes'), Name => 'Update', Reset => 1 &>
+<& /Admin/Elements/SelectStageForAdded, Default => $Stage &>
 % }
 
+<& /Elements/Submit, Name => 'Update' &>
 </form>
+
 <%ARGS>
-$id     => undef
-$Update => undef
+$id => undef
+$Stage => undef
+$Update => 0
 </%ARGS>
 <%INIT>
 my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
-$scrip->Load( $id );
-Abort(loc("Couldn't load scrip #[_1]", $id))
-    unless $scrip->id;
-
-my $disabled = $scrip->Disabled;
+$scrip->Load($id) or Abort(loc("Could not load scrip #[_1]", $id));
+$id = $scrip->id;
 
 if ( $Update ) {
-    my @attribs = qw(
-        Description
-        ScripAction ScripCondition Template
-        CustomPrepareCode CustomCommitCode CustomIsApplicableCode
-    );
-    if ($ARGS{"SetEnabled"}) {
-        push @attribs, "Disabled";
-        $ARGS{"Disabled"} = not $ARGS{"Enabled"};
+    my (@results);
+    if ( defined (my $del = $ARGS{"RemoveScrip-$id"}) ) {
+        foreach my $id ( ref $del? (@$del) : ($del) ) {
+            my ($status, $msg) = $scrip->RemoveFromObject( $id );
+            push @results, $msg;
+        }
+    }
+    if ( defined (my $add = $ARGS{"AddScrip-$id"}) ) {
+        foreach my $id ( ref $add? (@$add) : ($add) ) {
+            my ($status, $msg) = $scrip->AddToObject( $id, Stage => $Stage );
+            push @results, $msg;
+        }
     }
-    my @results = UpdateRecordObject(
-        AttributesRef   => \@attribs,
-        Object          => $scrip,
-        ARGSRef         => \%ARGS
-    );
     MaybeRedirectForResults(
         Actions   => \@results,
-        Arguments => { id => $scrip->id },
+        Arguments => { id => $id },
     );
 }
 
-my $EnabledChecked = qq[checked="checked"];
-$EnabledChecked = '' if $disabled;
+my $is_global = $scrip->IsGlobal;
+
+my $added = $scrip->AddedTo;
+my $not_added = $scrip->NotAddedTo;
+
+my $format = RT->Config->Get('AdminSearchResultFormat')->{'Queues'};
+
+my $title = loc('Modify associated objects for scrip #[_1]', $id);
 
-my @results;
-my ($ok, $msg) = $scrip->CompileCheck;
-push @results, $msg if !$ok;
 </%INIT>
diff --git a/share/html/Elements/RT__Queue/ColumnMap b/share/html/Elements/RT__Queue/ColumnMap
index e08dd7c..6b52763 100644
--- a/share/html/Elements/RT__Queue/ColumnMap
+++ b/share/html/Elements/RT__Queue/ColumnMap
@@ -89,6 +89,15 @@ my $COLUMN_MAP = {
         attribute => 'Lifecycle',
         value => sub { return $_[0]->Lifecycle->Name },
     },
+    ScripStage => {
+        title => 'Stage', # loc
+        value => sub {
+            my $sid = $_[-1];
+            my $os = RT::ObjectScrip->new( $_[0]->CurrentUser );
+            $os->LoadByCols( Scrip => $_[-1], ObjectId => $_[0]->id );
+            return $os->Stage;
+        },
+    },
 };
 
 foreach my $field (qw(
diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index aa84a76..d32e1da 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -362,6 +362,18 @@ my $build_admin_menu = sub {
         }
     }
 
+    if ( $request_path =~ m{^/Admin/Scrips/} ) {
+        if ( $m->request_args->{'id'} && $m->request_args->{'id'} =~ /^\d+$/ ) {
+            my $id = $m->request_args->{'id'};
+            my $obj = RT::Scrip->new( $session{'CurrentUser'} );
+            $obj->Load($id);
+
+            my $tabs = PageMenu();
+            $tabs->child( basics => title => loc('Basics') => path => "/Admin/Scrips/Modify.html?id=".$id );
+            $tabs->child( 'applies-to' => title => loc('Applies to'), path => "/Admin/Scrips/Objects.html?id=" . $id );
+        }
+    }
+
     if ( $request_path =~ m{^/Admin/Global/Scrips\.html} ) {
         my $tabs = PageMenu();
         $tabs->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" );

commit cd54f8b73f10f9793f2c50330c5b9e8f41e7d95c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 20 22:20:57 2012 +0400

    Move to a template picker that accounts for multiple queues

diff --git a/share/html/Admin/Scrips/Elements/EditBasics b/share/html/Admin/Scrips/Elements/EditBasics
index d7746b2..94233ee 100644
--- a/share/html/Admin/Scrips/Elements/EditBasics
+++ b/share/html/Admin/Scrips/Elements/EditBasics
@@ -62,12 +62,13 @@
 &></td></tr>
 
 <tr><td class="label"><&|/l&>Template</&>:</td><td class="value">\
-<& /Admin/Elements/SelectTemplate,
-    Default => $ARGS{"Template"} || $Scrip->TemplateObj->Id,
+<& SelectTemplate,
+    Default => $ARGS{"Template"}, Scrip => $Scrip, Queue => $Queue,
 &></td></tr>
 
 <%ARGS>
 $Scrip
+$Queue => undef
 </%ARGS>
 <%INIT>
 </%INIT>
diff --git a/share/html/Admin/Elements/SelectTemplate b/share/html/Admin/Scrips/Elements/SelectTemplate
similarity index 64%
rename from share/html/Admin/Elements/SelectTemplate
rename to share/html/Admin/Scrips/Elements/SelectTemplate
index 8d45c7d..3bcb212 100644
--- a/share/html/Admin/Elements/SelectTemplate
+++ b/share/html/Admin/Scrips/Elements/SelectTemplate
@@ -45,33 +45,52 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<select name="<%$Name%>">
-<option value="" 
-<% defined($Default) && $Default eq 'none' && qq[ selected="selected"] |n %>>-</option>
-%while  (my $Template = $PrimaryTemplates->Next) {
-<option value="<%$Template->Id%>" <% ($Template->Id == $Default) && qq[ selected="selected"] |n %>><% loc($Template->Name) %></option>
-%}
-%while  (my $Template = $OtherTemplates->Next) {
-<option value="<%$Template->Id%>" <% defined($Default) && ($Template->Id == $Default) && qq[ selected="selected"] |n %>><&|/l, loc($Template->Name) &>Global template: [_1]</&></option>
-%}
+<select name="<% $Name %>">
+<option value=""><% $current || '-' %></option>
+% foreach my $name ( @list ) {
+<option value="<% $name %>" \
+    <% lc($Default||'') eq lc $name ? 'selected="selected"' : '' |n %>
+><% loc($name) %></option>
+% }
 </select>
 
+<%ARGS>
+$Name => 'Template'
+$Queue => undef
+$Scrip => undef
+$Default => undef
+</%ARGS>
 <%INIT>
 
+my $current;
+$current = $Scrip->TemplateObj->Name if $Scrip;
 
-my $PrimaryTemplates = RT::Templates->new($session{'CurrentUser'});
-if ($Queue != 0) {
-$PrimaryTemplates->LimitToQueue($Queue);
-$PrimaryTemplates->OrderBy(FIELD => 'Name');
-}
+my $global = RT::Templates->new($session{'CurrentUser'});
+$global->LimitToGlobal;
+
+my %global;
+$global{ lc $_ } = $_ foreach map $_->Name, @{ $global->ItemsArrayRef };
 
-my $OtherTemplates = RT::Templates->new($session{'CurrentUser'});
-$OtherTemplates->LimitToGlobal;
-$OtherTemplates->OrderBy(FIELD => 'Name');
+my @queues;
+push @queues, @{ $Scrip->AddedTo->ItemsArrayRef } if $Scrip && $Scrip->id;
+push @queues, $Queue if $Queue && $Queue->id;
 
+my (%names, %counters);
+foreach my $queue ( @queues ) {
+    my $templates = RT::Templates->new($session{'CurrentUser'});
+    $templates->LimitToQueue( $queue->id );
+    foreach my $name ( map $_->Name, @{ $templates->ItemsArrayRef } ) {
+        next if $global{ lc $name };
+        $counters{ lc $name }++;
+        $names{lc $name} = $name;
+    }
+}
+delete $counters{ $_ }
+    foreach grep $counters{$_} != @queues,
+    keys %counters;
+
+my @list =
+    sort { lc loc($a) cmp lc loc($b) }
+    map $global{$_} || $names{$_},
+    keys %global, keys %counters;
 </%INIT>
-<%ARGS>
-$Queue => undef
-$Default => 'none'
-$Name => 'Template'
-</%ARGS>
diff --git a/share/html/Admin/Scrips/Modify.html b/share/html/Admin/Scrips/Modify.html
index 6cb2239..27a9cd5 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Modify.html
@@ -108,9 +108,10 @@ my $disabled = $scrip->Disabled;
 if ( $Update ) {
     my @attribs = qw(
         Description
-        ScripAction ScripCondition Template
+        ScripAction ScripCondition
         CustomPrepareCode CustomCommitCode CustomIsApplicableCode
     );
+    push @attribs, "Template" if defined $ARGS{Template} and length $ARGS{Template};
     if ($ARGS{"SetEnabled"}) {
         push @attribs, "Disabled";
         $ARGS{"Disabled"} = not $ARGS{"Enabled"};

commit 501d0a48c7bc1052d1ef306393767b9bd72e6262
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Fri Sep 30 11:47:14 2011 -0400

    Rename this to be less confusing
    
    Internally, this is commit.  We always explain it as commit.
    It actually runs during the commit, not as a 'cleanup' stage
    (TransactionBatch is actually much closer to being a 'cleanup' stage)

diff --git a/share/html/Admin/Scrips/Elements/EditCustomCode b/share/html/Admin/Scrips/Elements/EditCustomCode
index 775dd51..ecafa23 100644
--- a/share/html/Admin/Scrips/Elements/EditCustomCode
+++ b/share/html/Admin/Scrips/Elements/EditCustomCode
@@ -70,7 +70,7 @@ $Scrip
 my @list = (
     CustomIsApplicableCode => loc('Custom condition'),
     CustomPrepareCode      => loc('Custom action preparation code'),
-    CustomCommitCode       => loc('Custom action cleanup code'),
+    CustomCommitCode       => loc('Custom action commit code'),
 );
 
 my $min_lines = 10;

commit b426c7ff90cb83fd439134fa9ce9a18fd4bef5b3
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 24 20:28:57 2012 +0400

    test basics of administrating scrips

diff --git a/lib/RT/Test/Web.pm b/lib/RT/Test/Web.pm
index 8fae537..062626e 100644
--- a/lib/RT/Test/Web.pm
+++ b/lib/RT/Test/Web.pm
@@ -355,6 +355,24 @@ sub custom_field_input {
     return $res;
 }
 
+sub value_name {
+    my $self = shift;
+    my $field = shift;
+
+    my $input = $self->current_form->find_input( $field )
+        or return undef;
+
+    my @names = $input->value_names;
+    return $input->value unless @names;
+
+    my @values = $input->possible_values;
+    for ( my $i = 0; $i < @values; $i++ ) {
+        return $names[ $i ] if $values[ $i ] eq $input->value;
+    }
+    return undef;
+}
+
+
 sub check_links {
     my $self = shift;
     my %args = @_;
diff --git a/share/html/Admin/Scrips/Objects.html b/share/html/Admin/Scrips/Objects.html
index c4a1011..c348e78 100644
--- a/share/html/Admin/Scrips/Objects.html
+++ b/share/html/Admin/Scrips/Objects.html
@@ -49,7 +49,7 @@
 <& /Elements/Tabs &>
 <& /Elements/ListActions &>
 
-<form action="Objects.html" method="post">
+<form action="Objects.html" method="post" name="AddRemoveScrip">
 <input type="hidden" class="hidden" name="id" value="<% $id %>" />
 
 % if ( $is_global ) {
diff --git a/t/web/scrips.t b/t/web/scrips.t
index 855029a..edcfed4 100644
--- a/t/web/scrips.t
+++ b/t/web/scrips.t
@@ -1,7 +1,9 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 14;
+use RT::Test tests => 54;
+
+RT->Config->Set( UseTransactionBatch => 1 );
 
 # TODO:
 # Test the rest of the conditions.
@@ -101,3 +103,85 @@ sub prepare_code_with_value {
 
     RT::Test->clean_caught_mails;
 }
+
+note "check basics in scrip's admin interface";
+{
+    $m->follow_link_ok( { id => 'tools-config-global-scrips-create' } );
+    ok $m->form_name('CreateScrip');
+    is $m->value_name('Description'), '', 'empty value';
+    is $m->value_name('ScripAction'), '-', 'empty value';
+    is $m->value_name('ScripCondition'), '-', 'empty value';
+    is $m->value_name('Template'), '-', 'empty value';
+    $m->field('Description' => 'test');
+    $m->click('Create');
+    $m->content_contains("Action is mandatory argument");
+
+    ok $m->form_name('CreateScrip');
+    is $m->value_name('Description'), 'test', 'value stays on the page';
+    $m->select('ScripAction' => 'Notify Ccs');
+    $m->click('Create');
+    $m->content_contains("Template is mandatory argument");
+
+    ok $m->form_name('CreateScrip');
+    is $m->value_name('Description'), 'test', 'value stays on the page';
+    is $m->value_name('ScripAction'), 'Notify Ccs', 'value stays on the page';
+    $m->select('Template' => 'Blank');
+    $m->click('Create');
+    $m->content_contains("Condition is mandatory argument");
+
+    ok $m->form_name('CreateScrip');
+    is $m->value_name('Description'), 'test', 'value stays on the page';
+    is $m->value_name('ScripAction'), 'Notify Ccs', 'value stays on the page';
+    $m->select('ScripCondition' => 'On Close');
+    $m->click('Create');
+    $m->content_contains("Scrip Created");
+
+    ok $m->form_name('ModifyScrip');
+    is $m->value_name('Description'), 'test', 'correct value';
+    is $m->value_name('ScripCondition'), 'On Close', 'correct value';
+    is $m->value_name('ScripAction'), 'Notify Ccs', 'correct value';
+    is $m->value_name('Template'), 'Blank', 'correct value';
+    $m->field('Description' => 'test test');
+    $m->click('Update');
+    # regression
+    $m->content_lacks("Template is mandatory argument");
+
+    ok $m->form_name('ModifyScrip');
+    is $m->value_name('Description'), 'test test', 'correct value';
+    $m->content_contains("Description changed from", "found action result message");
+}
+
+note "apply scrip in different stage to different queues";
+{
+    my $queue = RT::Test->load_or_create_queue( Name => 'Regression' );
+    ok $queue && $queue->id, 'loaded or created queue';
+
+    $m->follow_link_ok( { id => 'tools-config-queues' } );
+    $m->follow_link_ok( { text => 'General' } );
+    $m->follow_link_ok( { id => 'page-scrips-create'});
+
+    ok $m->form_name('CreateScrip');
+    $m->field('Description' => 'test stage');
+    $m->select('ScripCondition' => 'On Close');
+    $m->select('ScripAction' => 'Notify Ccs');
+    $m->select('Template' => 'Blank');
+    $m->click('Create');
+    $m->content_contains("Scrip Created");
+
+    my ($sid) = ($m->content =~ /Modify scrip #(\d+)/);
+    ok $sid, "found scrip id on the page";
+
+    $m->follow_link_ok({ text => 'Applies to' });
+    ok $m->form_name('AddRemoveScrip');
+    $m->select('Stage' => 'Batch');
+    $m->tick( "AddScrip-$sid" => $queue->id );
+    $m->click('Update');
+    $m->content_contains("Object created");
+
+    $m->follow_link_ok({ text => 'General' });
+    $m->follow_link_ok({ id => 'page-scrips' });
+
+    my (@matches) = $m->content =~ /test stage/g;
+    # regression
+    is scalar @matches, 1, 'scrip mentioned only once';
+}

commit e397130db8e9298da386e9a459721c4ff60196b6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 4 00:31:57 2012 +0400

    make sure disabled scrips are not executed

diff --git a/t/api/scrip.t b/t/api/scrip.t
index e409a30..4cad73a 100644
--- a/t/api/scrip.t
+++ b/t/api/scrip.t
@@ -1,7 +1,7 @@
 
 use strict;
 use warnings;
-use RT::Test tests => 61;
+use RT::Test tests => 68;
 
 my $queue = RT::Test->load_or_create_queue( Name => 'General' );
 ok $queue && $queue->id, 'loaded or created queue';
@@ -169,3 +169,41 @@ note 'check applications vs. templates';
     ok($status, 'added scrip');
     RT::Test->object_scrips_are($scrip, [$queue, $queue_B], [0]);
 }
+
+note 'basic check for disabling scrips';
+{
+    my $scrip = RT::Scrip->new(RT->SystemUser);
+    my ($status, $msg) = $scrip->Create(
+        Queue => $queue->id,
+        ScripCondition => 'On Create',
+        ScripAction => 'User Defined',
+        CustomPrepareCode => 'return 1',
+        CustomCommitCode => '$self->TicketObj->SetPriority("87"); return 1',
+        Template => 'Blank'
+    );
+    ok($status, "created scrip");
+    is($scrip->Disabled, 0, "not disabled");
+
+    {
+        my $ticket = RT::Ticket->new(RT->SystemUser);
+        my ($tid, undef, $msg) = $ticket->Create(
+            Queue => $queue->id,
+            Subject => "test",
+        );
+        ok($tid, "created ticket") or diag "error: $msg";
+        is ($ticket->Priority , '87', "Ticket priority is set right");
+    }
+
+    ($status,$msg) = $scrip->SetDisabled(1);
+    is($scrip->Disabled, 1, "disabled");
+
+    {
+        my $ticket = RT::Ticket->new(RT->SystemUser);
+        my ($tid, undef, $msg) = $ticket->Create(
+            Queue => $queue->id,
+            Subject => "test",
+        );
+        ok($tid, "created ticket") or diag "error: $msg";
+        isnt ($ticket->Priority , '87', "Ticket priority is set right");
+    }
+}

commit 91cfdc4d92fd2ead39275b9de960221ce59589ef
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 31 00:45:05 2012 +0400

    Upgrade scripts for new schema

diff --git a/etc/upgrade/4.1.1/acl.Pg b/etc/upgrade/4.1.1/acl.Pg
new file mode 100755
index 0000000..9e8fc0a
--- /dev/null
+++ b/etc/upgrade/4.1.1/acl.Pg
@@ -0,0 +1,31 @@
+
+sub acl {
+    my $dbh = shift;
+
+    my @acls;
+
+    my @tables = qw (
+        objectscrips_id_seq
+        ObjectScrips
+    );
+
+    my $db_user = RT->Config->Get('DatabaseUser');
+
+    my $sequence_right
+        = ( $dbh->{pg_server_version} >= 80200 )
+        ? "USAGE, SELECT, UPDATE"
+        : "SELECT, UPDATE";
+
+    foreach my $table (@tables) {
+        # Tables are upper-case, sequences are lowercase in @tables
+        if ( $table =~ /^[a-z]/ ) {
+            push @acls, "GRANT $sequence_right ON $table TO \"$db_user\";"
+        }
+        else {
+            push @acls, "GRANT SELECT, INSERT, UPDATE, DELETE ON $table TO \"$db_user\";"
+        }
+    }
+    return (@acls);
+}
+
+1;
diff --git a/etc/upgrade/4.1.1/content b/etc/upgrade/4.1.1/content
new file mode 100644
index 0000000..02d1321
--- /dev/null
+++ b/etc/upgrade/4.1.1/content
@@ -0,0 +1,35 @@
+use strict; use warnings;
+
+our @Initial = (
+    sub {
+        require RT::ObjectScrips;
+        foreach my $stage ('TransactionCreate', 'TransactionBatch') {
+            my $applications = RT::ObjectScrips->new( RT->SystemUser );
+            $applications->Limit( FIELD => 'Stage', VALUE => $stage );
+            my $alias = $applications->Join(
+                FIELD1 => 'Scrip',
+                TABLE2 => 'Scrips', FIELD2 => 'id'
+            );
+            $applications->OrderByCols(
+                { ALIAS => $alias, FIELD => 'Description', ORDER => 'ASC' },
+            );
+            my %h; my $top_so = $h{0} = 0;
+            while ( my $record = $applications->Next ) {
+                my $oid = $record->ObjectId || 0;
+
+                my $so;
+                unless ( $oid ) {
+                    %h = (); $h{0} = $so = ++$top_so;
+                }
+                else {
+                    $so = $h{ $oid } = ($h{$oid}||$h{0}) + 1;
+                }
+                next if $record->SortOrder == $so;
+
+                my ($status, $msg) = $record->SetSortOrder($so);
+                RT->Logger->error("Couldn't set sort order: $msg")
+                    unless $status;
+            }
+        }
+    },
+);
diff --git a/etc/upgrade/4.1.1/schema.Oracle b/etc/upgrade/4.1.1/schema.Oracle
new file mode 100644
index 0000000..576c0f1
--- /dev/null
+++ b/etc/upgrade/4.1.1/schema.Oracle
@@ -0,0 +1,31 @@
+CREATE SEQUENCE OBJECTSCRIPS_seq;
+CREATE TABLE ObjectScrips (
+	id		NUMBER(11,0)
+                 CONSTRAINT ObjectScrips_Key PRIMARY KEY,
+        Scrip       NUMBER(11,0)  NOT NULL,
+	Stage		VARCHAR2(32) DEFAULT 'TransactionCreate' NOT NULL,
+        ObjectId              NUMBER(11,0)  NOT NULL,
+	SortOrder	NUMBER(11,0) DEFAULT 0 NOT NULL,
+	Disabled	NUMBER(11,0) DEFAULT 0 NOT NULL,
+	Creator		NUMBER(11,0) DEFAULT 0 NOT NULL,
+	Created		DATE,
+	LastUpdatedBy	NUMBER(11,0) DEFAULT 0 NOT NULL,
+	LastUpdated	DATE
+);
+ALTER TABLE Scrips ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
+
+INSERT INTO ObjectScrips(
+    id, Scrip, Stage, ObjectId,
+    Creator, Created, LastUpdatedBy, LastUpdated
+)
+(SELECT OBJECTSCRIPS_seq.nextval, id, Stage, Queue, Creator, Created, LastUpdatedBy, LastUpdated
+FROM Scrips)
+;
+
+UPDATE Scrips SET Disabled = 1 WHERE Stage = 'Disabled';
+UPDATE ObjectScrips SET Stage = 'TransactionCreate' WHERE Stage = 'Disabled';
+
+CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
+
+ALTER TABLE Scrips DROP COLUMN Stage;
+ALTER TABLE Scrips DROP COLUMN Queue;
diff --git a/etc/upgrade/4.1.1/schema.Pg b/etc/upgrade/4.1.1/schema.Pg
new file mode 100644
index 0000000..9716c5f
--- /dev/null
+++ b/etc/upgrade/4.1.1/schema.Pg
@@ -0,0 +1,34 @@
+CREATE SEQUENCE objectscrips_id_seq;
+
+CREATE TABLE ObjectScrips (
+  id INTEGER DEFAULT nextval('objectscrips_id_seq'),
+  Scrip integer NOT NULL,
+  Stage varchar(32) NOT NULL DEFAULT 'TransactionCreate' ,
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+  Disabled integer NOT NULL DEFAULT 0 ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created TIMESTAMP NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated TIMESTAMP NULL  ,
+  PRIMARY KEY (id)
+
+);
+ALTER TABLE Scrips ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
+
+INSERT INTO ObjectScrips(
+    Scrip, Stage, ObjectId,
+    Creator, Created, LastUpdatedBy, LastUpdated
+)
+SELECT id, Stage, Queue, Creator, Created, LastUpdatedBy, LastUpdated
+FROM Scrips
+;
+
+UPDATE Scrips SET Disabled = 1 WHERE Stage = 'Disabled';
+UPDATE ObjectScrips SET Stage = 'TransactionCreate' WHERE Stage = 'Disabled';
+
+CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
+
+ALTER TABLE Scrips DROP COLUMN Stage;
+ALTER TABLE Scrips DROP COLUMN Queue;
diff --git a/etc/upgrade/4.1.1/schema.SQLite b/etc/upgrade/4.1.1/schema.SQLite
new file mode 100644
index 0000000..24e0933
--- /dev/null
+++ b/etc/upgrade/4.1.1/schema.SQLite
@@ -0,0 +1,32 @@
+
+CREATE TABLE ObjectScrips (
+  id INTEGER NOT NULL  ,
+  Scrip int NOT NULL  ,
+  Stage varchar(32) NOT NULL DEFAULT 'TransactionCreate' ,
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+);
+ALTER TABLE Scrips ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
+
+INSERT INTO ObjectScrips(
+    Scrip, Stage, ObjectId,
+    Creator, Created, LastUpdatedBy, LastUpdated
+)
+SELECT id, Stage, Queue, Creator, Created, LastUpdatedBy, LastUpdated
+FROM Scrips
+;
+
+UPDATE Scrips SET Disabled = 1 WHERE Stage = 'Disabled';
+UPDATE ObjectScrips SET Stage = 'TransactionCreate' WHERE Stage = 'Disabled';
+
+CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
+
+# TODO: ALTER TABLE Scrips DROP COLUMN Stage;
+# TODO: ALTER TABLE Scrips DROP COLUMN Queue;
diff --git a/etc/upgrade/4.1.1/schema.mysql b/etc/upgrade/4.1.1/schema.mysql
new file mode 100644
index 0000000..2656c63
--- /dev/null
+++ b/etc/upgrade/4.1.1/schema.mysql
@@ -0,0 +1,31 @@
+CREATE TABLE ObjectScrips (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Scrip integer NOT NULL  ,
+  Stage varchar(32) CHARACTER SET ascii NOT NULL DEFAULT 'TransactionCreate',
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) ENGINE=InnoDB CHARACTER SET utf8;
+ALTER TABLE Scrips ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
+
+INSERT INTO ObjectScrips(
+    Scrip, Stage, ObjectId,
+    Creator, Created, LastUpdatedBy, LastUpdated
+)
+SELECT id, Stage, Queue, Creator, Created, LastUpdatedBy, LastUpdated
+FROM Scrips
+;
+
+UPDATE Scrips SET Disabled = 1 WHERE Stage = 'Disabled';
+UPDATE ObjectScrips SET Stage = 'TransactionCreate' WHERE Stage = 'Disabled';
+
+CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
+
+ALTER TABLE Scrips DROP COLUMN Stage;
+ALTER TABLE Scrips DROP COLUMN Queue;

commit c391eaf33dc9d2cf024e9233b77005e7f2213220
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Sep 6 01:18:50 2012 +0400

    apply scrip to multiple queues from initialdata
    
    old style was to duplicate scrip in each queue, but
    now we create records in ObjectScrips

diff --git a/lib/RT/Handle.pm b/lib/RT/Handle.pm
index 6e9edd5..58934da 100644
--- a/lib/RT/Handle.pm
+++ b/lib/RT/Handle.pm
@@ -1062,14 +1062,21 @@ sub InsertData {
             my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
             push @queues, 0 unless @queues; # add global queue at least
 
+            my ( $return, $msg ) = $new_entry->Create( %$item, Queue => shift @queues );
+            unless ( $return ) {
+                $RT::Logger->error( $msg );
+                next;
+            }
+            else {
+                $RT::Logger->debug( $return ."." );
+            }
             foreach my $q ( @queues ) {
-                my ( $return, $msg ) = $new_entry->Create( %$item, Queue => $q );
-                unless ( $return ) {
-                    $RT::Logger->error( $msg );
-                }
-                else {
-                    $RT::Logger->debug( $return ."." );
-                }
+                my ($return, $msg) = $new_entry->AddToObject(
+                    ObjectId => $q,
+                    Stage    => $item->{'Stage'},
+                );
+                $RT::Logger->error( "Couldn't apply scrip to $q: $msg" )
+                    unless $return;
             }
         }
         $RT::Logger->debug("done.");

commit 73afe942a19c9e4aa7ba13bc11e2bcb8aae42037
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Sep 7 04:21:20 2012 +0400

    Documentation for the new ApplyAndSort API

diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index 11757c4..1a8990f 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -55,7 +55,35 @@ use base 'RT::Record::AddAndSort';
 use RT::Scrip;
 use RT::ObjectScrips;
 
+=head1 NAME
+
+RT::ObjectScrip - record representing addition of a scrip to a queue
+
+=head1 DESCRIPTION
+
+This record is created if you want to add a scrip to a queue or globally.
+
+Inherits methods from L<RT::Record::AddAndSort>.
+
+For most operations it's better to use methods in L<RT::Scrip>.
+
+=head1 METHODS
+
+=head2 Table
+
+Returns table name for records of this class.
+
+=cut
+
 sub Table {'ObjectScrips'}
+
+=head2 ObjectCollectionClass
+
+Returns class name of collection of records scrips can be added to.
+Now it's only L<RT::Queue>, so 'RT::Queues' is returned.
+
+=cut
+
 sub ObjectCollectionClass {'RT::Queues'}
 
 =head2 ScripObj
@@ -72,6 +100,12 @@ sub ScripObj {
     return $obj;
 }
 
+=head2 Neighbors
+
+Stage splits scrips into neighborhoods. See L<RT::Record::AddAndSort/Neighbors and Siblings>.
+
+=cut
+
 sub Neighbors {
     my $self = shift;
     my %args = @_;
diff --git a/lib/RT/ObjectScrips.pm b/lib/RT/ObjectScrips.pm
index 0843219..4970743 100644
--- a/lib/RT/ObjectScrips.pm
+++ b/lib/RT/ObjectScrips.pm
@@ -55,8 +55,32 @@ use base 'RT::SearchBuilder::AddAndSort';
 use RT::Scrips;
 use RT::ObjectScrip;
 
+=head1 NAME
+
+RT::ObjectScrips - collection of RT::ObjectScrip records
+
+=head1 DESCRIPTION
+
+Collection of L<RT::ObjectScrip> records. Inherits methods from L<RT::SearchBuilder::AddAndSort>.
+
+=head1 METHODS
+
+=cut
+
+=head2 Table
+
+Returns name of the table where records are stored.
+
+=cut
+
 sub Table { 'ObjectScrips'}
 
+=head2 LimitToScrip
+
+Takes id of a L<RT::Scrip> object and limits this collection.
+
+=cut
+
 sub LimitToScrip {
     my $self = shift;
     my $id = shift;
diff --git a/lib/RT/Record/AddAndSort.pm b/lib/RT/Record/AddAndSort.pm
index b02ab49..f547cf5 100644
--- a/lib/RT/Record/AddAndSort.pm
+++ b/lib/RT/Record/AddAndSort.pm
@@ -52,11 +52,42 @@ use warnings;
 package RT::Record::AddAndSort;
 use base 'RT::Record';
 
+=head1 NAME
+
+RT::Record::AddAndSort - base class for records that can be added and sorted
+
+=head1 DESCRIPTION
+
+Base class for L<RT::ObjectCustomField> and L<RT::ObjectScrip> that unifies
+application of L<RT::CustomField>s and L<RT::Scrip>s to various objects. Also,
+deals with order of the records.
+
+=head1 METHODS
+
+=head2 Meta information
+
+=head3 CollectionClass
+
+Returns class representing collection for this record class. Basicly adds 's'
+at the end. Should be overriden if default doesn't work.
+
+For example returns L<RT::ObjectCustomFields> when called on L<RT::ObjectCustomField>.
+
+=cut
+
 sub CollectionClass {
     return (ref($_[0]) || $_[0]).'s';
 }
 
-sub ObjectCollectionClass { die "should be subclassed" }
+=head3 TargetField
+
+Returns name of the field in the table where id of object we add is stored.
+By default deletes everything up to '::Object' from class name.
+This method allows to use friendlier argument names and methods.
+
+For example returns 'Scrip' for L<RT::ObjectScrip>.
+
+=cut
 
 sub TargetField {
     my $class = ref($_[0]) || $_[0];
@@ -64,6 +95,43 @@ sub TargetField {
     return $class;
 }
 
+=head3 ObjectCollectionClass
+
+Takes an object under L</TargetField> name and should return class
+name representing collection the object can be added to.
+
+Must be overriden by sub classes.
+
+
+See L<RT::ObjectScrip/ObjectCollectionClass> and L<RT::ObjectCustomField/CollectionClass>.
+
+=cut
+
+sub ObjectCollectionClass { die "should be subclassed" }
+
+=head2 Manipulation
+
+=head3 Create
+
+Takes 'ObjectId' with id of an object we can be added to, object we can
+add to under L</TargetField> name, Disabled and SortOrder.
+
+This method doesn't create duplicates. If record already exists then it's not created, but
+loaded instead. Note that nothing is updated if record exist.
+
+If SortOrder is not defined then it's calculated to place new record last. If it's
+provided then it's caller's duty to make sure it is correct value.
+
+Example:
+
+    my $ocf = RT::ObjectCustomField->new( RT->SystemUser );
+    my ($id, $msg) = $ocf->Create( CustomField => 1, ObjectId => 0 );
+
+See L</Add> which has more error checks. Also, L<RT::Scrip> and L<RT::CustomField>
+have more appropriate methods that B<should be> prefered over calling this directly.
+
+=cut
+
 sub Create {
     my $self = shift;
     my %args = (
@@ -100,6 +168,15 @@ sub Create {
     );
 }
 
+=head3 Add
+
+Helper method that wraps L</Create> and does more checks to make sure
+result is consistent. Doesn't allow adding a record to an object if the
+record is already global. Removes record from particular objects when
+asked to add the record globally.
+
+=cut
+
 sub Add {
     my $self = shift;
     my %args = (@_);
@@ -140,12 +217,15 @@ sub IsAdded {
     return $record->id;
 }
 
-=head1 AddedTo
+=head3 AddedTo
+
+Returns collection with objects target of this record is added to.
+Class of the collection depends on L</ObjectCollectionClass>.
+See all L</NotAddedTo>.
 
-Returns collection with objects this custom field is added to.  Class of
-the collection depends on L</LookupType>.  See all L</NotAddedTo> .
+For example returns L<RT::Queues> collection if the target is L<RT::Scrip>.
 
-Doesn't take into account if the object is added globally.
+Returns empty collection if target is added globally.
 
 =cut
 
@@ -165,12 +245,13 @@ sub AddedTo {
     return $res;
 }
 
-=head1 NotAddedTo
+=head3 NotAddedTo
 
-Returns collection with objects this custom field is not added to.
-Class of the collection depends on L</LookupType>.  See all L</AddedTo>.
+Returns collection with objects target of this record is not added to.
+Class of the collection depends on L</ObjectCollectionClass>.
+See all L</AddedTo>.
 
-Doesn't take into account if the object is added globally.
+Returns empty collection if target is added globally.
 
 =cut
 
@@ -222,6 +303,12 @@ sub _AddedTo {
     return ($res, $alias);
 }
 
+=head3 Delete
+
+Deletes this record.
+
+=cut
+
 sub Delete {
     my $self = shift;
 
@@ -238,6 +325,17 @@ sub Delete {
     return $self->SUPER::Delete;
 }
 
+=head3 DeleteAll
+
+Helper method to delete all applications for one target (Scrip, CustomField, ...).
+Target can be provided in arguments. If it's not then L</TargetObj> is used.
+
+    $object_scrip->DeleteAll;
+
+    $object_scrip->DeleteAll( Scrip => $scrip );
+
+=cut
+
 sub DeleteAll {
     my $self = shift;
     my %args = (@_);
@@ -255,7 +353,7 @@ sub DeleteAll {
 
 =head3 MoveUp
 
-Moves the object up.
+Moves record up.
 
 =cut
 
@@ -263,12 +361,18 @@ sub MoveUp { return shift->Move( Up => @_ ) }
 
 =head3 MoveDown
 
-Moves the object down.
+Moves record down.
 
 =cut
 
 sub MoveDown { return shift->Move( Down => @_ ) }
 
+=head3 Move
+
+Takes 'up' or 'down'. One method that implements L</MoveUp> and L</MoveDown>.
+
+=cut
+
 sub Move {
     my $self = shift;
     my $dir = lc(shift || 'up');
@@ -361,6 +465,31 @@ sub Move {
     return (1,"Moved");
 }
 
+=head2 Accessors, instrospection and traversing.
+
+=head3 TargetObj
+
+Returns target object of this record. Returns L<RT::Scrip> object for
+L<RT::ObjectScrip>.
+
+=cut
+
+sub TargetObj {
+    my $self = shift;
+    my $id   = shift;
+
+    my $method = $self->TargetField .'Obj';
+    return $self->$method( $id );
+}
+
+=head3 NextSortOrder
+
+Returns next available SortOrder value in the L<neighborhood|/Neighbors>.
+Pass arguments to L</Neighbors> and can take optional ObjectId argument,
+calls ObjectId if it's not provided.
+
+=cut
+
 sub NextSortOrder {
     my $self = shift;
     my %args = (@_);
@@ -381,6 +510,12 @@ sub NextSortOrder {
     return $first->SortOrder + 1;
 }
 
+=head3 IsSortOrderShared
+
+Returns true if this record shares SortOrder value with a L<neighbor|/Neighbors>.
+
+=cut
+
 sub IsSortOrderShared {
     my $self = shift;
     return 0 unless $self->ObjectId;
@@ -391,19 +526,82 @@ sub IsSortOrderShared {
     return $neighbors->Count;
 }
 
-sub TargetObj {
-    my $self = shift;
-    my $id   = shift;
+=head2 Neighbors and Siblings
 
-    my $method = $self->TargetField .'Obj';
-    return $self->$method( $id );
-}
+These two methods should only be understood by developers who wants
+to implement new classes of records that can be added to other records
+and sorted.
+
+Main purpose is to maintain SortOrder values.
+
+Let's take a look at custom fields. A custom field can be created for tickets,
+queues, transactions, users... Custom fields created for tickets can
+be added globally or to particular set of queues. Custom fields for
+tickets are neighbors. Neighbor custom fields added to the same objects
+are siblings. Custom fields added globally are sibling to all neighbors.
+
+For scrips Stage defines neighborhood.
+
+Let's look at the three scrips in create stage S1, S2 and S3, queues Q1 and Q2 and
+G for global.
+
+    S1 at Q1, S3 at Q2 0
+    S2 at G         1
+    S1 at Q2        2
+
+Above table says that S2 is added globally, S1 is added to Q1 and executed
+before S2 in this queue, also S1 is added to Q1, but exectued after S2 in this
+queue, S3 is only added to Q2 and executed before S2 and S1.
+
+Siblings are scrips added to an object including globally added or only
+globally added. In our example there are three different collection
+of siblings: (S2) - global, (S1, S2) for Q1, (S3, S2, S1) for Q2.
+
+Sort order can be shared between neighbors, but can not be shared between siblings.
+
+Here is what happens with sort order if we move S1 at Q2 one position up:
+
+           S3 at Q2 0
+    S1 at Q1, S1 at Q2 1
+    S2 at G         2
+
+One position more:
+
+           S1 at Q2 0
+    S1 at Q1, S3 at Q2 1
+    S2 at G         2
+
+Hopefuly it's enough to understand how it works.
+
+Targets from different neighborhood can not be sorted against each other.
+
+=head3 Neighbors
+
+Returns collection of records of this class with all
+neighbors. By default all possible targets are neighbors.
+
+Takes the same arguments as L</Create> method. If arguments are not passed
+then uses the current record.
+
+See L</Neighbors and Siblings> for detailed description.
+
+See L<RT::ObjectCustomField/Neighbors> for example.
+
+=cut
 
 sub Neighbors {
     my $self = shift;
     return $self->CollectionClass->new( $self->CurrentUser );
 }
 
+=head3 Siblings
+
+Returns collection of records of this class with siblings.
+
+Takes the same arguments as L</Neighbors>. Siblings is subset of L</Neighbors>.
+
+=cut
+
 sub Siblings {
     my $self = shift;
     my %args = @_;
diff --git a/lib/RT/SearchBuilder/AddAndSort.pm b/lib/RT/SearchBuilder/AddAndSort.pm
index 7397f4f..2334798 100644
--- a/lib/RT/SearchBuilder/AddAndSort.pm
+++ b/lib/RT/SearchBuilder/AddAndSort.pm
@@ -52,11 +52,21 @@ use warnings;
 package RT::SearchBuilder::AddAndSort;
 use base 'RT::SearchBuilder';
 
-sub RecordClass {
-    my $class = ref($_[0]) || $_[0];
-    $class =~ s/s$// or return undef;
-    return $class;
-}
+=head1 NAME
+
+RT::SearchBuilder::AddAndSort - base class for 'add and sort' collections
+
+=head1 DESCRIPTION
+
+Base class for collections where records can be added to objects with order.
+See also L<RT::Record::AddAndSort>. Used by L<RT::ObjectScrips> and
+L<RT::ObjectCustomFields>.
+
+As it's about sorting then collection is sorted by SortOrder field.
+
+=head1 METHODS
+
+=cut
 
 sub _Init {
     my $self = shift;
@@ -74,17 +84,53 @@ sub _Init {
     return $self->SUPER::_Init(@_);
 }
 
+=head2 RecordClass
+
+Returns class name of records in this collection. This generic implementation
+just strips trailing 's'.
+
+=cut
+
+sub RecordClass {
+    my $class = ref($_[0]) || $_[0];
+    $class =~ s/s$// or return undef;
+    return $class;
+}
+
+=head2 LimitToObjectId
+
+Takes id of an object and limits collection.
+
+=cut
+
 sub LimitToObjectId {
     my $self = shift;
     my $id = shift || 0;
     $self->Limit( FIELD => 'ObjectId', VALUE => $id );
 }
 
+=head2 NewItem
+
+Returns an empty new collection's item
+
+=cut
+
+sub NewItem {
+    my $self = shift;
+    return $self->RecordClass->new( $self->CurrentUser );
+}
+
+=head1 METHODS FOR TARGETS
+
+Rather than implementing a base class for targets (L<RT::Scrip>,
+L<RT::CustomField>) and its collections. This class provides
+class methods to limit target collections.
+
 =head2 LimitTargetToNotAdded
 
-Takes either list of object ids or nothing. Limits collection
-to custom fields to listed objects or any corespondingly. Use
-zero to mean global.
+Takes a collection object and optional list of object ids. Limits the
+collection to records not added to listed objects or if the list is
+empty then any object. Use 0 (zero) to mean global.
 
 =cut
 
@@ -107,8 +153,8 @@ sub LimitTargetToNotAdded {
 
 =head2 LimitTargetToAdded
 
-Limits collection to custom fields to listed objects or any corespondingly. Use
-zero to mean global.
+L</LimitTargetToNotAdded> with reverse meaning. Takes the same
+arguments.
 
 =cut
 
@@ -129,6 +175,16 @@ sub LimitTargetToAdded {
     return $alias;
 }
 
+=head2 JoinTargetToAdded
+
+Joins collection to this table using left join, limits joined table
+by ids if those are provided.
+
+Returns alias of the joined table. Join is cached and re-used for
+multiple calls.
+
+=cut
+
 sub JoinTargetToAdded {
     my $self = shift;
     my $collection = shift;
@@ -152,6 +208,15 @@ sub JoinTargetToAdded {
     return $alias;
 }
 
+=head2 JoinTargetToThis
+
+Joins target collection to this table using TargetField.
+
+Takes New and Left arguments. Use New to avoid caching and re-using
+this join. Use Left to create LEFT JOIN rather than inner.
+
+=cut
+
 sub JoinTargetToThis {
     my $self = shift;
     my $collection = shift;
@@ -173,17 +238,6 @@ sub JoinTargetToThis {
     return $collection->{ $key } = $alias;
 }
 
-=head2 NewItem
-
-Returns an empty new collection's item
-
-=cut
-
-sub NewItem {
-    my $self = shift;
-    return $self->RecordClass->new( $self->CurrentUser );
-}
-
 RT::Base->_ImportOverlays();
 
 1;

commit 4c369341fba90c2e073c381098b6fc97f722dfbc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 8 02:37:04 2012 +0400

    Scrip and CF ->IsAddedToAny methods

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index a5547c4..a0eccb9 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1303,6 +1303,20 @@ sub IsAdded {
 
 sub IsGlobal { return shift->IsAdded(0) }
 
+=head2 IsAddedToAny
+
+Returns true if custom field is applied to any object.
+
+=cut
+
+sub IsAddedToAny {
+    my $self = shift;
+    my $id = shift;
+    my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
+    $ocf->LoadByCols( CustomField => $self->id );
+    return $ocf->id ? 1 : 0;
+}
+
 =head2 AddToObject OBJECT
 
 Add this custom field as a custom field for a single object, such as a queue or group.
diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 9f883a2..fb78a72 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -231,6 +231,13 @@ sub IsAdded {
     return $record;
 }
 
+sub IsAddedToAny {
+    my $self = shift;
+    my $record = RT::ObjectScrip->new( $self->CurrentUser );
+    $record->LoadByCols( Scrip => $self->id );
+    return $record->id ? 1 : 0;
+}
+
 sub AddedTo {
     my $self = shift;
     return RT::ObjectScrip->new( $self->CurrentUser )

commit 66d326337f2928e6d84676c872e669f40ade893a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Sep 12 19:32:11 2012 +0400

    Force CF "Disabled" flag into a 0/1 boolean
    
    undef does not cause the database to pick up the default value during
    Create.

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index a0eccb9..8648078 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -374,6 +374,8 @@ sub Create {
         }
     }
 
+    $args{'Disabled'} ||= 0;
+
     (my $rv, $msg) = $self->SUPER::Create(
         Name        => $args{'Name'},
         Type        => $args{'Type'},
diff --git a/share/html/Admin/CustomFields/Modify.html b/share/html/Admin/CustomFields/Modify.html
index 3c8efbb..fcaf20e 100644
--- a/share/html/Admin/CustomFields/Modify.html
+++ b/share/html/Admin/CustomFields/Modify.html
@@ -173,7 +173,7 @@ else {
             LinkValueTo   => $LinkValueTo,
             IncludeContentForValue => $IncludeContentForValue,
             BasedOn       => $BasedOn,
-            Disabled      => !$Enabled,
+            Disabled      => ($Enabled ? 0 : 1),
         );
         if (!$val) {
             push @results, loc("Could not create CustomField: [_1]", $msg);

commit f186024d6c23ad8033b22263e82c450d04a4973e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 22 01:08:44 2012 +0400

    Add deprecated Apply* methods to CustomFields for back-compat

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 8648078..158ca7e 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1253,6 +1253,7 @@ sub IsOnlyGlobal {
     return ($self->LookupType =~ /^RT::(?:Group|User)/io);
 
 }
+sub ApplyGlobally { Carp::carp("DEPRECATED, use IsOnlyGlobal"); return shift->IsOnlyGlobal(@_) }
 
 =head1 AddedTo
 
@@ -1269,6 +1270,7 @@ sub AddedTo {
     return RT::ObjectCustomField->new( $self->CurrentUser )
         ->AddedTo( CustomField => $self );
 }
+sub AppliedTo { Carp::carp("DEPRECATED: use AddedTo"); shift->AddedTo(@_) };
 
 =head1 NotAddedTo
 
@@ -1285,6 +1287,7 @@ sub NotAddedTo {
     return RT::ObjectCustomField->new( $self->CurrentUser )
         ->NotAddedTo( CustomField => $self );
 }
+sub NotAppliedTo { Carp::carp("DEPRECATED: use NotAddedTo"); shift->NotAddedTo(@_) };
 
 =head2 IsAdded
 
@@ -1302,6 +1305,7 @@ sub IsAdded {
     return undef unless $ocf->id;
     return $ocf;
 }
+sub IsApplied { Carp::carp("DEPRECATED: use IsAdded"); shift->IsAdded(@_) };
 
 sub IsGlobal { return shift->IsAdded(0) }
 

commit 70693937fdb52f84496f905bba81ba1518426485
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Nov 5 16:35:37 2012 -0800

    Tests: Ensure scrips can't be applied both globally and to individual queues from the UI

diff --git a/t/web/scrips.t b/t/web/scrips.t
index edcfed4..cf12427 100644
--- a/t/web/scrips.t
+++ b/t/web/scrips.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 54;
+use RT::Test tests => undef;
 
 RT->Config->Set( UseTransactionBatch => 1 );
 
@@ -151,6 +151,40 @@ note "check basics in scrip's admin interface";
     $m->content_contains("Description changed from", "found action result message");
 }
 
+note "check application in admin interface";
+{
+    $m->follow_link_ok({ id => 'tools-config-global-scrips-create' });
+    $m->submit_form_ok({
+        with_fields => {
+            Description     => "testing application",
+            ScripCondition  => "On Create",
+            ScripAction     => "Open Tickets",
+            Template        => "Blank",
+        },
+        button => 'Create',
+    }, "created scrip");
+    $m->content_contains("Scrip Created", "found result message");
+
+    my ($sid) = ($m->content =~ /Modify scrip #(\d+)/);
+    ok $sid, "found scrip id on the page";
+    RT::Test->object_scrips_are($sid, [0]);
+
+    $m->follow_link_ok({ id => 'page-applies-to' });
+    ok $m->form_name("AddRemoveScrip"), "found form";
+    $m->tick("RemoveScrip-$sid", 0);
+    $m->click_ok("Update", "update scrip application");
+    RT::Test->object_scrips_are($sid, []);
+
+    my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+    ok $queue && $queue->id, "loaded queue";
+
+    ok $m->form_name("AddRemoveScrip"), "found form";
+    $m->tick("AddScrip-$sid", 0);
+    $m->tick("AddScrip-$sid", $queue->id);
+    $m->click_ok("Update", "update scrip application");
+    RT::Test->object_scrips_are($sid, [0], [$queue->id]);
+}
+
 note "apply scrip in different stage to different queues";
 {
     my $queue = RT::Test->load_or_create_queue( Name => 'Regression' );
@@ -185,3 +219,6 @@ note "apply scrip in different stage to different queues";
     # regression
     is scalar @matches, 1, 'scrip mentioned only once';
 }
+
+undef $m;
+done_testing;

commit 4444e6c3a0c359ce902fd3536e43d60f6361f4d7
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Nov 5 17:01:44 2012 -0800

    Disable selection of individual objects when "apply globally" is checked
    
    Unapplied scrips and CFs show both an "apply globally" checkbox as well
    as collection lists of available individual objects.  If "apply
    globally" is checked, prevent checking individual objects as well
    before the page is submitted.  The API already prevents both changes
    from going through, so this is simply a bit of icing on the admin
    interface.

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 5c5bcee..07923ac 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -874,6 +874,7 @@ Set(@JSFiles, qw/
     supersubs.js
     jquery.supposition.js
     history-folding.js
+    event-registration.js
     late.js
 /);
 
diff --git a/share/html/NoAuth/js/event-registration.js b/share/html/NoAuth/js/event-registration.js
new file mode 100644
index 0000000..bbeeec8
--- /dev/null
+++ b/share/html/NoAuth/js/event-registration.js
@@ -0,0 +1,62 @@
+%# 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 }}}
+jQuery(function() {
+    var global_checkboxes = [
+        "form[name=AddRemoveScrip] input[type=checkbox][name^=AddScrip-][value=0]",
+        "form input[type=checkbox][name^=AddCustomField-][value=0]"
+    ];
+    jQuery(global_checkboxes.join(", "))
+        .change(function(){
+            var self    = jQuery(this);
+            var checked = self.attr("checked");
+
+            self.closest("form")
+                .find("table.collection input[type=checkbox]")
+                .attr("disabled", checked ? "disabled" : "");
+        });
+});

commit 305317197a96daf9b3e9223146736d3b75293050
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Nov 5 22:14:35 2012 -0800

    Turn the apply globally checkbox labels into real clickable <label>s

diff --git a/share/html/Admin/Scrips/Objects.html b/share/html/Admin/Scrips/Objects.html
index c348e78..52db4d9 100644
--- a/share/html/Admin/Scrips/Objects.html
+++ b/share/html/Admin/Scrips/Objects.html
@@ -54,13 +54,17 @@
 
 % if ( $is_global ) {
 <h2><&|/l&>Applies to all objects</&></h2>
+<label>
 <input type="checkbox" name="RemoveScrip-<% $id %>" value="0" />
 <&|/l&>check this box to remove this scrip from all objects and be able to choose specific objects.</&>
+</label>
 % } else {
 <h2><&|/l&>Apply globally</&></h2>
 
+<label>
 <input type="checkbox" name="AddScrip-<% $id %>" value="0" />
 <&|/l&>check this box to apply this scrip to all objects.</&>
+</label>
 
 <h2><&|/l&>Selected objects</&></h2>
 <& /Elements/CollectionList,

commit f9fd8e9f70d39b111388f1689a57b3bafbd79acb
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Nov 5 22:21:25 2012 -0800

    Grab the parent form and checkbox state from the current element in setCheckbox()
    
    This provides more flexibility for setCheckbox() than passing in the
    form and state as arguments themselves.
    
    Desired state may still be passed as the third argument, if necessary,
    as with a button using this function.

diff --git a/share/html/Elements/ColumnMap b/share/html/Elements/ColumnMap
index 7295e3f..1ad3524 100644
--- a/share/html/Elements/ColumnMap
+++ b/share/html/Elements/ColumnMap
@@ -119,9 +119,9 @@ my $COLUMN_MAP = {
             my $checked = $DECODED_ARGS->{ $name .'All' }? 'checked="checked"': '';
 
             return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked
-                              onclick="setCheckbox(this.form, },
+                              onclick="setCheckbox(this, },
                               $m->interp->apply_escapes($name,'j'),
-                              \qq{, this.checked)" />};
+                              \qq{)" />};
         },
         value => sub {
             my $id = $_[0]->id;
diff --git a/share/html/Elements/RT__CustomField/ColumnMap b/share/html/Elements/RT__CustomField/ColumnMap
index 84ad3df..626b62f 100644
--- a/share/html/Elements/RT__CustomField/ColumnMap
+++ b/share/html/Elements/RT__CustomField/ColumnMap
@@ -121,9 +121,9 @@ my $COLUMN_MAP = {
             my $checked = $DECODED_ARGS->{ $name .'All' }? 'checked="checked"': '';
 
             return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked
-                              onclick="setCheckbox(this.form, },
+                              onclick="setCheckbox(this, },
                               $m->interp->apply_escapes($name,'j'),
-                              \qq{, this.checked)" />};
+                              \qq{)" />};
         },
         value => sub {
             my $id = $_[0]->id;
diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index 999453a..6f14181 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -104,7 +104,7 @@ my $COLUMN_MAP = {
             my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': '';
 
             return \qq{<input type="checkbox" name="${name}All" value="1" $checked
-                              onclick="setCheckbox(this.form, '$name', this.checked)" />};
+                              onclick="setCheckbox(this, '$name')" />};
         },
         value => sub {
             my $id = $_[0]->id;
diff --git a/share/html/Elements/Submit b/share/html/Elements/Submit
index b7840d3..7629707 100644
--- a/share/html/Elements/Submit
+++ b/share/html/Elements/Submit
@@ -52,10 +52,10 @@ id="<%$id%>"
 >
   <div class="extra-buttons">
 % if ($CheckAll) {
-  <input type="button" value="<%$CheckAllLabel%>" onclick="setCheckbox(this.form, <% $match %>, true);return false;" class="button" />
+  <input type="button" value="<%$CheckAllLabel%>" onclick="setCheckbox(this, <% $match %>);return false;" class="button" />
 % }
 % if ($ClearAll) {
-  <input type="button" value="<%$ClearAllLabel%>" onclick="setCheckbox(this.form, <% $match %>, false);return false;" class="button" />
+  <input type="button" value="<%$ClearAllLabel%>" onclick="setCheckbox(this, <% $match %>);return false;" class="button" />
 % }
 % if ($Reset) {
   <input type="reset" value="<%$ResetLabel%>" class="button" />
diff --git a/share/html/NoAuth/js/util.js b/share/html/NoAuth/js/util.js
index fe5c0a3..3c9b151 100644
--- a/share/html/NoAuth/js/util.js
+++ b/share/html/NoAuth/js/util.js
@@ -127,7 +127,10 @@ function focusElementById(id) {
     if (e) e.focus();
 }
 
-function setCheckbox(form, name, val) {
+function setCheckbox(input, name, val) {
+    if (val == null) val = input.checked;
+
+    var form    = input.form;
     var myfield = form.getElementsByTagName('input');
     for ( var i = 0; i < myfield.length; i++ ) {
         if ( myfield[i].type != 'checkbox' ) continue;

commit 3903c9ee9cf92b5fff7126829665c3126ab84303
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Nov 5 22:45:08 2012 -0800

    setCheckbox()es within the closest form _or_ collection list
    
    If input is inside a collection list, we don't want to affect other
    collection lists.
    
    This prevents the checkboxes to apply scrips and CFs from checking the
    "apply globally" checkbox inside the form but outside the collection
    list.

diff --git a/share/html/NoAuth/js/util.js b/share/html/NoAuth/js/util.js
index 3c9b151..6e045a4 100644
--- a/share/html/NoAuth/js/util.js
+++ b/share/html/NoAuth/js/util.js
@@ -130,8 +130,9 @@ function focusElementById(id) {
 function setCheckbox(input, name, val) {
     if (val == null) val = input.checked;
 
-    var form    = input.form;
-    var myfield = form.getElementsByTagName('input');
+    // Find inputs within the current form or collection list, whichever is closest.
+    var container = jQuery(input).closest("form, table.collection-as-table").get(0);
+    var myfield   = container.getElementsByTagName('input');
     for ( var i = 0; i < myfield.length; i++ ) {
         if ( myfield[i].type != 'checkbox' ) continue;
         if ( name ) {

commit e168ad8f10451250f79e8bbc47d655e50c4ca9df
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Nov 8 18:17:44 2012 -0500

    Remove /Admin/Elements/SelectScrip, last referenced in 392f8fd

diff --git a/share/html/Admin/Elements/SelectScrip b/share/html/Admin/Elements/SelectScrip
deleted file mode 100644
index f0adab1..0000000
--- a/share/html/Admin/Elements/SelectScrip
+++ /dev/null
@@ -1,72 +0,0 @@
-%# 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 }}}
-<select name="<%$Name%>">
-<option value="" 
-<% $Default eq undef && qq[ selected="selected"] |n %>
->-</option>
-%while  (my $Scrip = $Scrips->Next) {
-<option value="<% $Scrip->Id %>"
-<% $Scrip->Id == $Default && qq[ selected="selected"] |n %>
-><% loc($Scrip->Name) %>
-</option>
-%}
-</select>
-
-<%INIT>
-my $Scrips = RT::Scrips->new($session{'CurrentUser'});
-$Scrips->UnLimit;
-
-
-
-</%INIT>
-<%ARGS>
-
-$Default => undef
-$Name => 'Scrip'
-
-</%ARGS>

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


More information about the Rt-commit mailing list