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

Alex Vandiver alexmv at bestpractical.com
Tue Nov 6 16:17:27 EST 2012


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

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

    extract re-usable code from ObjectCustomField[s]
    
    Two new classes for records and collections

diff --git a/lib/RT/ObjectCustomField.pm b/lib/RT/ObjectCustomField.pm
index 61bc355..459164f 100644
--- a/lib/RT/ObjectCustomField.pm
+++ b/lib/RT/ObjectCustomField.pm
@@ -45,93 +45,23 @@
 # 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 {
-    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;
-}
-
+# 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 +84,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..19e5766
--- /dev/null
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -0,0 +1,275 @@
+# 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 TargetField {
+    my $class = ref($_[0]) || $_[0];
+    $class =~ s/.*::Object// or return undef;
+    return $class;
+}
+
+sub Create {
+    my $self = shift;
+    my %args = (
+        Scrip       => 0,
+        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{'Scrip'}'");
+        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(
+        $tfield   => $target->id,
+        ObjectId  => $args{'ObjectId'},
+        SortOrder => $args{'SortOrder'},
+    );
+}
+
+sub Delete {
+    my $self = shift;
+
+    my $siblings = $self->Neighbors;
+    $siblings->LimitToObjectId( $self->ObjectId );
+    $siblings->Limit( FIELD => 'SortOrder', OPERATOR => '>', VALUE => $self->SortOrder );
+
+    # Move everything below us up
+    my $sort_order = $self->SortOrder;
+    while (my $record = $siblings->Next) {
+        $record->SetSortOrder($record->SortOrder - 1);
+    }
+
+    $self->SUPER::Delete;
+}
+
+=head2 Sorting scrips applications
+
+scrips sorted on multiple layers. First of all custom
+fields with different lookup type are sorted independently. All
+global scrips have fixed order for all objects, but you
+can insert object specific scrips between them. Object
+specific scrips 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 scrip up. See </Sorting scrips applications>.
+
+=cut
+
+sub MoveUp {
+    my $self = shift;
+
+    my $siblings = $self->Siblings;
+    $siblings->Limit( FIELD => 'SortOrder', OPERATOR => '<', VALUE => $self->SortOrder );
+    $siblings->OrderByCols( { FIELD => 'SortOrder', ORDER => 'DESC' } );
+
+    my @above = ($siblings->Next, $siblings->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 scrip");
+        }
+    }
+    elsif ( $above[1] && $above[0]->SortOrder == $above[1]->SortOrder + 1 ) {
+        my $move_siblings = $self->Neighbors;
+        $move_siblings->Limit(
+            FIELD => 'SortOrder',
+            OPERATOR => '>=',
+            VALUE => $above[0]->SortOrder,
+        );
+        $move_siblings->OrderByCols( { FIELD => 'SortOrder', ORDER => 'DESC' } );
+        while ( my $record = $move_siblings->Next ) {
+            my ($status, $msg) = $record->SetSortOrder( $record->SortOrder + 1 );
+            unless ( $status ) {
+                return (0, "Couldn't move scrip");
+            }
+        }
+        $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 scrip");
+    }
+
+    return (1,"Moved scrip up");
+}
+
+=head3 MoveDown
+
+Moves scrip down. See </Sorting scrips applications>.
+
+=cut
+
+sub MoveDown {
+    my $self = shift;
+
+    my $siblings = $self->Siblings;
+    $siblings->Limit( FIELD => 'SortOrder', OPERATOR => '>', VALUE => $self->SortOrder );
+    $siblings->OrderByCols( { FIELD => 'SortOrder', ORDER => 'ASC' } );
+
+    my @below = ($siblings->Next, $siblings->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 scrip");
+        }
+    }
+    elsif ( $below[1] && $below[0]->SortOrder + 1 == $below[1]->SortOrder ) {
+        my $move_siblings = $self->Neighbors;
+        $move_siblings->Limit(
+            FIELD => 'SortOrder',
+            OPERATOR => '<=',
+            VALUE => $below[0]->SortOrder,
+        );
+        $move_siblings->OrderByCols( { FIELD => 'SortOrder', ORDER => 'ASC' } );
+        while ( my $record = $move_siblings->Next ) {
+            my ($status, $msg) = $record->SetSortOrder( $record->SortOrder - 1 );
+            unless ( $status ) {
+                return (0, "Couldn't move scrip");
+            }
+        }
+        $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 scrip");
+    }
+
+    return (1,"Moved scrip down");
+}
+
+sub NextSortOrder {
+    my $self = shift;
+    my $siblings = $self->Siblings( @_ );
+    $siblings->OrderBy( FIELD => 'SortOrder', ORDER => 'DESC' );
+    return 0 unless my $first = $siblings->First;
+    return $first->SortOrder + 1;
+}
+
+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/ObjectCustomFields.pm b/lib/RT/SearchBuilder/ApplyAndSort.pm
similarity index 57%
copy from lib/RT/ObjectCustomFields.pm
copy to lib/RT/SearchBuilder/ApplyAndSort.pm
index 9864949..95813ea 100644
--- a/lib/RT/ObjectCustomFields.pm
+++ b/lib/RT/SearchBuilder/ApplyAndSort.pm
@@ -46,39 +46,32 @@
 #
 # END BPS TAGGED BLOCK }}}
 
-package RT::ObjectCustomFields;
-
 use strict;
 use warnings;
 
-
-use RT::ObjectCustomField;
-
+package RT::SearchBuilder::ApplyAndSort;
 use base 'RT::SearchBuilder';
 
-sub Table { 'ObjectCustomFields'}
+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(@_) );
-}
-
+    # By default, order by SortOrder
+    $self->OrderByCols(
+         { ALIAS => 'main',
+           FIELD => 'SortOrder',
+           ORDER => 'ASC' },
+         { ALIAS => 'main',
+           FIELD => 'id',
+           ORDER => 'ASC' },
+    );
 
-sub LimitToCustomField {
-    my $self = shift;
-    my $id = shift;
-    $self->Limit( FIELD => 'CustomField', VALUE => $id );
+    return $self->SUPER::_Init(@_);
 }
 
 sub LimitToObjectId {
@@ -87,69 +80,17 @@ sub LimitToObjectId {
     $self->Limit( FIELD => 'ObjectId', VALUE => $id );
 }
 
-sub LimitToLookupType {
-    my $self = shift;
-    my $lookup = shift;
-
-    $self->{'_cfs_alias'} ||= $self->Join(
-        ALIAS1 => 'main',
-        FIELD1 => 'CustomField',
-        TABLE2 => 'CustomFields',
-        FIELD2 => 'id',
-    );
-    $self->Limit(
-        ALIAS    => $self->{'_cfs_alias'},
-        FIELD    => 'LookupType',
-        OPERATOR => '=',
-        VALUE    => $lookup,
-    );
-}
-
-sub HasEntryForCustomField {
-    my $self = shift;
-    my $id = shift;
-
-    my @items = grep {$_->CustomField == $id } @{$self->ItemsArrayRef};
-
-    if ($#items > 1) {
-	die "$self HasEntry had a list with more than one of $id in it. this can never happen";
-    }
-    if ($#items == -1 ) {
-	return undef;
-    }
-    else {
-	return ($items[0]);
-    }  
-}
-
-sub CustomFields {
-    my $self = shift;
-    my %seen;
-    map { $_->CustomFieldObj } @{$self->ItemsArrayRef};
-}
-
-sub _DoSearch {
-    my $self = shift;
-    if ($self->{'_cfs_alias'}) {
-    $self->Limit( ALIAS           => $self->{'_cfs_alias'},
-                 FIELD           => 'Disabled',
-                 OPERATOR        => '!=',
-                 VALUE           =>  1);
-    }
-    $self->SUPER::_DoSearch()
-}
-
-
 =head2 NewItem
 
-Returns an empty new RT::ObjectCustomField item
+Returns an empty new collection's item
 
 =cut
 
 sub NewItem {
     my $self = shift;
-    return(RT::ObjectCustomField->new($self->CurrentUser));
+    return $self->RecordClass->new( $self->CurrentUser );
 }
+
 RT::Base->_ImportOverlays();
 
 1;

commit 6213fc682361d13f0d2290919e35b54552d50970
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Dec 13 04:04:11 2011 +0400

    extract some code from CustomField.pm into new modules

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index a409c3a..8d5623f 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);
 }
@@ -1360,7 +1358,6 @@ Takes an object
 
 =cut
 
-
 sub AddToObject {
     my $self  = shift;
     my $object = shift;
@@ -1374,26 +1371,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/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index 19e5766..92b0f00 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -100,6 +100,46 @@ sub Create {
     );
 }
 
+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(
+        CustomField => $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;
+}
+
 sub Delete {
     my $self = shift;
 
@@ -116,6 +156,21 @@ sub Delete {
     $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 };
+}
+
 =head2 Sorting scrips applications
 
 scrips sorted on multiple layers. First of all custom

commit a4cec256a01238222e14b95d82850d02a6574674
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Dec 13 17:02:29 2011 +0400

    move *AppliedTo methods

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 8d5623f..ec436e3 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1263,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
@@ -1289,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
diff --git a/lib/RT/ObjectCustomField.pm b/lib/RT/ObjectCustomField.pm
index 459164f..f4d2483 100644
--- a/lib/RT/ObjectCustomField.pm
+++ b/lib/RT/ObjectCustomField.pm
@@ -56,6 +56,12 @@ use RT::ObjectCustomFields;
 
 sub Table {'ObjectCustomFields'}
 
+sub ObjectCollectionClass {
+    my $self = shift;
+    my %args = (@_);
+    return $args{'CustomField'}->CollectionClassFromLookupType;
+}
+
 # XXX: Where is ACL check when we create a record?
 
 =head2 CustomFieldObj
diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index 92b0f00..78e0614 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -56,6 +56,8 @@ 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;
@@ -140,6 +142,90 @@ sub IsApplied {
     return $record->id;
 }
 
+=head1 AppliedTo
+
+Returns collection with objects this custom field is applied to.
+Class of the collection depends on L</LookupType>.
+See all L</NotAppliedTo> .
+
+Doesn't takes into account if 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 takes into account if 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 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    => $self->id,
+    );
+    return ($res, $alias);
+}
+
 sub Delete {
     my $self = shift;
 

commit 1334a3ecd392d48a3f7a896532c52c95b9048479
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Dec 14 01:44:31 2011 +0400

    move more methods, now related to collections

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/SearchBuilder/ApplyAndSort.pm b/lib/RT/SearchBuilder/ApplyAndSort.pm
index 95813ea..b3243c4 100644
--- a/lib/RT/SearchBuilder/ApplyAndSort.pm
+++ b/lib/RT/SearchBuilder/ApplyAndSort.pm
@@ -80,6 +80,97 @@ sub LimitToObjectId {
     $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',
+    );
+}
+
+=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',
+    );
+}
+
+sub JoinTargetToApplied {
+    my $self = shift;
+    my $collection = shift;
+    my @ids = @_;
+
+    my $alias = $self->JoinTargetToThis( $collection, New => 1, 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 => 'CustomField'
+    );
+    return $alias if $args{'New'};
+    return $collection->{ $key } = $alias;
+}
+
 =head2 NewItem
 
 Returns an empty new collection's item

commit 21ba926702972e7a0259c21b3afd9b53e76c621f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Dec 14 15:11:05 2011 +0400

    get rid of last mentions of CustomField in generic class

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index 78e0614..4d262b6 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -130,7 +130,7 @@ sub Apply {
     }
 
     return $self->Create(
-        CustomField => $tid, ObjectId => $oid,
+        $field => $tid, ObjectId => $oid,
     );
 }
 
diff --git a/lib/RT/SearchBuilder/ApplyAndSort.pm b/lib/RT/SearchBuilder/ApplyAndSort.pm
index b3243c4..49d65f9 100644
--- a/lib/RT/SearchBuilder/ApplyAndSort.pm
+++ b/lib/RT/SearchBuilder/ApplyAndSort.pm
@@ -165,7 +165,7 @@ sub JoinTargetToThis {
         ALIAS1 => 'main',
         FIELD1 => 'id',
         TABLE2 => $table,
-        FIELD2 => 'CustomField'
+        FIELD2 => $self->RecordClass->TargetField,
     );
     return $alias if $args{'New'};
     return $collection->{ $key } = $alias;

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

    ObjectScrips table in schemas

diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index 4bcae6c..751d03f 100755
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -150,6 +150,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 SEQUENCE ACL_seq;
 CREATE TABLE ACL (
diff --git a/etc/schema.Pg b/etc/schema.Pg
index 565f76b..7ab5ae2 100755
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -242,6 +242,21 @@ CREATE TABLE Scrips (
 );
 
 
+CREATE SEQUENCE objectscrips_id_seq;
+
+CREATE TABLE ObjectCustomFields (
+  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)
+
+);
 
 
 
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index 6897be2..5a770f5 100755
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -162,6 +162,19 @@ 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)
+);
+
 --- {{{ ACL
 CREATE TABLE ACL (
   id INTEGER PRIMARY KEY  ,
diff --git a/etc/schema.mysql b/etc/schema.mysql
index 9ed0337..adf0ef1 100755
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -152,6 +152,19 @@ 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 TABLE ACL (
   id INTEGER NOT NULL  AUTO_INCREMENT,

commit a73a17303d8d6bae17870907b61200b077d24112
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Dec 14 23:38:43 2011 +0400

    ObjectScrip[s] classes
    
    * scrips <-> queues many to many relation
    * custom sorting

diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
new file mode 100644
index 0000000..d08a2dc
--- /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::ApplyAndSort';
+
+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..d8bbf35
--- /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::ApplyAndSort';
+
+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;

commit ff796db724fe6bc892b1aa4a4cd93d26d723fc2c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sun Dec 18 02:32:24 2011 +0400

    drop Queue column in Scrips and usage of it in most places

diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index 751d03f..1f82447 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,
diff --git a/etc/schema.Pg b/etc/schema.Pg
index 7ab5ae2..eb335e6 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  ,
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index 5a770f5..ed37ad4 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  ,
diff --git a/etc/schema.mysql b/etc/schema.mysql
index adf0ef1..31bf3b8 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  ,
diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 40b030c..5a37fc6 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -170,7 +170,6 @@ sub Create {
         unless $condition->Id;
 
     my ( $id, $msg ) = $self->SUPER::Create(
-        Queue                  => $args{'Queue'},
         Template               => $template->Id,
         ScripCondition         => $condition->id,
         Stage                  => $args{'Stage'},
@@ -208,21 +207,10 @@ sub Delete {
 
 
 
-=head2 QueueObj
-
-Retuns an RT::Queue object with this Scrip's queue
-
-=cut
 
 sub QueueObj {
     my $self = shift;
 
-    if ( !$self->{'QueueObj'} ) {
-        require RT::Queue;
-        $self->{'QueueObj'} = RT::Queue->new( $self->CurrentUser );
-        $self->{'QueueObj'}->Load( $self->__Value('Queue') );
-    }
-    return ( $self->{'QueueObj'} );
 }
 
 
@@ -504,8 +492,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 +537,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);
     }
 
@@ -904,24 +889,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 +967,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/share/html/Admin/Elements/EditScrip b/share/html/Admin/Elements/EditScrip
index e4c49ab..bd73b83 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 91d4cc437933ceb047d9545e73758d4f427b5c0d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sun Dec 18 02:34:33 2011 +0400

    use new ApplySort API in Scrip[s] classes

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 5a37fc6..e7b36ed 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'}
@@ -179,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 )
+            ->Apply( Scrip => $self, ObjectId => $args{'Queue'} );
+        $RT::Logger->error( "Couldn't apply scrip: $msg" ) unless $status;
     }
+
+    return ( $id, $self->loc('Scrip Created') );
 }
 
 
@@ -205,15 +211,42 @@ sub Delete {
     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 $record->id;
+}
 
+sub AddedTo {
+    my $self = shift;
+    return RT::ObjectScrip->new( $self->CurrentUser )
+        ->AppliedTo( Scrip => $self );
+}
 
+sub NotAddedTo {
+    my $self = shift;
+    return RT::ObjectScrip->new( $self->CurrentUser )
+        ->NotAppliedTo( Scrip => $self );
+}
 
-sub QueueObj {
+sub AddToObject {
     my $self = shift;
+    my $object = shift;
 
+    my $rec = RT::ObjectScrip->new( $self->CurrentUser );
+    return $rec->Apply( 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 applied') ) unless $rec->id;
+    return $rec->Delete;
+}
 
 =head2 ActionObj
 
@@ -577,18 +610,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;
 }
 
 
diff --git a/lib/RT/Scrips.pm b/lib/RT/Scrips.pm
index fa33f7e..f7c114f 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,32 @@ 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 )
+        ->LimitTargetToApplied( $self => @_ );
+}
+
+sub LimitToNotAdded {
+    my $self = shift;
+    return RT::ObjectScrips->new( $self->CurrentUser )
+        ->LimitTargetToNotApplied( $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 

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

    AdminURL column map for scrips
    
    use only one format string in the config

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 43618c5..0a1464a 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2578,13 +2578,8 @@ Set(%AdminSearchResultFormat,
         .q{,__AppliedTo__, __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="__AdminURL__">__id__</a>/TITLE:#'}
+        .q{,'<a href="__AdminURL__">__Description__</a>/TITLE:Description'}
         .q{,__Stage__, __Condition__, __Action__, __Template__},
 
     Templates =>
diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index 776a2d8..2ee2905 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -98,6 +98,20 @@ my $COLUMN_MAP = {
 	attribute => 'Stage',
 	value     => sub { return $_[0]->Stage() },
     },
+
+    AdminURL => {
+	value     => sub {
+            my $queue = $_[0]->AppliedTo->First;
+            my $res = RT->Config->Get('WebPath') .'/Admin';
+            if ( $queue ) {
+                $res .= '/Queues/Scrip.html?Queue='. $queue->id .'&';
+            } else {
+                $res .= '/Global/Scrip.html?';
+            }
+            $res .= 'id='. $_[0]->id;
+            return $res;
+        },
+    },
 };
 
 </%ONCE>

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

    column maps fro removing scrips and moving them

diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index 2ee2905..d4c187b 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -112,6 +112,60 @@ my $COLUMN_MAP = {
             return $res;
         },
     },
+    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 = 'MoveCustomField';
+            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 3cfdd9253e5f7372ad03ee27ad0ca9c4ae6c6c2c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sun Dec 18 02:38:52 2011 +0400

    new UI for scrips management

diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index 2fdcae6..410fa01 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -50,70 +50,122 @@
 <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->(Applied => 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;
 }
 
-if ($QueueObj->id) {
-    $Scrips->LimitToQueue($id);
-    $Format ||= RT->Config->Get('AdminSearchResultFormat')->{'Scrips'};
-}
-else {
-    $Scrips->LimitToGlobal();
-    $Format ||= RT->Config->Get('AdminSearchResultFormat')->{'GlobalScrips'};
+my $find_scrips = sub {
+    my %args = (Applied => 1, @_);
+    my $scrips = RT::Scrips->new($session{'CurrentUser'});
+    $scrips->Limit( FIELD => 'Stage', VALUE => $args{'Stage'} )
+        if $args{'Stage'};
+    my $method = $args{'Applied'}? 'LimitToAdded' : 'LimitToNotAdded';
+    $scrips->$method(0, $id);
+    $scrips->ApplySortOrder if $args{'Applied'};
+    return $scrips;
+};
+
+$Format ||= RT->Config->Get('AdminSearchResultFormat')->{'Scrips'};
+my $DisplayFormat = $Format;
+if ( $id ) {
+    $DisplayFormat = "__RemoveCheckBox__, $DisplayFormat";
+} else {
+    $DisplayFormat = "__CheckBox.{RemoveScrip}__, $DisplayFormat";
 }
+$DisplayFormat .= ", __Move__";
+
+my %common_applied_args = (
+    %ARGS,
+    Format => $Format,
+    DisplayFormat => $DisplayFormat,
+    Rows => 0,
+    Page => 1,
+    AllowSorting => 0,
+    PassArguments => [ qw(Format id) ],
+);
 
-# 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 ( $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;
     }
-    else {
+}
+
+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;
     }
 }
+
 </%init>
 
 <%ARGS>
 $id => undef
 $title => undef
 $Format => undef
- at DeleteScrip => ()
+
+ at RemoveScrip => ()
+$RemoveScrips => undef
+
+ at AddScrip => ()
+$AddScrips => undef
+
 </%ARGS>

commit 7857d61a4b79f861aa26ec4ac8202cbd8a782252
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Dec 19 20:46:46 2011 +0400

    pass everything in Create
    
    so table can have additional columns except those expected
    by ApplyAndSort

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index 4d262b6..927eb12 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -96,9 +96,8 @@ sub Create {
     }
 
     return $self->SUPER::Create(
+        %args,
         $tfield   => $target->id,
-        ObjectId  => $args{'ObjectId'},
-        SortOrder => $args{'SortOrder'},
     );
 }
 
@@ -257,6 +256,24 @@ sub DeleteAll {
     $_->Delete foreach @{ $list->ItemsArrayRef };
 }
 
+sub SetDisabledOnAll {
+    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 );
+    foreach ( @{ $list->ItemsArrayRef } ) {
+        my ($status, $msg) = $_->SetDisabled( $args{Value} || 0 );
+        return ($status, $msg) unless $status;
+    }
+}
+
 =head2 Sorting scrips applications
 
 scrips sorted on multiple layers. First of all custom

commit feff2b969973b4605ad79d34eefee0ba5455bbf3
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Dec 20 01:17:08 2011 +0400

    forgot to change name of the table on c&p

diff --git a/etc/schema.Pg b/etc/schema.Pg
index eb335e6..b57e0bf 100755
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -243,7 +243,7 @@ CREATE TABLE Scrips (
 
 CREATE SEQUENCE objectscrips_id_seq;
 
-CREATE TABLE ObjectCustomFields (
+CREATE TABLE ObjectScrips (
   id INTEGER DEFAULT nextval('objectscrips_id_seq'),
   Scrip integer NOT NULL,
   ObjectId integer NOT NULL,

commit 19df50133d8fd3b0ef138836b23e236e48ac7acb
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Dec 20 01:49:50 2011 +0400

    delete mention of a subclass in ApplyAndSort.pm

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index 927eb12..5b14124 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -67,7 +67,6 @@ sub TargetField {
 sub Create {
     my $self = shift;
     my %args = (
-        Scrip       => 0,
         ObjectId    => 0,
         SortOrder   => undef,
         @_

commit d68fa399fce28eb03d2e65750f0f0ad0d01f245d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Dec 20 17:00:52 2011 +0400

    default Disabled to zero
    
    explicit NULL value doesn't trigger DB side default

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index 5b14124..75d57dc 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -93,6 +93,7 @@ sub Create {
             ObjectId => $args{'ObjectId'},
         );
     }
+    $args{'Disabled'} ||= 0;
 
     return $self->SUPER::Create(
         %args,

commit 9f0f1b1b2b1c7dffc825ad38a2e7d629154d9f8c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Dec 20 17:02:43 2011 +0400

    report success from SetDisabledOnAll

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index 75d57dc..3d905b9 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -272,6 +272,7 @@ sub SetDisabledOnAll {
         my ($status, $msg) = $_->SetDisabled( $args{Value} || 0 );
         return ($status, $msg) unless $status;
     }
+    return (1, $self->loc("Disabled all applications") );
 }
 
 =head2 Sorting scrips applications

commit 5de6a06e0b3dd020b700b462006d1147a0f0eef4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Dec 20 17:03:52 2011 +0400

    drop handling of disabled column
    
    we move column into OCFs table and will handle it
    differently

diff --git a/lib/RT/ObjectCustomFields.pm b/lib/RT/ObjectCustomFields.pm
index c863dab..72b1894 100644
--- a/lib/RT/ObjectCustomFields.pm
+++ b/lib/RT/ObjectCustomFields.pm
@@ -103,17 +103,6 @@ sub CustomFields {
     map { $_->CustomFieldObj } @{$self->ItemsArrayRef};
 }
 
-sub _DoSearch {
-    my $self = shift;
-    if ($self->{'_cfs_alias'}) {
-    $self->Limit( ALIAS           => $self->{'_cfs_alias'},
-                 FIELD           => 'Disabled',
-                 OPERATOR        => '!=',
-                 VALUE           =>  1);
-    }
-    $self->SUPER::_DoSearch()
-}
-
 RT::Base->_ImportOverlays();
 
 1;

commit 58f8f54de265cf99dea11b0978f84dcd88d825c3
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Dec 20 17:07:36 2011 +0400

    move Disabled column from things we apply
    
    We move it into Object{CustomField,Scrip}s table, so
    particular application can be disabled in theory and
    disabling doesn't change order when we re-enable.
    
    In first implementation we disable/enable all records
    at once through applied object.

diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index 1f82447..e570a4f 100755
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -156,6 +156,7 @@ CREATE TABLE ObjectScrips (
         Scrip       NUMBER(11,0)  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,
@@ -331,6 +332,7 @@ CREATE TABLE ObjectCustomFields (
         CustomField       NUMBER(11,0)  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,
@@ -378,8 +380,7 @@ CREATE TABLE CustomFields (
 	Creator		NUMBER(11,0) DEFAULT 0 NOT NULL,
 	Created		DATE,
 	LastUpdatedBy	NUMBER(11,0) DEFAULT 0 NOT NULL,
-	LastUpdated	DATE,
-	Disabled	NUMBER(11,0) DEFAULT 0 NOT NULL
+	LastUpdated	DATE
 );
 
 
diff --git a/etc/schema.Pg b/etc/schema.Pg
index b57e0bf..79a0158 100755
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -248,6 +248,7 @@ CREATE TABLE ObjectScrips (
   Scrip integer NOT NULL,
   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  ,
@@ -568,7 +569,6 @@ CREATE TABLE CustomFields (
   Created TIMESTAMP NULL  ,
   LastUpdatedBy integer NOT NULL DEFAULT 0  ,
   LastUpdated TIMESTAMP NULL  ,
-  Disabled integer NOT NULL DEFAULT 0 ,
   PRIMARY KEY (id)
 
 );
@@ -584,6 +584,7 @@ CREATE TABLE ObjectCustomFields (
   CustomField integer NOT NULL,
   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  ,
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index ed37ad4..42eacf7 100755
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -166,6 +166,7 @@ CREATE TABLE ObjectScrips (
   Scrip int NOT NULL  ,
   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  ,
@@ -402,7 +403,6 @@ CREATE TABLE CustomFields (
   Created DATETIME NULL  ,
   LastUpdatedBy integer NOT NULL DEFAULT 0  ,
   LastUpdated DATETIME NULL  ,
-  Disabled int2 NOT NULL DEFAULT 0 ,
   PRIMARY KEY (id)
 ) ;
 
@@ -413,6 +413,7 @@ CREATE TABLE ObjectCustomFields (
   CustomField int NOT NULL  ,
   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  ,
diff --git a/etc/schema.mysql b/etc/schema.mysql
index 31bf3b8..3453621 100755
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -156,6 +156,7 @@ CREATE TABLE ObjectScrips (
   Scrip integer NOT NULL  ,
   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  ,
@@ -380,7 +381,6 @@ CREATE TABLE CustomFields (
   Created DATETIME NULL  ,
   LastUpdatedBy integer NOT NULL DEFAULT 0  ,
   LastUpdated DATETIME NULL  ,
-  Disabled int2 NOT NULL DEFAULT 0 ,
   PRIMARY KEY (id)
 ) ENGINE=InnoDB CHARACTER SET utf8;
 
@@ -391,6 +391,7 @@ CREATE TABLE ObjectCustomFields (
   CustomField integer NOT NULL  ,
   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  ,
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index ec436e3..1db0514 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -383,7 +383,6 @@ sub Create {
         BasedOn     => $args{'BasedOn'},
         ValuesClass => $args{'ValuesClass'},
         Description => $args{'Description'},
-        Disabled    => $args{'Disabled'},
         LookupType  => $args{'LookupType'},
         Repeated    => $args{'Repeated'},
     );
@@ -404,6 +403,7 @@ sub Create {
         $OCF->Create(
             CustomField => $self->Id,
             ObjectId => $args{'Queue'},
+            Disabled => $args{'Disabled'},
         );
     }
 
@@ -488,8 +488,10 @@ sub LoadByName {
     # When loading by name, we _can_ load disabled fields, but prefer
     # non-disabled fields.
     $CFs->FindAllRows;
+    my $alias = RT::ObjectCustomFields->new( $self->CurrentUser )
+        ->JoinTargetToThis( $CFs, Left => 1, New => 1 );
     $CFs->OrderByCols(
-        { FIELD => "Disabled", ORDER => 'ASC' },
+        { ALIAS => $alias, FIELD => "Disabled", ORDER => 'ASC' },
     );
 
     # We only want one entry.
@@ -991,6 +993,19 @@ sub _Value {
     return $self->__Value( @_ );
 }
 
+=head2 Disabled
+
+Returns the current value of Disabled. 
+(In the database, Disabled is stored as smallint(6).)
+
+=cut
+
+sub Disabled {
+    my $self = shift;
+    my $record = RT::ObjectCustomField->new( $self->CurrentUser );
+    $record->LoadByCols( CustomField => $self->id );
+    return $record->Disabled;
+}
 
 =head2 SetDisabled
 
@@ -1000,6 +1015,12 @@ Takes a boolean.
 
 =cut
 
+sub SetDisabled {
+    my $self = shift;
+    return RT::ObjectCustomField->new( $self->CurrentUser )
+        ->SetDisabledOnAll( CustomField => $self->id, Value => shift );
+}
+
 
 =head2 SetTypeComposite
 
@@ -1949,29 +1970,9 @@ Returns the current value of LastUpdatedBy.
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
 
-
 =cut
 
 
-=head2 Disabled
-
-Returns the current value of Disabled. 
-(In the database, Disabled is stored as smallint(6).)
-
-
-
-=head2 SetDisabled VALUE
-
-
-Set Disabled to VALUE. 
-Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Disabled will be stored as a smallint(6).)
-
-
-=cut
-
-
-
 sub _CoreAccessible {
     {
      
@@ -2005,8 +2006,6 @@ sub _CoreAccessible {
         {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 => ''},
-        Disabled => 
-        {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
 
  }
 };
diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index bf15b16..37ca251 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -91,7 +91,6 @@ sub _Init {
 	   FIELD => 'id',
 	   ORDER => 'ASC' },
      );
-    $self->{'with_disabled_column'} = 1;
 
     return ( $self->SUPER::_Init(@_) );
 }
diff --git a/lib/RT/ObjectCustomField.pm b/lib/RT/ObjectCustomField.pm
index f4d2483..72c9c24 100644
--- a/lib/RT/ObjectCustomField.pm
+++ b/lib/RT/ObjectCustomField.pm
@@ -212,6 +212,8 @@ sub _CoreAccessible {
 		{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'},
+        Disabled => 
+        {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
         Creator =>
 		{read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created =>
diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index d08a2dc..0b02a39 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -191,6 +191,8 @@ sub _CoreAccessible {
 		{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'},
+        Disabled => 
+        {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
         Creator =>
 		{read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created =>

commit a42dffcf3faeb09945677c822bde0e964d663d24
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Dec 20 18:48:12 2011 +0400

    dummy Create methods to pass only things we need

diff --git a/lib/RT/ObjectCustomField.pm b/lib/RT/ObjectCustomField.pm
index 72c9c24..5f838e3 100644
--- a/lib/RT/ObjectCustomField.pm
+++ b/lib/RT/ObjectCustomField.pm
@@ -64,6 +64,19 @@ sub ObjectCollectionClass {
 
 # XXX: Where is ACL check when we create a record?
 
+sub Create {
+    my $self = shift;
+    my %args = (@_);
+    return $self->SUPER::Create(
+        map { $_ => $args{ $_ } } qw(
+            CustomField ObjectId
+            SortOrder Disabled
+            Created Creator
+            LastUpdated LastUpdatedBy
+        )
+    );
+}
+
 =head2 CustomFieldObj
 
 Returns the CustomField Object which has the id returned by CustomField
diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index 0b02a39..dab264f 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -58,6 +58,19 @@ use RT::ObjectScrips;
 sub Table {'ObjectScrips'}
 sub ObjectCollectionClass {'RT::Queues'}
 
+sub Create {
+    my $self = shift;
+    my %args = (@_);
+    return $self->SUPER::Create(
+        map { $_ => $args{ $_ } } qw(
+            Scrip ObjectId
+            SortOrder Disabled
+            Created Creator
+            LastUpdated LastUpdatedBy
+        )
+    );
+}
+
 =head2 ScripObj
 
 Returns the Scrip Object which has the id returned by Scrip

commit 79fd35691acdf51a5bce0bd6b173c556a31b166c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 22 16:04:26 2011 +0400

    sort scrips by sort order, not description

diff --git a/lib/RT/Scrips.pm b/lib/RT/Scrips.pm
index f7c114f..4304ed0 100644
--- a/lib/RT/Scrips.pm
+++ b/lib/RT/Scrips.pm
@@ -391,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

commit 11b1cc3dc62306cf37ad32037c5e39c409994e5d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 22 16:05:14 2011 +0400

    allow to control join via LimitToQueue

diff --git a/lib/RT/Scrips.pm b/lib/RT/Scrips.pm
index 4304ed0..8844595 100644
--- a/lib/RT/Scrips.pm
+++ b/lib/RT/Scrips.pm
@@ -87,15 +87,15 @@ another call to this method
 
 sub LimitToQueue  {
     my $self = shift;
-    my $queue = shift;
-    return unless defined $queue;
+    my %args = @_%2? (Queue => @_) : @_;
+    return unless defined $args{'Queue'};
 
     my $alias = RT::ObjectScrips->new( $self->CurrentUser )
-        ->JoinTargetToThis( $self );
+        ->JoinTargetToThis( $self, %args );
     $self->Limit(
         ALIAS => $alias,
         FIELD => 'ObjectId',
-        VALUE => int $queue,
+        VALUE => int $args{'Queue'},
     );
 }
 

commit 013c038694384db1f3381a04b1220435acf19eed
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 22 16:05:43 2011 +0400

    pass through arguments in LimitToGlobal

diff --git a/lib/RT/Scrips.pm b/lib/RT/Scrips.pm
index 8844595..ca5bdbd 100644
--- a/lib/RT/Scrips.pm
+++ b/lib/RT/Scrips.pm
@@ -111,7 +111,7 @@ another call to this method or LimitToQueue
 
 sub LimitToGlobal  {
     my $self = shift;
-    return $self->LimitToQueue(0);
+    return $self->LimitToQueue(0, @_);
 }
 
 sub LimitToAdded {

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

    move Stage column into ObjectScrips table

diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index e570a4f..33787a9 100755
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -141,7 +141,6 @@ CREATE TABLE Scrips (
 	CustomIsApplicableCode	CLOB,
 	CustomPrepareCode	CLOB,
 	CustomCommitCode	CLOB,
-	Stage		VARCHAR2(32),
 	Template	NUMBER(11,0) DEFAULT 0 NOT NULL,
   	Creator 	NUMBER(11,0) DEFAULT 0 NOT NULL,
   	Created 	DATE,
@@ -154,6 +153,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,
 	Disabled	NUMBER(11,0) DEFAULT 0 NOT NULL,
diff --git a/etc/schema.Pg b/etc/schema.Pg
index 79a0158..94d89b2 100755
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -230,7 +230,6 @@ CREATE TABLE Scrips (
   CustomIsApplicableCode text NULL  ,
   CustomPrepareCode text NULL  ,
   CustomCommitCode text NULL  ,
-  Stage varchar(32) NULL  ,
   Template integer NOT NULL DEFAULT 0  ,
   Creator integer NOT NULL DEFAULT 0  ,
   Created TIMESTAMP NULL  ,
@@ -246,6 +245,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 'TrasactionCreate' ,
   ObjectId integer NOT NULL,
   SortOrder integer NOT NULL DEFAULT 0  ,
   Disabled integer NOT NULL DEFAULT 0 ,
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index 42eacf7..b48b431 100755
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -150,7 +150,6 @@ CREATE TABLE Scrips (
   CustomIsApplicableCode text NULL  ,
   CustomPrepareCode text NULL  ,
   CustomCommitCode text NULL  ,
-  Stage varchar(32) NULL  ,
   Template integer NULL DEFAULT 0 ,
   Creator integer NULL DEFAULT 0 ,
   Created DATETIME NULL  ,
@@ -164,6 +163,7 @@ CREATE TABLE Scrips (
 CREATE TABLE ObjectScrips (
   id INTEGER NOT NULL  ,
   Scrip int NOT NULL  ,
+  Stage varchar(32) NOT NULL DEFAULT 'TranslationCreate' ,
   ObjectId integer NOT NULL,
   SortOrder integer NOT NULL DEFAULT 0  ,
   Disabled int2 NOT NULL DEFAULT 0 ,
diff --git a/etc/schema.mysql b/etc/schema.mysql
index 3453621..e9fd3b1 100755
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -142,7 +142,6 @@ CREATE TABLE Scrips (
   CustomIsApplicableCode text NULL  ,
   CustomPrepareCode text NULL  ,
   CustomCommitCode text NULL  ,
-  Stage varchar(32) CHARACTER SET ascii NULL  ,
   Template integer NOT NULL DEFAULT 0  ,
   Creator integer NOT NULL DEFAULT 0  ,
   Created DATETIME NULL  ,
@@ -154,6 +153,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 'TranslationCreate',
   ObjectId integer NOT NULL,
   SortOrder integer NOT NULL DEFAULT 0  ,
   Disabled int2 NOT NULL DEFAULT 0 ,
diff --git a/lib/RT.pm b/lib/RT.pm
index 7e3da22..a881e72 100644
--- a/lib/RT.pm
+++ b/lib/RT.pm
@@ -464,7 +464,7 @@ sub InitClasses {
     if ( $args{'Heavy'} ) {
         # load scrips' modules
         my $scrips = RT::Scrips->new(RT->SystemUser);
-        $scrips->Limit( FIELD => 'Stage', OPERATOR => '!=', VALUE => 'Disabled' );
+        $scrips->LimitToEnabled;
         while ( my $scrip = $scrips->Next ) {
             local $@;
             eval { $scrip->LoadModules } or
diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index dab264f..d7be981 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -116,9 +116,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
 
@@ -200,6 +207,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 e7b36ed..d0a59e6 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -175,7 +175,6 @@ sub Create {
     my ( $id, $msg ) = $self->SUPER::Create(
         Template               => $template->Id,
         ScripCondition         => $condition->id,
-        Stage                  => $args{'Stage'},
         ScripAction            => $action->Id,
         Description            => $args{'Description'},
         CustomPrepareCode      => $args{'CustomPrepareCode'},
@@ -184,12 +183,20 @@ sub Create {
     );
     return ( $id, $msg ) unless $id;
 
-    unless ( $args{'Stage'} eq 'Disabled' ) {
-        my ($status, $msg) = RT::ObjectScrip->new( $self->CurrentUser )
-            ->Apply( Scrip => $self, ObjectId => $args{'Queue'} );
-        $RT::Logger->error( "Couldn't apply scrip: $msg" ) unless $status;
+    if ( $args{'Stage'} eq 'Disabled' ) {
+        $RT::Logger->warning("Disabled Stage is deprecated");
+        $args{'Stage'} = 'TransactionCreate';
+        $args{'Disabled'} = 1;
     }
 
+    (my $status, $msg) = RT::ObjectScrip->new( $self->CurrentUser )->Apply(
+        Scrip    => $self,
+        Stage    => $args{'Stage'},
+        ObjectId => $args{'Queue'},
+        Disabled => $args{'Disabled'},
+    );
+    $RT::Logger->error( "Couldn't apply scrip: $msg" ) unless $status;
+
     return ( $id, $self->loc('Scrip Created') );
 }
 
@@ -317,7 +324,27 @@ sub TemplateObj {
     return ( $self->{'TemplateObj'} );
 }
 
+=head2 Stage
+
+Takes TicketObj named argument and returns scrip's stage when
+applied 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}
@@ -404,16 +431,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;
@@ -906,24 +941,6 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Stage
-
-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).)
-
-
-=cut
-
-
 =head2 Template
 
 Returns the current value of Template.
@@ -1000,8 +1017,6 @@ 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 => ''},
         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 ca5bdbd..5a7dc81 100644
--- a/lib/RT/Scrips.pm
+++ b/lib/RT/Scrips.pm
@@ -126,6 +126,39 @@ sub LimitToNotAdded {
         ->LimitTargetToNotApplied( $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'},
+    );
+}
+
+=head2 LimitToEnabled
+
+Limits scrips to that are applied to any queue or globally
+and application is not disabled.
+
+=cut
+
+sub LimitToEnabled {
+    my $self = shift;
+
+    my $alias = RT::ObjectScrips->new( $self->CurrentUser )
+        ->JoinTargetToThis( $self );
+    $self->Limit(
+        ALIAS => $alias,
+        FIELD => 'Disabled',
+        VALUE => 0,
+    );
+}
+
 sub ApplySortOrder {
     my $self = shift;
     my $order = shift || 'ASC';
@@ -354,12 +387,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');
 

commit 3fe5c9be7d627bfd87f31691aa0f667b4d375b73
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 22 16:17:26 2011 +0400

    remove Stage column map

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 0a1464a..0718463 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2580,7 +2580,7 @@ Set(%AdminSearchResultFormat,
     Scrips =>
         q{'<a href="__AdminURL__">__id__</a>/TITLE:#'}
         .q{,'<a href="__AdminURL__">__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/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index d4c187b..334d6f9 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() },
-    },
 
     AdminURL => {
 	value     => sub {

commit 7fe5d954f77e3d008edff6365191b7b2b50a59d5
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 22 16:19:51 2011 +0400

    no more disabled stage

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'

commit f7f051e97c05bea5ccb0d9dd006b6df9a5550c4f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 02:21:13 2011 +0400

    typo, s/TranslationCreate/TransactionCreate/g

diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index b48b431..25b1a48 100755
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -163,7 +163,7 @@ CREATE TABLE Scrips (
 CREATE TABLE ObjectScrips (
   id INTEGER NOT NULL  ,
   Scrip int NOT NULL  ,
-  Stage varchar(32) NOT NULL DEFAULT 'TranslationCreate' ,
+  Stage varchar(32) NOT NULL DEFAULT 'TransactionCreate' ,
   ObjectId integer NOT NULL,
   SortOrder integer NOT NULL DEFAULT 0  ,
   Disabled int2 NOT NULL DEFAULT 0 ,
diff --git a/etc/schema.mysql b/etc/schema.mysql
index e9fd3b1..d9e9b73 100755
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -153,7 +153,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 'TranslationCreate',
+  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 ,

commit 48b67c06308e18ac48e6637f609afa591d972d1a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 02:21:58 2011 +0400

    we deleted GlobalScrips search format from the config

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;

commit 236d50be34f1f06f74af7774b5ece16380ced5a6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 02:22:45 2011 +0400

    _AppliedTo should check target, not self

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index 3d905b9..380dd26 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -220,7 +220,7 @@ sub _AppliedTo {
         LEFTJOIN => $alias,
         ALIAS    => $alias,
         FIELD    => $field,
-        VALUE    => $self->id,
+        VALUE    => $target->id,
     );
     return ($res, $alias);
 }

commit 2da496b2c2523773e8b7b8fe3cdaebb8537313ae
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 02:23:36 2011 +0400

    use LimitByStage instead of direct Limit
    
    Stage column has been moved to ObjectScrips table

diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index 410fa01..4db2987 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -106,7 +106,7 @@ if ( $id ) {
 my $find_scrips = sub {
     my %args = (Applied => 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{'Applied'}? 'LimitToAdded' : 'LimitToNotAdded';
     $scrips->$method(0, $id);

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

    we don't need separate list of global scrips
    
    global scrips are mixed with global in one list for sorting
    purposes

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 cf490fdc090b44834f7ff1a3974e4d8f0011e4aa
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 ea7d83e08b165bab313801c383760f856d029941
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 04:11:20 2011 +0400

    Move column map needs context

diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index 4db2987..025e7bc 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -102,6 +102,7 @@ if ( $id ) {
     $QueueObj->Load( $id );
     Abort(loc("Couldn't load queue #[_1]", $id)) unless $QueueObj->id;
 }
+$id ||= 0;
 
 my $find_scrips = sub {
     my %args = (Applied => 1, @_);
@@ -121,7 +122,7 @@ if ( $id ) {
 } else {
     $DisplayFormat = "__CheckBox.{RemoveScrip}__, $DisplayFormat";
 }
-$DisplayFormat .= ", __Move__";
+$DisplayFormat .= ", __Move.{$id}__";
 
 my %common_applied_args = (
     %ARGS,

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

    pass all arguments for tables with additional columns
    
    like Stage column in ObjectScrips table

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index 380dd26..ae93d60 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -89,8 +89,8 @@ sub Create {
 
     unless ( defined $args{'SortOrder'} ) {
         $args{'SortOrder'} = $self->NextSortOrder(
+            %args,
             $tfield  => $target,
-            ObjectId => $args{'ObjectId'},
         );
     }
     $args{'Disabled'} ||= 0;
@@ -129,7 +129,7 @@ sub Apply {
     }
 
     return $self->Create(
-        $field => $tid, ObjectId => $oid,
+        %args, $field => $tid, ObjectId => $oid,
     );
 }
 

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

    missing closing tag (</select>)

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 c5f09920b318231256f3bff4f542aa64ddf4f62d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 18:19:04 2011 +0400

    pass Stage argument when ObjectScrip created

diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index d7be981..c28f806 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -63,7 +63,7 @@ sub Create {
     my %args = (@_);
     return $self->SUPER::Create(
         map { $_ => $args{ $_ } } qw(
-            Scrip ObjectId
+            Scrip Stage ObjectId
             SortOrder Disabled
             Created Creator
             LastUpdated LastUpdatedBy
@@ -90,6 +90,7 @@ sub Neighbors {
     my %args = @_;
 
     my $res = $self->CollectionClass->new( $self->CurrentUser );
+    $res->Limit( FIELD => 'Stage', VALUE => $args{'Stage'} || $self->Stage );
     return $res;
 }
 

commit 88fd8dfbb50f8a51691301e8a4dcf5b29f0741da
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 d0a59e6..d149a4e 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -239,10 +239,10 @@ sub NotAddedTo {
 
 sub AddToObject {
     my $self = shift;
-    my $object = shift;
+    my %args = @_%2? (ObjectId => @_) : (@_);
 
     my $rec = RT::ObjectScrip->new( $self->CurrentUser );
-    return $rec->Apply( Scrip => $self, ObjectId => $object );
+    return $rec->Apply( %args, Scrip => $self );
 }
 
 sub RemoveFromObject {
diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index 025e7bc..b8da77c 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -86,6 +86,10 @@
 <p><i><&|/l&>(No scrips)</&></i></p>
 % }
 
+<div style="text-align:right">
+<% loc('Select stage for scrips you add') %>: <& SelectStage &>
+</div>
+
 <& /Elements/Submit,
     Name => 'AddScrips',
     Caption => loc("Apply selected scrips"),
@@ -151,7 +155,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;
     }
 }
@@ -168,5 +172,6 @@ $RemoveScrips => undef
 
 @AddScrip => ()
 $AddScrips => undef
+$Stage     => 'TransactionCreate'
 
 </%ARGS>

commit eb6f58ad9840580e354a3b6cd4f653ca7515e07b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 23 19:05:08 2011 +0400

    move scrips up and down

diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index b8da77c..beaa3c2 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -160,6 +160,24 @@ if ( $AddScrips ) {
     }
 }
 
+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;
+    }
+}
+
+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>
@@ -174,4 +192,7 @@ $RemoveScrips => undef
 $AddScrips => undef
 $Stage     => 'TransactionCreate'
 
+$MoveScripUp => undef
+$MoveScripDown => undef
+
 </%ARGS>
diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index 334d6f9..0002d0e 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -140,7 +140,7 @@ my $COLUMN_MAP = {
             my $context = $_[2] || 0;
             return '' unless $_[0]->IsAdded( $context );
 
-            my $name = 'MoveCustomField';
+            my $name = 'MoveScrip';
             my $args = $m->caller_args( 1 );
             my @pass = ref $args->{'PassArguments'}
                 ? @{$args->{'PassArguments'}}

commit cb24b01bee54ca9ee859a14230b864a4eb61ce51
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Dec 24 03:29:26 2011 +0400

    delete all applications when scrip is deleted

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index d149a4e..9a1b717 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -215,6 +215,8 @@ sub Delete {
         return ( 0, $self->loc('Permission Denied') );
     }
 
+    RT::ObjectScrip->new( $self->CurrentUser )->DeleteAll( Scrip => $self );
+
     return ( $self->SUPER::Delete(@_) );
 }
 

commit 413545e2c99c6173d53e65e769a805db0c815349
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Dec 24 03:58:09 2011 +0400

    only a few column maps can go as first thing in href

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 0718463..92cc510 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2578,8 +2578,8 @@ Set(%AdminSearchResultFormat,
         .q{,__AppliedTo__, __FriendlyType__, __FriendlyPattern__},
 
     Scrips =>
-        q{'<a href="__AdminURL__">__id__</a>/TITLE:#'}
-        .q{,'<a href="__AdminURL__">__Description__</a>/TITLE:Description'}
+        q{'<a href="__WebPath__/__AdminURL__">__id__</a>/TITLE:#'}
+        .q{,'<a href="__WebPath__/__AdminURL__">__Description__</a>/TITLE:Description'}
         .q{, __Condition__, __Action__, __Template__},
 
     Templates =>
diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index 0002d0e..72deb80 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -97,7 +97,7 @@ my $COLUMN_MAP = {
     AdminURL => {
 	value     => sub {
             my $queue = $_[0]->AppliedTo->First;
-            my $res = RT->Config->Get('WebPath') .'/Admin';
+            my $res = 'Admin';
             if ( $queue ) {
                 $res .= '/Queues/Scrip.html?Queue='. $queue->id .'&';
             } else {

commit ff1a1d632eb99d4f85c4325a3c768f85057be8dc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Dec 24 03:58:53 2011 +0400

    typo in method call

diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index 72deb80..d093f98 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -96,7 +96,7 @@ my $COLUMN_MAP = {
 
     AdminURL => {
 	value     => sub {
-            my $queue = $_[0]->AppliedTo->First;
+            my $queue = $_[0]->AddedTo->First;
             my $res = 'Admin';
             if ( $queue ) {
                 $res .= '/Queues/Scrip.html?Queue='. $queue->id .'&';

commit 159c4b2c79624deedff3c430a8ee3600885adaa2
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Dec 27 14:57:39 2011 +0400

    an unique index on ObjectScrips table

diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index 33787a9..039a646 100755
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -162,7 +162,7 @@ CREATE TABLE ObjectScrips (
 	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 94d89b2..5c2bab6 100755
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -258,6 +258,7 @@ CREATE TABLE ObjectScrips (
 
 );
 
+CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
 
 
 
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index 25b1a48..8a912ed 100755
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -174,6 +174,7 @@ CREATE TABLE ObjectScrips (
   LastUpdated DATETIME NULL  ,
   PRIMARY KEY (id)
 );
+CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
 
 --- {{{ ACL
 CREATE TABLE ACL (
diff --git a/etc/schema.mysql b/etc/schema.mysql
index d9e9b73..5e72bf9 100755
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -165,6 +165,7 @@ CREATE TABLE ObjectScrips (
   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,

commit 9b01206ab2d1ca47ea319a1345ed8f6f7ff7f726
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Dec 27 15:25:59 2011 +0400

    show stage selector only when we create a scrip

diff --git a/share/html/Admin/Elements/EditScrip b/share/html/Admin/Elements/EditScrip
index bd73b83..227cec2 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>
 </&>

commit 4740633e74961870eccb3c3ebfc6550cfc0bd2e7
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Dec 28 00:05:07 2011 +0400

    scrips disabling in the UI and API

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 9a1b717..46eccf0 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -348,6 +348,33 @@ sub Stage {
     return undef;
 }
 
+=head2 Disabled
+
+Returns the current value of Disabled. 
+
+=cut
+
+sub Disabled {
+    my $self = shift;
+    my $record = RT::ObjectScrip->new( $self->CurrentUser );
+    $record->LoadByCols( Scrip => $self->id );
+    return $record->Disabled;
+}
+
+=head2 SetDisabled
+
+Takes a boolean.
+1 will cause this scrip to no longer be avaialble for objects.
+0 will re-enable this field.
+
+=cut
+
+sub SetDisabled {
+    my $self = shift;
+    return RT::ObjectScrip->new( $self->CurrentUser )
+        ->SetDisabledOnAll( Scrip => $self->id, Value => shift );
+}
+
 
 =head2 Apply { TicketObj => undef, TransactionObj => undef}
 
diff --git a/share/html/Admin/Elements/EditScrip b/share/html/Admin/Elements/EditScrip
index 227cec2..9cd3cb4 100644
--- a/share/html/Admin/Elements/EditScrip
+++ b/share/html/Admin/Elements/EditScrip
@@ -87,6 +87,31 @@
 &></td></tr>
 % }
 
+% if ( $id ne 'new' && $added_to_any ) {
+<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>
+% }
+
+% if ( $id eq 'new' || $added_to_any  ) {
+<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 +172,18 @@ if ( $id ) {
     $SubmitLabel = loc('Save Changes');
 }
 
+my $EnabledChecked = qq[checked="checked"];
+my $added_to_any = 0;
+
 unless ( $id ) {
     $id = 'new';
     $SubmitLabel = loc('Create');
 }
+else {
+    my $disabled = $scrip->Disabled;
+    $added_to_any = 1 if defined $disabled;
+    $EnabledChecked = '' if $disabled;
+}
 
 my $min_lines = 10;
 
@@ -185,6 +218,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 +226,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 a490c5e8ef29fbc919dd1ed7743cd1af6481fd8f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Dec 28 00:06:25 2011 +0400

    Stage in the UI only available during creation

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 817b0c6881ad8c2e6080e17e4cc578210c7aae00
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Dec 28 02:03:42 2011 +0400

    new default order of script is different
    
    fix tests by re-ordering them

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 78fe0b3f277786071de0edf7c8e5868211c7a0e0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 30 01:30:00 2011 +0400

    update tests, scrip order no longer description based

diff --git a/t/api/scrip_order.t b/t/api/scrip_order.t
index 6fba7e5..d6b0ecc 100644
--- a/t/api/scrip_order.t
+++ b/t/api/scrip_order.t
@@ -3,51 +3,73 @@ 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 => 9;
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+ok $queue && $queue->id, 'loaded or created queue';
+
+{
+    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");
+}
+
+sub create_scrip_ok {
+    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 move_scrip_ok {
+    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";
+}
 
 

commit b94a3f9a955904597bd4fbdbe425dbc3651a82b5
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 4 15:29:50 2012 +0400

    more tests for scrips ordering

diff --git a/t/api/scrip_order.t b/t/api/scrip_order.t
index d6b0ecc..cf3c79d 100644
--- a/t/api/scrip_order.t
+++ b/t/api/scrip_order.t
@@ -3,11 +3,12 @@ use strict;
 use warnings;
 
 use RT;
-use RT::Test tests => 9;
+use RT::Test tests => 110;
 
 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",
@@ -40,6 +41,83 @@ ok $queue && $queue->id, 'loaded or created queue';
     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 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_B->id );
+
+    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 global scrip";
+{
+    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->move_scrip_ok( $scrips[0], 0, 'down' );
+    @scrips = @scrips[1, 2, 3, 0, 4];
+    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 {
     my $self = shift;
     my %args = (
@@ -59,6 +137,13 @@ sub create_scrip_ok {
     return $scrip;
 }
 
+sub delete_all_scrips {
+    my $self = shift;
+    my $scrips = RT::Scrips->new( RT->SystemUser );
+    $scrips->UnLimit;
+    $_->Delete foreach @{ $scrips->ItemsArrayRef };
+}
+
 sub move_scrip_ok {
     my $self = shift;
     my ($scrip, $queue, $dir) = @_;
@@ -72,4 +157,46 @@ sub move_scrip_ok {
     ok $status, "moved scrip $dir" or diag "error: $msg";
 }
 
+sub check_scrips_order {
+    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;
+        }
+    }
+}
+
 

commit d5b1bf70bed712333a36e4c26bd124009b5378fb
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 4 15:31:44 2012 +0400

    fix NextSortOrder result for global records
    
    when global records are added we were checking only
    other global records, but there can be a local record
    with bigger sort order number than the last applied
    global. Fix it by checking every neighbor for a new
    global application.
    
    It's ok to ignore neighbors for local applications
    as those can share sort order.

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index ae93d60..dd8a4dd 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -396,9 +396,21 @@ sub MoveDown {
 
 sub NextSortOrder {
     my $self = shift;
-    my $siblings = $self->Siblings( @_ );
-    $siblings->OrderBy( FIELD => 'SortOrder', ORDER => 'DESC' );
-    return 0 unless my $first = $siblings->First;
+    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;
 }
 

commit 1433314235691a65ba0523467434de5f0465dea2
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 4 15:41:16 2012 +0400

    don't always move applications when we delete
    
    when we delete a record that shares sort order
    with other record(s), we shouldn't move anything

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index dd8a4dd..ed315ba 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -229,12 +229,18 @@ sub Delete {
     my $self = shift;
 
     my $siblings = $self->Neighbors;
-    $siblings->LimitToObjectId( $self->ObjectId );
-    $siblings->Limit( FIELD => 'SortOrder', OPERATOR => '>', VALUE => $self->SortOrder );
+    $siblings->Limit( FIELD => 'SortOrder', OPERATOR => '>=', VALUE => $self->SortOrder );
+    $siblings->OrderBy( FIELD => 'SortOrder', ORDER => 'ASC' );
 
-    # Move everything below us up
     my $sort_order = $self->SortOrder;
     while (my $record = $siblings->Next) {
+        next if $record->id == $self->id;
+        return $self->SUPER::Delete if $self->SortOrder == $record->SortOrder;
+        last;
+    }
+
+    # Move everything below us up
+    foreach my $record ( @{ $siblings->ItemsArrayRef } ) {
         $record->SetSortOrder($record->SortOrder - 1);
     }
 

commit 8247166bcaa504813224fd5eb15c8dacb80a8d48
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 4 23:27:47 2012 +0400

    more tests around scrips' order

diff --git a/t/api/scrip_order.t b/t/api/scrip_order.t
index cf3c79d..cf5a827 100644
--- a/t/api/scrip_order.t
+++ b/t/api/scrip_order.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 use RT;
-use RT::Test tests => 110;
+use RT::Test tests => 204;
 
 my $queue = RT::Test->load_or_create_queue( Name => 'General' );
 ok $queue && $queue->id, 'loaded or created queue';
@@ -70,6 +70,32 @@ note "move around two local scrips";
     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();
@@ -77,14 +103,30 @@ note "move local scrip below global";
     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 );
+    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 global scrip";
+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;
@@ -93,12 +135,29 @@ note "move global scrip";
     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();
@@ -109,7 +168,6 @@ note "delete scrips one by one";
     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 ) {
@@ -119,6 +177,7 @@ note "delete scrips one by one";
 }
 
 sub create_scrip_ok {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
     my $self = shift;
     my %args = (
         ScripCondition => 'On Create',
@@ -138,6 +197,7 @@ sub create_scrip_ok {
 }
 
 sub delete_all_scrips {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
     my $self = shift;
     my $scrips = RT::Scrips->new( RT->SystemUser );
     $scrips->UnLimit;
@@ -145,6 +205,7 @@ sub delete_all_scrips {
 }
 
 sub move_scrip_ok {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
     my $self = shift;
     my ($scrip, $queue, $dir) = @_;
 
@@ -158,6 +219,7 @@ sub move_scrip_ok {
 }
 
 sub check_scrips_order {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
     my $self = shift;
     my $scrips = shift;
     my $queues = shift;
@@ -199,4 +261,13 @@ sub check_scrips_order {
     }
 }
 
+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"
+    );
+
+}
+
 

commit 7a09992c1f9002b2d8b9bab37f7ccdc50ab2b5c7
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 4 23:28:40 2012 +0400

    generalize, fix and comment Move{Up,Down} methods
    
    Split moving into three primary cases:
    1) global moving
    2) local with local
    3) local with global
    
    First case was broken and other had corner cases.
    Comment every branch of code. Use one method for
    moving up and down.

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index ed315ba..f6b82c6 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -300,52 +300,7 @@ Moves scrip up. See </Sorting scrips applications>.
 
 =cut
 
-sub MoveUp {
-    my $self = shift;
-
-    my $siblings = $self->Siblings;
-    $siblings->Limit( FIELD => 'SortOrder', OPERATOR => '<', VALUE => $self->SortOrder );
-    $siblings->OrderByCols( { FIELD => 'SortOrder', ORDER => 'DESC' } );
-
-    my @above = ($siblings->Next, $siblings->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 scrip");
-        }
-    }
-    elsif ( $above[1] && $above[0]->SortOrder == $above[1]->SortOrder + 1 ) {
-        my $move_siblings = $self->Neighbors;
-        $move_siblings->Limit(
-            FIELD => 'SortOrder',
-            OPERATOR => '>=',
-            VALUE => $above[0]->SortOrder,
-        );
-        $move_siblings->OrderByCols( { FIELD => 'SortOrder', ORDER => 'DESC' } );
-        while ( my $record = $move_siblings->Next ) {
-            my ($status, $msg) = $record->SetSortOrder( $record->SortOrder + 1 );
-            unless ( $status ) {
-                return (0, "Couldn't move scrip");
-            }
-        }
-        $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 scrip");
-    }
-
-    return (1,"Moved scrip up");
-}
+sub MoveUp { return shift->Move( Up => @_ ) }
 
 =head3 MoveDown
 
@@ -353,51 +308,98 @@ Moves scrip down. See </Sorting scrips applications>.
 
 =cut
 
-sub MoveDown {
+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 => '>', VALUE => $self->SortOrder );
-    $siblings->OrderByCols( { FIELD => 'SortOrder', ORDER => 'ASC' } );
-
-    my @below = ($siblings->Next, $siblings->Next);
-    unless ($below[0]) {
-        return (0, "Can not move down. It's already at the bottom");
+    $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;
-    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 scrip");
+    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'};
         }
     }
-    elsif ( $below[1] && $below[0]->SortOrder + 1 == $below[1]->SortOrder ) {
-        my $move_siblings = $self->Neighbors;
-        $move_siblings->Limit(
-            FIELD => 'SortOrder',
-            OPERATOR => '<=',
-            VALUE => $below[0]->SortOrder,
-        );
-        $move_siblings->OrderByCols( { FIELD => 'SortOrder', ORDER => 'ASC' } );
-        while ( my $record = $move_siblings->Next ) {
-            my ($status, $msg) = $record->SetSortOrder( $record->SortOrder - 1 );
-            unless ( $status ) {
-                return (0, "Couldn't move scrip");
-            }
+
+    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;
         }
-        $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 scrip");
+        return (0, "Couldn't move: $msg");
     }
 
-    return (1,"Moved scrip down");
+    return (1,"Moved");
 }
 
 sub NextSortOrder {
@@ -420,6 +422,16 @@ sub NextSortOrder {
     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;

commit 97686f933bb7f57c6dc23fe679ed16aad5c9db35
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 4 23:35:37 2012 +0400

    use IsSortOrderShared in Delete
    
    current version has some tiny performance benefits,
    win is so small that it doesn't worth code complexity.

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index f6b82c6..b638751 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -228,23 +228,17 @@ sub _AppliedTo {
 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' );
-
-    my $sort_order = $self->SortOrder;
-    while (my $record = $siblings->Next) {
-        next if $record->id == $self->id;
-        return $self->SUPER::Delete if $self->SortOrder == $record->SortOrder;
-        last;
-    }
-
-    # Move everything below us up
     foreach my $record ( @{ $siblings->ItemsArrayRef } ) {
         $record->SetSortOrder($record->SortOrder - 1);
     }
 
-    $self->SUPER::Delete;
+    return $self->SUPER::Delete;
 }
 
 sub DeleteAll {

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

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

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 46eccf0..a02c722 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -243,16 +243,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->Apply( %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 applied') ) unless $rec->id;
     return $rec->Delete;
 }

commit bb348a219318021404d042a7c04209e42d9f2ade
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..147dc79 100644
--- a/t/api/scrip.t
+++ b/t/api/scrip.t
@@ -4,46 +4,39 @@ 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 f1a0798b5e195d410d0197c7c068a2c046c7f719
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 9 17:55:04 2012 +0400

    check that template exists when we apply scrip

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index a02c722..ce3f1a4 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -256,6 +256,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->Apply( %args, Scrip => $self );
 }

commit 00cc2c2b29fedfb00f3092785f7dea3f4c2934d6
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 147dc79..df15599 100644
--- a/t/api/scrip.t
+++ b/t/api/scrip.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 use RT;
-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';
@@ -39,7 +39,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(
@@ -110,3 +110,82 @@ 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',
+    );
+    ok($status, 'created a scrip') or diag "error: $msg";
+    main->check_applications($scrip, [$queue], [0, $queue_B]);
+
+    ($status, $msg) = $scrip->AddToObject( $queue_B->id );
+    ok(!$status, $msg);
+    main->check_applications($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');
+    main->check_applications($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);
+    main->check_applications($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');
+    main->check_applications($scrip, [$queue, $queue_B], [0]);
+}
+
+sub check_applications {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    my $self = shift;
+    my $scrip = shift;
+    my $to = shift || [];
+    my $not_to = shift || [];
+
+    ok($scrip->IsAdded(ref $_? $_->id : $_), 'added to queue' ) foreach @$to;
+    ok(!$scrip->IsAdded(ref $_? $_->id : $_), 'not added' ) foreach @$not_to;
+    is_deeply(
+        [sort map $_->id, @{ $scrip->AddedTo->ItemsArrayRef }],
+        [sort grep $_, map ref $_? $_->id : $_, @$to],
+        'correct list of queues',
+    );
+    is_deeply(
+        [sort map $_->id, @{ $scrip->NotAddedTo->ItemsArrayRef }],
+        [sort grep $_, map ref $_? $_->id : $_, @$not_to],
+        'correct list of queues',
+    );
+}
+

commit 77868eb48197188387f0805307a5e1b4be1d6bed
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 9 17:56:17 2012 +0400

    not sure why, undef is not replaced with default

diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index c28f806..676354d 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -61,6 +61,7 @@ sub ObjectCollectionClass {'RT::Queues'}
 sub Create {
     my $self = shift;
     my %args = (@_);
+    $args{'Stage'} ||= 'TransactionCreate'; #XXX: why don't we turn undef into default?
     return $self->SUPER::Create(
         map { $_ => $args{ $_ } } qw(
             Scrip Stage ObjectId

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

    scrip->IsGlobal method, alias to ->IsAdded(0)

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index ce3f1a4..a62ffe2 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -220,6 +220,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/Elements/EditScrip b/share/html/Admin/Elements/EditScrip
index 9cd3cb4..b1a0974 100644
--- a/share/html/Admin/Elements/EditScrip
+++ b/share/html/Admin/Elements/EditScrip
@@ -89,7 +89,7 @@
 
 % if ( $id ne 'new' && $added_to_any ) {
 <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__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index d093f98..d0dbf39 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -117,7 +117,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 };

commit 730f7c8789bf31c4d65897fbe202d97fd826db1a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 12 00:45:22 2012 +0400

    comment unexpected code

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index a62ffe2..c52e58b 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -396,6 +396,9 @@ Returns the current value of Disabled.
 
 sub Disabled {
     my $self = shift;
+    # we check only one record as at this moment all applications
+    # should be disabled or none, this method should be dropped when
+    # we implement full functionality
     my $record = RT::ObjectScrip->new( $self->CurrentUser );
     $record->LoadByCols( Scrip => $self->id );
     return $record->Disabled;

commit b5cffecad2d0effe5602ddfe098114aafed30ae6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 12 00:58:55 2012 +0400

    disable many applications under DB's transaction

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index b638751..9e7d623 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -268,10 +268,15 @@ sub SetDisabledOnAll {
 
     my $list = $self->CollectionClass->new( $self->CurrentUser );
     $list->Limit( FIELD => $field, VALUE => $id );
+    $RT::Handle->BeginTransaction;
     foreach ( @{ $list->ItemsArrayRef } ) {
         my ($status, $msg) = $_->SetDisabled( $args{Value} || 0 );
-        return ($status, $msg) unless $status;
+        unless ( $status ) {
+            $RT::Handle->Rollback;
+            return ($status, $msg);
+        }
     }
+    $RT::Handle->Commit;
     return (1, $self->loc("Disabled all applications") );
 }
 

commit ceccd9e635f4ccfd4b73e889eb3f5835411508a2
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 12 00:59:38 2012 +0400

    very basic tests for disabling scrips

diff --git a/t/api/scrip.t b/t/api/scrip.t
index df15599..a8437f7 100644
--- a/t/api/scrip.t
+++ b/t/api/scrip.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 use RT;
-use RT::Test tests => 61;
+use RT::Test tests => 66;
 
 my $queue = RT::Test->load_or_create_queue( Name => 'General' );
 ok $queue && $queue->id, 'loaded or created queue';
@@ -168,6 +168,27 @@ note 'check applications vs. templates';
     main->check_applications($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,
+        ScripAction    => 'User Defined',
+        ScripCondition => 'User Defined',
+        Template       => 'Blank',
+    );
+    ok($status, "created scrip");
+    is($scrip->Disabled, 0, "not disabled");
+
+    ($status,$msg) = $scrip->SetDisabled(1);
+    is($scrip->Disabled, 1, "disabled");
+
+    ($status, $msg) = $scrip->RemoveFromObject( $queue->id );
+    ok($status, 'removed scrip from queue');
+
+    is($scrip->Disabled, undef, "not applied");
+}
+
 sub check_applications {
     local $Test::Builder::Level = $Test::Builder::Level + 1;
     my $self = shift;

commit 89c567d97d8c3ee56f79d8b22a8ca2aa5f29792e
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jan 11 16:17:14 2012 -0500

    Typo'd default Stage value in the Postgres schema

diff --git a/etc/schema.Pg b/etc/schema.Pg
index 5c2bab6..b4742fc 100755
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -245,7 +245,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 'TrasactionCreate' ,
+  Stage varchar(32) NOT NULL DEFAULT 'TransactionCreate' ,
   ObjectId integer NOT NULL,
   SortOrder integer NOT NULL DEFAULT 0  ,
   Disabled integer NOT NULL DEFAULT 0 ,

commit 148f1b770525ba1400a4af2e5311e7658924443f
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 beaa3c2..aad378b 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 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 ce9a1e797f5aa4ef35bfb8bed36a8d95f92fcf3d
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/EditScrips b/share/html/Admin/Elements/EditScrips
index aad378b..b72a645 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -51,7 +51,7 @@
 <input type="hidden" class="hidden" name="id" value="<% $id %>" />
 
 <h2><&|/l&>Scrips</&></h2>
-<div class="admin-hint">Scrips run after each individual change to a ticket.</div>
+<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 ) {
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 83f55bd56986e131d667631eb8c2995e396d7b1e
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..a31ebe5
--- /dev/null
+++ b/share/html/Admin/Scrips/index.html
@@ -0,0 +1,70 @@
+%# 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->UnLimit;
+
+$m->callback(CallbackName => 'Massage', Scrips => $scrips);
+
+$Format ||= RT->Config->Get('AdminSearchResultFormat')->{'Scrips'};
+</%INIT>
\ No newline at end of file
diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index 015c2e3..70556e6 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 ca75c7b8527cc6ac653a83763add044b5d53e65c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 13 21:46:32 2012 +0400

    Move scrip editor into one place

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

commit 9252347394549c6ef0b5a99e042f30ca9c77fbe8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 13 21:49:12 2012 +0400

    we don't need AdminURL column map

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 92cc510..ea780b6 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2578,8 +2578,8 @@ Set(%AdminSearchResultFormat,
         .q{,__AppliedTo__, __FriendlyType__, __FriendlyPattern__},
 
     Scrips =>
-        q{'<a href="__WebPath__/__AdminURL__">__id__</a>/TITLE:#'}
-        .q{,'<a href="__WebPath__/__AdminURL__">__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{, __Condition__, __Action__, __Template__},
 
     Templates =>
diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index d0dbf39..d90a536 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -93,20 +93,6 @@ my $COLUMN_MAP = {
 	attribute => 'Description',
 	value     => sub { return $_[0]->Description() },
     },
-
-    AdminURL => {
-	value     => sub {
-            my $queue = $_[0]->AddedTo->First;
-            my $res = 'Admin';
-            if ( $queue ) {
-                $res .= '/Queues/Scrip.html?Queue='. $queue->id .'&';
-            } else {
-                $res .= '/Global/Scrip.html?';
-            }
-            $res .= 'id='. $_[0]->id;
-            return $res;
-        },
-    },
     RemoveCheckBox => {
         title => sub {
             my $name = 'RemoveScrip';

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

    turn EditScrip into Create and Modify html comps
    
    EditScrip was used in two places, now it's one, so
    it's not required.
    
    Create and Modify are different for scrips, so we
    split them up and use smaller components for common
    ports.

diff --git a/share/html/Admin/Elements/EditScrip b/share/html/Admin/Elements/EditScrip
deleted file mode 100644
index b1a0974..0000000
--- a/share/html/Admin/Elements/EditScrip
+++ /dev/null
@@ -1,243 +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="Scrip.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' && $added_to_any ) {
-<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>
-% }
-
-% if ( $id eq 'new' || $added_to_any  ) {
-<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"];
-my $added_to_any = 0;
-
-unless ( $id ) {
-    $id = 'new';
-    $SubmitLabel = loc('Create');
-}
-else {
-    my $disabled = $scrip->Disabled;
-    $added_to_any = 1 if defined $disabled;
-    $EnabledChecked = '' if $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..c301125
--- /dev/null
+++ b/share/html/Admin/Scrips/Create.html
@@ -0,0 +1,137 @@
+%# 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 &>
+
+<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"},
+
+        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>
\ No newline at end of file
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..4bc8ceb 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Modify.html
@@ -45,19 +45,96 @@
 %# 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 ( $added_to_any ) {
+<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>
+% }
+
+% if ( $added_to_any  ) {
+<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>
+use Data::Dumper;
+print STDERR Dumper \%ARGS;
+
+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;
+my $added_to_any = 0;
+$added_to_any = 1 if defined $disabled;
+
+if ( $Update ) {
+    my @attribs = qw(
+        Description
+        ScripAction ScripCondition Template
+        CustomPrepareCode CustomCommitCode CustomIsApplicableCode
+    );
+    if ( $ARGS{'SetEnabled'} && $added_to_any ) {
+        push @attribs, 'Disabled';
+        $ARGS{"Disabled"} = $ARGS{"Enabled"}? 0 : 1;
+    }
+    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 70556e6..9558d90 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 =>

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

    update validator, check 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 c3bd42fda863858c3fc9850fa336ce72abbd7ca9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 20 18:49:22 2012 +0400

    change Create links for scrips

diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index 9558d90..033c6b5 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -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/Global/Scrip.html?Create=1" );
+    $scrips->child( create => title => loc('Create'), path => "/Admin/Scrip/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/Queues/Scrip.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 );

commit ee2c322607fa1e43bb3bf16025d21fa6a3acc707
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/Objects.html b/share/html/Admin/Scrips/Objects.html
new file mode 100644
index 0000000..5c900b0
--- /dev/null
+++ b/share/html/Admin/Scrips/Objects.html
@@ -0,0 +1,145 @@
+%# 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 &>
+
+<form action="Objects.html" method="post">
+<input type="hidden" class="hidden" name="id" value="<% $id %>" />
+
+% 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 {
+<h2><% loc('Stage') %></h2>
+<& /Admin/Elements/SelectStage, Default => $Stage &>
+
+<h2><&|/l&>Apply globally</&></h2>
+
+<input type="checkbox" name="AddScrip-<% $id %>" value="0" />
+<&|/l&>check this box to apply this scrip to all objects.</&>
+
+<h2><&|/l&>Selected objects</&></h2>
+<& /Elements/CollectionList,
+    OrderBy => 'id',
+    Order => 'ASC',
+    %ARGS,
+    Collection => $applied,
+    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),
+    ],
+&>
+
+<h2><&|/l&>Unselected objects</&></h2>
+<& /Elements/CollectionList,
+    OrderBy => 'Name',
+    Order   => 'ASC',
+    %ARGS,
+    Collection    => $not_applied,
+    Rows          => 50,
+    Format        => $format,
+    DisplayFormat => "'__CheckBox.{AddScrip-". $id ."}__',". $format,
+    AllowSorting  => 1,
+    ShowEmpty     => 0,
+    PassArguments => [
+        qw(id Stage Format Rows Page Order OrderBy),
+    ],
+&>
+
+% }
+
+<& /Elements/Submit, Name => 'Update' &>
+</form>
+
+<%ARGS>
+$id => undef
+$Stage => undef
+$Update => 0
+</%ARGS>
+<%INIT>
+my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
+$scrip->Load($id) or Abort(loc("Could not load scrip #[_1]", $id));
+$id = $scrip->id;
+
+if ( $Update ) {
+    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;
+        }
+    }
+    MaybeRedirectForResults(
+        Actions   => \@results,
+        Arguments => { id => $id },
+    );
+}
+
+my $is_global = $scrip->IsGlobal;
+
+my $applied = $scrip->AddedTo;
+my $not_applied = $scrip->NotAddedTo;
+
+my $format = RT->Config->Get('AdminSearchResultFormat')->{'Queues'};
+
+my $title = loc('Modify associated objects for scrip #[_1]', $id);
+
+</%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 033c6b5..e6c0370 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( 'added-to' => title => loc('Added to'), path => "/Admin/Scrips/Objects.html?id=" . $id );
+        }
+    }
+
     if ( $request_path =~ m{^/Admin/Global/(Scrip|Template)s?\.html} ) {
         my $type = $1;
         my $tabs = PageMenu();

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

    delete debugging statements

diff --git a/share/html/Admin/Scrips/Modify.html b/share/html/Admin/Scrips/Modify.html
index 4bc8ceb..0c8ed76 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Modify.html
@@ -98,9 +98,6 @@ $id     => undef
 $Update => undef
 </%ARGS>
 <%INIT>
-use Data::Dumper;
-print STDERR Dumper \%ARGS;
-
 my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
 $scrip->Load( $id );
 Abort(loc("Couldn't load scrip #[_1]", $id))

commit 91179c858d1ecb52ad3edc0275cfa4a43fd1b06e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 20 21:47:03 2012 +0400

    link a few pages right from inside Modify.html

diff --git a/share/html/Admin/Scrips/Modify.html b/share/html/Admin/Scrips/Modify.html
index 0c8ed76..e6766c8 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Modify.html
@@ -58,15 +58,17 @@
 <& Elements/EditBasics, %ARGS, Scrip => $scrip &>
 
 % if ( $added_to_any ) {
-<tr><td class="label"><&|/l&>Added</&>:</td><td class="value">\
+<tr><td class="label"><a href="Objects.html?id=<% $id %>"><&|/l&>Added</&></a>:</td>
+<td class="value">\
 % if ( $scrip->IsGlobal ) {
-<% loc('Global') %>
+<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++;
-<% $queue->Name %>
+<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;

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

    new templates picker that accounts 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/Scrips/Elements/EditBasics b/share/html/Admin/Scrips/Elements/SelectTemplate
similarity index 62%
copy from share/html/Admin/Scrips/Elements/EditBasics
copy to share/html/Admin/Scrips/Elements/SelectTemplate
index d7746b2..1148e42 100644
--- a/share/html/Admin/Scrips/Elements/EditBasics
+++ b/share/html/Admin/Scrips/Elements/SelectTemplate
@@ -45,29 +45,56 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<tr><td class="label"><&|/l&>Description</&>:</td><td class="value">\
-<input name="Description" \
-    size="60" \
-    value="<% $ARGS{"Description"} || $Scrip->Description || '' %>" />
-</td></tr>
-
-<tr><td class="label"><&|/l&>Condition</&>:</td><td class="value">\
-<& /Admin/Elements/SelectScripCondition,
-    Default => $ARGS{"ScripCondition"} || $Scrip->ConditionObj->Id,
-&></td></tr>
-
-<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>
+<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>
-$Scrip
+$Name => 'Template'
+$Queue => undef
+$Scrip => undef
+$Default => undef
 </%ARGS>
 <%INIT>
+
+my $current;
+$current = $Scrip->TemplateObj->Name if $Scrip;
+
+my $global = RT::Templates->new($session{'CurrentUser'});
+$global->LimitToGlobal;
+
+my %global;
+$global{ lc $_ } = $_ foreach map $_->Name, @{ $global->ItemsArrayRef };
+
+my @queues;
+if ( $Scrip && $Scrip->id ) {
+    @queues = @{ $Scrip->AddedTo->ItemsArrayRef };
+}
+elsif ( $Queue && $Queue->id ) {
+    @queues = ($Queue);
+}
+
+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>

commit 91288fb145b50f9294e29f83ed8f1bdfb7c7138a
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 e058b4b517a3e38854028a97c6cf9772193fd2a6
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Jan 23 16:07:42 2012 -0500

    Fix typoed URL for Configuration → Global → Scrips → Create

diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index e6c0370..a4351d2 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -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/Scrip/Create.html" );
+    $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html" );
 
     my $templates = $admin_global->child( templates =>
         title       => loc('Templates'),

commit 6bb18736a54fb57a85fdaac077dbad1724171d12
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 672bc168cd38a2a716d054e6bc4def8f613b7f88
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 24 00:59:57 2012 +0400

    fix Scrip's tabs

diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index a4351d2..54dbc4d 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -374,15 +374,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/Create.html" );
+    }
 
-        # 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/} ) {

commit 5e2a9c4ea0740f7ba4a27d9f4b53b6afd9391e75
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 24 01:00:38 2012 +0400

    fix walk.t, page has been moved

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 f1560111a107b940f39217a91158b984edb96980
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 24 01:22:53 2012 +0400

    fix tests, we changed admin UI for scrips

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 55087df75bb90fd629864fdf01c7c8478efef344
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 24 02:03:58 2012 +0400

    we have to pass queue obj as it's expected

diff --git a/share/html/Admin/Scrips/Create.html b/share/html/Admin/Scrips/Create.html
index c301125..07ff3dd 100644
--- a/share/html/Admin/Scrips/Create.html
+++ b/share/html/Admin/Scrips/Create.html
@@ -55,7 +55,7 @@
 <&| /Widgets/TitleBox, title => loc('Basics') &>
 <table>
 
-<& Elements/EditBasics, %ARGS, Scrip => $scrip &>
+<& 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>

commit f27cad6fa0668ac7ac2a97ec882a11425e69dda4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 24 02:04:58 2012 +0400

    update tests acording to recent UI changes

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');

commit 4d16d4d6d39be2bd300c7b822bcb10618eae0274
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/t/web/scrips.t b/t/web/scrips.t
index 855029a..a9f8e7d 100644
--- a/t/web/scrips.t
+++ b/t/web/scrips.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 14;
+use RT::Test tests => 41;
 
 # TODO:
 # Test the rest of the conditions.
@@ -101,3 +101,51 @@ 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");
+}
+

commit cfbb3e6d9726c4815212b4d50cd594514f6d3f78
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 24 20:32:08 2012 +0400

    treat empty value as no update

diff --git a/share/html/Admin/Scrips/Modify.html b/share/html/Admin/Scrips/Modify.html
index e6766c8..0672d22 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Modify.html
@@ -112,9 +112,10 @@ $added_to_any = 1 if defined $disabled;
 if ( $Update ) {
     my @attribs = qw(
         Description
-        ScripAction ScripCondition Template
         CustomPrepareCode CustomCommitCode CustomIsApplicableCode
     );
+    push @attribs, grep defined $ARGS{$_} && length $ARGS{$_},
+        qw(ScripAction ScripCondition Template);
     if ( $ARGS{'SetEnabled'} && $added_to_any ) {
         push @attribs, 'Disabled';
         $ARGS{"Disabled"} = $ARGS{"Enabled"}? 0 : 1;

commit a81aa7fddd9a8496c74bccb9cbe1606c4f8db297
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 24 22:42:55 2012 +0400

    make sure limiting by stage works with OID limit

diff --git a/lib/RT/SearchBuilder/ApplyAndSort.pm b/lib/RT/SearchBuilder/ApplyAndSort.pm
index 49d65f9..2b3d4b8 100644
--- a/lib/RT/SearchBuilder/ApplyAndSort.pm
+++ b/lib/RT/SearchBuilder/ApplyAndSort.pm
@@ -102,6 +102,7 @@ sub LimitTargetToNotApplied {
         OPERATOR => 'IS',
         VALUE    => 'NULL',
     );
+    return $alias;
 }
 
 =head2 LimitTargetToApplied
@@ -125,6 +126,7 @@ sub LimitTargetToApplied {
         OPERATOR => 'IS NOT',
         VALUE    => 'NULL',
     );
+    return $alias;
 }
 
 sub JoinTargetToApplied {
@@ -132,7 +134,7 @@ sub JoinTargetToApplied {
     my $collection = shift;
     my @ids = @_;
 
-    my $alias = $self->JoinTargetToThis( $collection, New => 1, Left => 1 );
+    my $alias = $self->JoinTargetToThis( $collection, New => 0, Left => 1 );
     return $alias unless @ids;
 
     # XXX: we need different EA in join clause, but DBIx::SB
diff --git a/share/html/Admin/Scrips/Objects.html b/share/html/Admin/Scrips/Objects.html
index 5c900b0..8d67804 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 a9f8e7d..0f745cd 100644
--- a/t/web/scrips.t
+++ b/t/web/scrips.t
@@ -1,7 +1,9 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 41;
+use RT::Test tests => 54;
+
+RT->Config->Set( UseTransactionBatch => 1 );
 
 # TODO:
 # Test the rest of the conditions.
@@ -149,3 +151,38 @@ note "check basics in scrip's admin interface";
     $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 => 'Added 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 78c5c7ad7fde3263954c50af2099dde7637e212d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 25 01:10:18 2012 +0400

    account scrip and queue if both passed in templates selector
    
    Further limits choice of templates and would be handy if
    next step is to apply the scrip to the queue.

diff --git a/share/html/Admin/Scrips/Elements/SelectTemplate b/share/html/Admin/Scrips/Elements/SelectTemplate
index 1148e42..3bcb212 100644
--- a/share/html/Admin/Scrips/Elements/SelectTemplate
+++ b/share/html/Admin/Scrips/Elements/SelectTemplate
@@ -72,12 +72,8 @@ my %global;
 $global{ lc $_ } = $_ foreach map $_->Name, @{ $global->ItemsArrayRef };
 
 my @queues;
-if ( $Scrip && $Scrip->id ) {
-    @queues = @{ $Scrip->AddedTo->ItemsArrayRef };
-}
-elsif ( $Queue && $Queue->id ) {
-    @queues = ($Queue);
-}
+push @queues, @{ $Scrip->AddedTo->ItemsArrayRef } if $Scrip && $Scrip->id;
+push @queues, $Queue if $Queue && $Queue->id;
 
 my (%names, %counters);
 foreach my $queue ( @queues ) {

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

    upgrade script for scrips on mysql

diff --git a/etc/upgrade/4.1.1/content b/etc/upgrade/4.1.1/content
new file mode 100644
index 0000000..5f55bfd
--- /dev/null
+++ b/etc/upgrade/4.1.1/content
@@ -0,0 +1,34 @@
+use strict; use warnings;
+
+our @Initial = (
+    sub {
+        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->OrderBy(
+                { 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.mysql b/etc/upgrade/4.1.1/schema.mysql
new file mode 100644
index 0000000..c9061a7
--- /dev/null
+++ b/etc/upgrade/4.1.1/schema.mysql
@@ -0,0 +1,38 @@
+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;
+
+INSERT INTO ObjectScrips(
+    Scrip, Stage, ObjectId,
+    Creator, Created, LastUpdatedBy, LastUpdated
+)
+SELECT id, Stage, Queue, Creator, Created, LastUpdatedBy, LastUpdated
+FROM Scrips
+;
+
+UPDATE ObjectScrips SET Disabled = 1 WHERE Scrip IN (
+    SELECT id FROM Scrips 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;
+
+ALTER TABLE ObjectCustomFields ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
+UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
+    SELECT id FROM CustomFields WHERE Disabled != 0
+);
+ALTER TABLE CustomFields DROP COLUMN Disabled;

commit b40a3648e99dc5cb56768db9cbca29fda0dc91e9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 31 02:08:21 2012 +0400

    we don't use ListGlobalScrips anymore
    
    no need in keeping it around

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>

commit 4eddc2e82b0771856e74b0deb499901ae49c4ff1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Feb 1 14:39:56 2012 +0400

    don't load RT in tests before ::Test module

diff --git a/t/api/scrip.t b/t/api/scrip.t
index a8437f7..4b709f0 100644
--- a/t/api/scrip.t
+++ b/t/api/scrip.t
@@ -1,7 +1,6 @@
 
 use strict;
 use warnings;
-use RT;
 use RT::Test tests => 66;
 
 my $queue = RT::Test->load_or_create_queue( Name => 'General' );
diff --git a/t/api/scrip_order.t b/t/api/scrip_order.t
index cf5a827..caa6d95 100644
--- a/t/api/scrip_order.t
+++ b/t/api/scrip_order.t
@@ -2,7 +2,6 @@
 use strict;
 use warnings;
 
-use RT;
 use RT::Test tests => 204;
 
 my $queue = RT::Test->load_or_create_queue( Name => 'General' );

commit ac4c9a1c77d84caea271a0cdae2367e7e61211ec
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Feb 2 12:27:39 2012 +0400

    load module we need

diff --git a/etc/upgrade/4.1.1/content b/etc/upgrade/4.1.1/content
index 5f55bfd..7622502 100644
--- a/etc/upgrade/4.1.1/content
+++ b/etc/upgrade/4.1.1/content
@@ -2,6 +2,7 @@ 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 );

commit 33ea1110656a07a2aaab9398c17596ca1c66fb9f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Feb 2 12:28:00 2012 +0400

    we need OrderByCols here, not OrderBy

diff --git a/etc/upgrade/4.1.1/content b/etc/upgrade/4.1.1/content
index 7622502..02d1321 100644
--- a/etc/upgrade/4.1.1/content
+++ b/etc/upgrade/4.1.1/content
@@ -10,7 +10,7 @@ our @Initial = (
                 FIELD1 => 'Scrip',
                 TABLE2 => 'Scrips', FIELD2 => 'id'
             );
-            $applications->OrderBy(
+            $applications->OrderByCols(
                 { ALIAS => $alias, FIELD => 'Description', ORDER => 'ASC' },
             );
             my %h; my $top_so = $h{0} = 0;

commit dd7728b9fed0ae30243f58fa86017e8c6a7b1f1e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Feb 3 22:42:08 2012 +0400

    use "applies" in the UI
    
    Apply is used in scrips' API already, so we use add there,
    but in UI to be similar to CF UI we should use 'applies'.

diff --git a/share/html/Admin/Scrips/Modify.html b/share/html/Admin/Scrips/Modify.html
index 0672d22..c83e27e 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Modify.html
@@ -58,7 +58,7 @@
 <& Elements/EditBasics, %ARGS, Scrip => $scrip &>
 
 % if ( $added_to_any ) {
-<tr><td class="label"><a href="Objects.html?id=<% $id %>"><&|/l&>Added</&></a>:</td>
+<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>
diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index 54dbc4d..d32e1da 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -370,7 +370,7 @@ my $build_admin_menu = sub {
 
             my $tabs = PageMenu();
             $tabs->child( basics => title => loc('Basics') => path => "/Admin/Scrips/Modify.html?id=".$id );
-            $tabs->child( 'added-to' => title => loc('Added to'), path => "/Admin/Scrips/Objects.html?id=" . $id );
+            $tabs->child( 'applies-to' => title => loc('Applies to'), path => "/Admin/Scrips/Objects.html?id=" . $id );
         }
     }
 
diff --git a/t/web/scrips.t b/t/web/scrips.t
index 0f745cd..9667116 100644
--- a/t/web/scrips.t
+++ b/t/web/scrips.t
@@ -171,7 +171,7 @@ note "apply scrip in different stage to different queues";
     my ($sid) = ($m->content =~ /Modify scrip #(\d+)/);
     ok $sid, "found scrip id on the page";
 
-    $m->follow_link_ok({ text => 'Added to' });
+    $m->follow_link_ok({ text => 'Applies to' });
     ok $m->form_name('AddRemoveScrip');
     $m->select('Stage' => 'Batch');
     $m->tick( "AddScrip-$sid" => $queue->id );

commit 3d71f6c75d1c2689b9ea4d366b9d67d8cb337cb0
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 ea780b6..709766b 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 d90a536..8dcd00e 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -93,6 +93,16 @@ my $COLUMN_MAP = {
 	attribute => 'Description',
 	value     => sub { return $_[0]->Description() },
     },
+    Disabled => {
+        title     => \' ',
+        value     => sub {
+            my $v = $_[0]->Disabled;
+            return !defined $v
+                ? $_[0]->loc('Not applied')
+                : $v ? $_[0]->loc('Disabled')
+                : $_[0]->loc('Enabled')
+        },
+    },
     RemoveCheckBox => {
         title => sub {
             my $name = 'RemoveScrip';

commit 9f6eccdd5d5764aa3a906f8dcac18b434ab19834
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/lib/RT/ObjectScrips.pm b/lib/RT/ObjectScrips.pm
index d8bbf35..b13d868 100644
--- a/lib/RT/ObjectScrips.pm
+++ b/lib/RT/ObjectScrips.pm
@@ -55,6 +55,12 @@ use base 'RT::SearchBuilder::ApplyAndSort';
 use RT::Scrips;
 use RT::ObjectScrip;
 
+sub _Init {
+    my $self = shift;
+    $self->{'with_disabled_column'} = 1;
+    return $self->SUPER::_Init( @_ );
+}
+
 sub Table { 'ObjectScrips'}
 
 sub LimitToScrip {
diff --git a/lib/RT/Scrips.pm b/lib/RT/Scrips.pm
index 5a7dc81..fde846f 100644
--- a/lib/RT/Scrips.pm
+++ b/lib/RT/Scrips.pm
@@ -389,6 +389,7 @@ sub _FindScrips {
 
     $self->LimitToQueue( $self->{'TicketObj'}->QueueObj->Id );
     $self->LimitToGlobal;
+    $self->LimitToEnabled;
     $self->LimitByStage( $args{'Stage'} );
 
     my $ConditionsAlias = $self->NewAlias('ScripConditions');
diff --git a/t/api/scrip.t b/t/api/scrip.t
index 4b709f0..c43fb53 100644
--- a/t/api/scrip.t
+++ b/t/api/scrip.t
@@ -1,7 +1,7 @@
 
 use strict;
 use warnings;
-use RT::Test tests => 66;
+use RT::Test tests => 70;
 
 my $queue = RT::Test->load_or_create_queue( Name => 'General' );
 ok $queue && $queue->id, 'loaded or created queue';
@@ -171,17 +171,39 @@ note 'basic check for disabling scrips';
 {
     my $scrip = RT::Scrip->new(RT->SystemUser);
     my ($status, $msg) = $scrip->Create(
-        Queue          => $queue->Id,
-        ScripAction    => 'User Defined',
-        ScripCondition => 'User Defined',
-        Template       => 'Blank',
+        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");
+    }
+
     ($status, $msg) = $scrip->RemoveFromObject( $queue->id );
     ok($status, 'removed scrip from queue');
 

commit 506c09c4de8a326aeb18e7f1e10287d060b95ec8
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Tue Apr 10 17:41:55 2012 -0400

    Schema upgrades for Postgres

diff --git a/etc/upgrade/4.1.0/acl.Pg b/etc/upgrade/4.1.0/acl.Pg
new file mode 100755
index 0000000..9e8fc0a
--- /dev/null
+++ b/etc/upgrade/4.1.0/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.0/schema.Pg b/etc/upgrade/4.1.0/schema.Pg
new file mode 100644
index 0000000..5865866
--- /dev/null
+++ b/etc/upgrade/4.1.0/schema.Pg
@@ -0,0 +1,41 @@
+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)
+
+);
+
+INSERT INTO ObjectScrips(
+    Scrip, Stage, ObjectId,
+    Creator, Created, LastUpdatedBy, LastUpdated
+)
+SELECT id, Stage, Queue, Creator, Created, LastUpdatedBy, LastUpdated
+FROM Scrips
+;
+
+UPDATE ObjectScrips SET Disabled = 1 WHERE Scrip IN (
+    SELECT id FROM Scrips 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;
+
+ALTER TABLE ObjectCustomFields ADD COLUMN Disabled integer NOT NULL DEFAULT 0;
+UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
+    SELECT id FROM CustomFields WHERE Disabled != 0
+);
+ALTER TABLE CustomFields DROP COLUMN Disabled;

commit 0d9f425b39f3500e837f7ae39c21ba9615e471b3
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Apr 12 12:28:37 2012 -0400

    Grant rights on ObjectScrips for Pg

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

commit c2c34c8c7b4b00569808ff4c04d635754d6a8ff9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Apr 13 20:06:53 2012 +0400

    schema upgrade for Oracle

diff --git a/etc/upgrade/4.1.0/schema.Oracle b/etc/upgrade/4.1.0/schema.Oracle
new file mode 100644
index 0000000..097adbf
--- /dev/null
+++ b/etc/upgrade/4.1.0/schema.Oracle
@@ -0,0 +1,38 @@
+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
+);
+
+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 ObjectScrips SET Disabled = 1 WHERE Scrip IN (
+    SELECT id FROM Scrips 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;
+
+ALTER TABLE ObjectCustomFields ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
+UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
+    SELECT id FROM CustomFields WHERE Disabled != 0
+);
+ALTER TABLE CustomFields DROP COLUMN Disabled;
\ No newline at end of file

commit c69b33ceed96c01157c1ab02759438ca4221cc62
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Apr 13 20:07:08 2012 +0400

    incomplete schema upgrade for SQLite
    
    drop column is special

diff --git a/etc/upgrade/4.1.0/schema.SQLite b/etc/upgrade/4.1.0/schema.SQLite
new file mode 100644
index 0000000..15df601
--- /dev/null
+++ b/etc/upgrade/4.1.0/schema.SQLite
@@ -0,0 +1,39 @@
+
+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)
+);
+
+INSERT INTO ObjectScrips(
+    Scrip, Stage, ObjectId,
+    Creator, Created, LastUpdatedBy, LastUpdated
+)
+SELECT id, Stage, Queue, Creator, Created, LastUpdatedBy, LastUpdated
+FROM Scrips
+;
+
+UPDATE ObjectScrips SET Disabled = 1 WHERE Scrip IN (
+    SELECT id FROM Scrips 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;
+
+ALTER TABLE ObjectCustomFields ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
+UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
+    SELECT id FROM CustomFields WHERE Disabled != 0
+);
+# TODO: ALTER TABLE CustomFields DROP COLUMN Disabled;
\ No newline at end of file

commit e450071db5c53a161e0b0955dd424875aa11cb5f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Sep 5 17:44:41 2012 +0400

    move upgrade to 4.1.1 dir

diff --git a/etc/upgrade/4.1.0/content b/etc/upgrade/4.1.0/content
deleted file mode 100644
index 80783eb..0000000
--- a/etc/upgrade/4.1.0/content
+++ /dev/null
@@ -1,25 +0,0 @@
- at Initial = (
-    sub {
-        my $users = RT::Users->new(RT->SystemUser);
-        $users->UnLimit;
-
-        while (my $user = $users->Next) {
-            my $settings = $user->Preferences('HomepageSettings')
-                or next;
-            next if exists $settings->{sidebar};
-
-            $settings->{sidebar} = delete $settings->{summary};
-            $user->SetPreferences('HomepageSettings', $settings);
-        }
-    },
-    sub {
-        my ($default_portlets) = RT->System->Attributes->Named('HomepageSettings');
-        my $settings = $default_portlets->Content;
-        return if exists $settings->{sidebar};
-
-        $settings->{sidebar} = delete $settings->{summary};
-        $default_portlets->SetContent($settings);
-    },
-);
-
-
diff --git a/etc/upgrade/4.1.0/acl.Pg b/etc/upgrade/4.1.1/acl.Pg
similarity index 100%
rename from etc/upgrade/4.1.0/acl.Pg
rename to etc/upgrade/4.1.1/acl.Pg
diff --git a/etc/upgrade/4.1.0/schema.Oracle b/etc/upgrade/4.1.1/schema.Oracle
similarity index 100%
rename from etc/upgrade/4.1.0/schema.Oracle
rename to etc/upgrade/4.1.1/schema.Oracle
diff --git a/etc/upgrade/4.1.0/schema.Pg b/etc/upgrade/4.1.1/schema.Pg
similarity index 100%
rename from etc/upgrade/4.1.0/schema.Pg
rename to etc/upgrade/4.1.1/schema.Pg
diff --git a/etc/upgrade/4.1.0/schema.SQLite b/etc/upgrade/4.1.1/schema.SQLite
similarity index 100%
rename from etc/upgrade/4.1.0/schema.SQLite
rename to etc/upgrade/4.1.1/schema.SQLite

commit 23536f0e2972c85d63256d5a1bf972c1e341f31c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Sep 5 17:53:11 2012 +0400

    split upgrade scripts in two parts
    
    one for scrips and one for custom fields. It will help
    upgrade people who run scrips' part as an extension on
    4.0.

diff --git a/etc/upgrade/4.1.1/schema.Oracle b/etc/upgrade/4.1.1/schema.Oracle
index 097adbf..6db5535 100644
--- a/etc/upgrade/4.1.1/schema.Oracle
+++ b/etc/upgrade/4.1.1/schema.Oracle
@@ -29,10 +29,4 @@ 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;
-
-ALTER TABLE ObjectCustomFields ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
-UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
-    SELECT id FROM CustomFields WHERE Disabled != 0
-);
-ALTER TABLE CustomFields DROP COLUMN Disabled;
\ No newline at end of file
+ALTER TABLE Scrips DROP COLUMN Queue;
\ No newline at end of file
diff --git a/etc/upgrade/4.1.1/schema.Pg b/etc/upgrade/4.1.1/schema.Pg
index 5865866..2134428 100644
--- a/etc/upgrade/4.1.1/schema.Pg
+++ b/etc/upgrade/4.1.1/schema.Pg
@@ -33,9 +33,3 @@ CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
 
 ALTER TABLE Scrips DROP COLUMN Stage;
 ALTER TABLE Scrips DROP COLUMN Queue;
-
-ALTER TABLE ObjectCustomFields ADD COLUMN Disabled integer NOT NULL DEFAULT 0;
-UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
-    SELECT id FROM CustomFields WHERE Disabled != 0
-);
-ALTER TABLE CustomFields DROP COLUMN Disabled;
diff --git a/etc/upgrade/4.1.1/schema.SQLite b/etc/upgrade/4.1.1/schema.SQLite
index 15df601..77eccdb 100644
--- a/etc/upgrade/4.1.1/schema.SQLite
+++ b/etc/upgrade/4.1.1/schema.SQLite
@@ -30,10 +30,4 @@ 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;
-
-ALTER TABLE ObjectCustomFields ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
-UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
-    SELECT id FROM CustomFields WHERE Disabled != 0
-);
-# TODO: ALTER TABLE CustomFields DROP COLUMN Disabled;
\ No newline at end of file
+# TODO: ALTER TABLE Scrips DROP COLUMN Queue;
\ No newline at end of file
diff --git a/etc/upgrade/4.1.1/schema.mysql b/etc/upgrade/4.1.1/schema.mysql
index c9061a7..fbd6284 100644
--- a/etc/upgrade/4.1.1/schema.mysql
+++ b/etc/upgrade/4.1.1/schema.mysql
@@ -30,9 +30,3 @@ CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip);
 
 ALTER TABLE Scrips DROP COLUMN Stage;
 ALTER TABLE Scrips DROP COLUMN Queue;
-
-ALTER TABLE ObjectCustomFields ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
-UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
-    SELECT id FROM CustomFields WHERE Disabled != 0
-);
-ALTER TABLE CustomFields DROP COLUMN Disabled;
diff --git a/etc/upgrade/4.1.2/schema.Oracle b/etc/upgrade/4.1.2/schema.Oracle
new file mode 100644
index 0000000..4e173f6
--- /dev/null
+++ b/etc/upgrade/4.1.2/schema.Oracle
@@ -0,0 +1,5 @@
+ALTER TABLE ObjectCustomFields ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
+UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
+    SELECT id FROM CustomFields WHERE Disabled != 0
+);
+ALTER TABLE CustomFields DROP COLUMN Disabled;
\ No newline at end of file
diff --git a/etc/upgrade/4.1.2/schema.Pg b/etc/upgrade/4.1.2/schema.Pg
new file mode 100644
index 0000000..d388dae
--- /dev/null
+++ b/etc/upgrade/4.1.2/schema.Pg
@@ -0,0 +1,5 @@
+ALTER TABLE ObjectCustomFields ADD COLUMN Disabled integer NOT NULL DEFAULT 0;
+UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
+    SELECT id FROM CustomFields WHERE Disabled != 0
+);
+ALTER TABLE CustomFields DROP COLUMN Disabled;
diff --git a/etc/upgrade/4.1.2/schema.SQLite b/etc/upgrade/4.1.2/schema.SQLite
new file mode 100644
index 0000000..02f0367
--- /dev/null
+++ b/etc/upgrade/4.1.2/schema.SQLite
@@ -0,0 +1,5 @@
+ALTER TABLE ObjectCustomFields ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
+UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
+    SELECT id FROM CustomFields WHERE Disabled != 0
+);
+# TODO: ALTER TABLE CustomFields DROP COLUMN Disabled;
\ No newline at end of file
diff --git a/etc/upgrade/4.1.2/schema.mysql b/etc/upgrade/4.1.2/schema.mysql
new file mode 100644
index 0000000..20c9841
--- /dev/null
+++ b/etc/upgrade/4.1.2/schema.mysql
@@ -0,0 +1,5 @@
+ALTER TABLE ObjectCustomFields ADD COLUMN Disabled int2 NOT NULL DEFAULT 0;
+UPDATE ObjectCustomFields SET Disabled = 1 WHERE CustomField IN (
+    SELECT id FROM CustomFields WHERE Disabled != 0
+);
+ALTER TABLE CustomFields DROP COLUMN Disabled;

commit d63ddcae23f6dde63353fcdfc5fa1cdee96b2e67
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 c52e58b..38d23bc 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 7b90022af65ca1e4299a8b4b4c11af61a15de354
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Sep 6 01:17:08 2012 +0400

    test return value of Disabled for falseness

diff --git a/t/api/scrip.t b/t/api/scrip.t
index c43fb53..7c9bf4a 100644
--- a/t/api/scrip.t
+++ b/t/api/scrip.t
@@ -207,7 +207,7 @@ note 'basic check for disabling scrips';
     ($status, $msg) = $scrip->RemoveFromObject( $queue->id );
     ok($status, 'removed scrip from queue');
 
-    is($scrip->Disabled, undef, "not applied");
+    ok(!$scrip->Disabled, "not applied");
 }
 
 sub check_applications {

commit 0e7faeec69c9099d077ff887c6a4fb25376ba0f6
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..abc3db2 100644
--- a/lib/RT/Handle.pm
+++ b/lib/RT/Handle.pm
@@ -1062,14 +1062,22 @@ 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'},
+                    Disabled => $item->{'Disabled'},
+                );
+                $RT::Logger->error( "Couldn't apply scrip to $q: $msg" )
+                    unless $return;
             }
         }
         $RT::Logger->debug("done.");

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

    documentation for new ApplyAndSort API

diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index 676354d..10e4500 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -55,9 +55,74 @@ use base 'RT::Record::ApplyAndSort';
 use RT::Scrip;
 use RT::ObjectScrips;
 
+=head1 NAME
+
+RT::ObjectScrip - record representing application of a scrip to a queue
+
+=head1 DESCRIPTION
+
+This record is created if you want to apply a scrip to a queue or globally.
+
+Inherits methods from L<RT::Record::ApplyAndSort>.
+
+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 applied to.
+Now it's only L<RT::Queue>, so 'RT::Queues' is returned.
+
+=cut
+
 sub ObjectCollectionClass {'RT::Queues'}
 
+=head2 Create
+
+Creates a record, e.g. applies scrip to a queue or globally.
+
+It's better to use L<RT::Scrip/AddToObject> method instead.
+
+Takes:
+
+=over 4
+
+=item Scrip
+
+Scrip object or id.
+
+=item Stage
+
+Stage of the scrip. The same scrip can be run in different stages.
+
+=item ObjectId
+
+Id or an object this scrip should be applied to. For now it's a queue.
+Use 0 to mean globally.
+
+=item Disabled
+
+Boolean indicator whether this new application is active or not. For now
+all applications of the same Scrip should be either disabled or active.
+
+=item Created, Creator, LastUpdated, LastUpdatedBy
+
+Generated automatically from CurrentUser and the current time, but can be
+overriden.
+
+=back
+
+=cut
+
 sub Create {
     my $self = shift;
     my %args = (@_);
@@ -86,6 +151,12 @@ sub ScripObj {
     return $obj;
 }
 
+=head2 Neighbors
+
+Stage splits scrips into neighborhoods. See L<RT::Record::ApplyAndSort/Neighbors and Siblings>.
+
+=cut
+
 sub Neighbors {
     my $self = shift;
     my %args = @_;
diff --git a/lib/RT/ObjectScrips.pm b/lib/RT/ObjectScrips.pm
index b13d868..278c5e2 100644
--- a/lib/RT/ObjectScrips.pm
+++ b/lib/RT/ObjectScrips.pm
@@ -55,14 +55,38 @@ use base 'RT::SearchBuilder::ApplyAndSort';
 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::ApplyAndSort>.
+
+=head1 METHODS
+
+=cut
+
 sub _Init {
     my $self = shift;
     $self->{'with_disabled_column'} = 1;
     return $self->SUPER::_Init( @_ );
 }
 
+=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/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index 9e7d623..5d0e847 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -52,11 +52,42 @@ use warnings;
 package RT::Record::ApplyAndSort;
 use base 'RT::Record';
 
+=head1 NAME
+
+RT::Record::ApplyAndSort - base class for records that can be applied 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 apply 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,42 @@ sub TargetField {
     return $class;
 }
 
+=head3 ObjectCollectionClass
+
+Takes an object under L</TargetField> name and should return class
+name representing collection the object can be applied 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 apply to, object we apply 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</Apply> 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 = (
@@ -101,6 +168,15 @@ sub Create {
     );
 }
 
+=head3 Apply
+
+Helper method that wraps L</Create> and does more checks to make sure result
+is consistent. Doesn't allow to apply a record to an object if the record
+is already global. Un-applies record from particular objects when asked
+to apply the record globally.
+
+=cut
+
 sub Apply {
     my $self = shift;
     my %args = (@_);
@@ -133,98 +209,12 @@ sub Apply {
     );
 }
 
-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 AppliedTo
+=head3 Delete
 
-Returns collection with objects this custom field is applied to.
-Class of the collection depends on L</LookupType>.
-See all L</NotAppliedTo> .
-
-Doesn't takes into account if object is applied globally.
+Deletes this record.
 
 =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 takes into account if 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 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;
 
@@ -241,6 +231,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 = (@_);
@@ -256,6 +257,12 @@ sub DeleteAll {
     $_->Delete foreach @{ $list->ItemsArrayRef };
 }
 
+=head3 SetDisabledOnAll
+
+Like L</DeleteAll>, but changes Disabled field on all records.
+
+=cut
+
 sub SetDisabledOnAll {
     my $self = shift;
     my %args = (@_);
@@ -280,22 +287,17 @@ sub SetDisabledOnAll {
     return (1, $self->loc("Disabled all applications") );
 }
 
-=head2 Sorting scrips applications
-
-scrips sorted on multiple layers. First of all custom
-fields with different lookup type are sorted independently. All
-global scrips have fixed order for all objects, but you
-can insert object specific scrips between them. Object
-specific scrips 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.
+sub IsApplied {
+    my $self = shift;
+    my ($tid, $oid) = @_;
+    my $record = $self->new( $self->CurrentUser );
+    $record->LoadByCols( $self->TargetField => $tid, ObjectId => $oid );
+    return $record->id;
+}
 
 =head3 MoveUp
 
-Moves scrip up. See </Sorting scrips applications>.
+Moves record up.
 
 =cut
 
@@ -303,12 +305,18 @@ sub MoveUp { return shift->Move( Up => @_ ) }
 
 =head3 MoveDown
 
-Moves scrip down. See </Sorting scrips applications>.
+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');
@@ -401,6 +409,117 @@ 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 );
+}
+
+=head1 AppliedTo
+
+Returns collection with objects target of this record is applied to.
+Class of the collection depends on L</ObjectCollectionClass>.
+See all L</NotAppliedTo>.
+
+For example returns L<RT::Queues> collection if the target is L<RT::Scrip>.
+
+Returns empty collection if target 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 target of this record is not applied to.
+Class of the collection depends on L</ObjectCollectionClass>.
+See all L</AppliedTo>.
+
+Returns empty collection if target 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 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);
+}
+
+=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 = (@_);
@@ -421,6 +540,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;
@@ -431,19 +556,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 applied 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 applied globally or to particular set of queues. Custom fields for
+tickets are neighbors. Neighbor custom fields applied to the same objects
+are siblings. Custom field applied globally is 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 applied globally, S1 is applied to Q1 and executed
+before S2 in this queue, also S1 is applied to Q1, but exectued after S2 in this
+queue, S3 is only applied to Q2 and executed before S2 and S1.
+
+Siblings are scrips applied to an object including globally applied or only
+globally applied. 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/ApplyAndSort.pm b/lib/RT/SearchBuilder/ApplyAndSort.pm
index 2b3d4b8..2f584f1 100644
--- a/lib/RT/SearchBuilder/ApplyAndSort.pm
+++ b/lib/RT/SearchBuilder/ApplyAndSort.pm
@@ -52,11 +52,21 @@ 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;
-}
+=head1 NAME
+
+RT::SearchBuilder::ApplyAndSort - base class for 'apply and sort' collections
+
+=head1 DESCRIPTION
+
+Base class for collections where records can be applied to objects with order.
+See also L<RT::Record::ApplyAndSort>. 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 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.
+Takes a collection object and optional list of object ids. Limits
+the collection to records not applied to listed objects or if
+the list is empty then any object. Use 0 (zero) to mean global.
 
 =cut
 
@@ -107,8 +153,8 @@ sub LimitTargetToNotApplied {
 
 =head2 LimitTargetToApplied
 
-Limits collection to custom fields to listed objects or any corespondingly. Use
-zero to mean global.
+L</LimitTargetToNotApplied> with reverse meaning. Takes the same
+arguments.
 
 =cut
 
@@ -129,6 +175,16 @@ sub LimitTargetToApplied {
     return $alias;
 }
 
+=head2 JoinTargetToApplied
+
+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 JoinTargetToApplied {
     my $self = shift;
     my $collection = shift;
@@ -152,6 +208,15 @@ sub JoinTargetToApplied {
     return $alias;
 }
 
+=head3 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 c016abaa6e0efedb73a2628a866e71fbc9438c97
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Sep 7 13:13:44 2012 -0700

    Adjust POD header levels to match surrounding headers

diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index 5d0e847..a8b5e70 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -426,7 +426,7 @@ sub TargetObj {
     return $self->$method( $id );
 }
 
-=head1 AppliedTo
+=head3 AppliedTo
 
 Returns collection with objects target of this record is applied to.
 Class of the collection depends on L</ObjectCollectionClass>.
@@ -454,7 +454,7 @@ sub AppliedTo {
     return $res;
 }
 
-=head1 NotAppliedTo
+=head3 NotAppliedTo
 
 Returns collection with objects target of this record is not applied to.
 Class of the collection depends on L</ObjectCollectionClass>.
diff --git a/lib/RT/SearchBuilder/ApplyAndSort.pm b/lib/RT/SearchBuilder/ApplyAndSort.pm
index 2f584f1..425d394 100644
--- a/lib/RT/SearchBuilder/ApplyAndSort.pm
+++ b/lib/RT/SearchBuilder/ApplyAndSort.pm
@@ -208,7 +208,7 @@ sub JoinTargetToApplied {
     return $alias;
 }
 
-=head3 JoinTargetToThis
+=head2 JoinTargetToThis
 
 Joins target collection to this table using TargetField.
 

commit 0e98f51218efc2a169a4cdf07a91f11766bb263b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Sep 8 02:34:11 2012 +0400

    sync Scrip->IsAdded's return value with CustomField's

diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 38d23bc..514fca3 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -226,7 +226,8 @@ sub IsAdded {
     my $self = shift;
     my $record = RT::ObjectScrip->new( $self->CurrentUser );
     $record->LoadByCols( Scrip => $self->id, ObjectId => shift || 0 );
-    return $record->id;
+    return undef unless $record->id;
+    return $record;
 }
 
 sub AddedTo {

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

    Scrip->IsAddedToAny and CF->IsAppliedToAny methods

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 1db0514..355c97a 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1321,6 +1321,20 @@ sub IsApplied {
     return $ocf;
 }
 
+=head2 IsAppliedToAny
+
+Returns true if custom field is applied to any object.
+
+=cut
+
+sub IsAppliedToAny {
+    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 514fca3..a648f51 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -230,6 +230,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 117f934803109cda0f1dd783503d25f7cc41d563
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Sep 12 19:28:55 2012 +0400

    keep return value of Disabled as boolean
    
    don't rely on Disabled to return undef if CF or Scrip
    is not applied. Use a method we have for that.

diff --git a/share/html/Admin/Scrips/Modify.html b/share/html/Admin/Scrips/Modify.html
index c83e27e..54fea72 100644
--- a/share/html/Admin/Scrips/Modify.html
+++ b/share/html/Admin/Scrips/Modify.html
@@ -106,8 +106,7 @@ Abort(loc("Couldn't load scrip #[_1]", $id))
     unless $scrip->id;
 
 my $disabled = $scrip->Disabled;
-my $added_to_any = 0;
-$added_to_any = 1 if defined $disabled;
+my $added_to_any = $scrip->IsAddedToAny;
 
 if ( $Update ) {
     my @attribs = qw(

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

    CF disabling UI
    
    If CF is not applied to any object then Disabled is not defined
    and con not be set. We hide disabled UI on create and when CF
    is not applied.

diff --git a/share/html/Admin/CustomFields/Modify.html b/share/html/Admin/CustomFields/Modify.html
index 0110b7d..838af10 100644
--- a/share/html/Admin/CustomFields/Modify.html
+++ b/share/html/Admin/CustomFields/Modify.html
@@ -92,6 +92,12 @@
         Default => $CustomFieldObj->LookupType || $LookupType, &>
 </td></tr>
 
+% if ( $CustomFieldObj->id && !$CustomFieldObj->IsAppliedToAny ) {
+<tr><td></td>
+<td><i><&|/l&>Custom field is not added to any object.</&></i>
+</td></tr>
+% }
+
 <tr class="edit_validation"><td class="label"><&|/l&>Validation</&></td>
 <td><& /Widgets/ComboBox,
     Name    => 'Pattern',
@@ -130,11 +136,13 @@
 
 % $m->callback(CallbackName => 'BeforeEnabled', CustomField => $CustomFieldObj, CFvalidations => \@CFvalidations);
 
+% if ( $CustomFieldObj->id && $CustomFieldObj->IsAppliedToAny ) {
 <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%> />
 <&|/l&>Enabled (Unchecking this box disables this custom field)</&>
 </td></tr>
+% }
 
 % $m->callback(CallbackName => 'EndOfTable', CustomField => $CustomFieldObj, CFvalidations => \@CFvalidations);
 

commit a002023b4ffe8303c3b66ec73aa96a8643711af1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Sep 20 18:08:47 2012 +0400

    don't use Disabled to check IsAddedToAny

diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index 8dcd00e..9959d67 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -96,11 +96,10 @@ my $COLUMN_MAP = {
     Disabled => {
         title     => \' ',
         value     => sub {
-            my $v = $_[0]->Disabled;
-            return !defined $v
-                ? $_[0]->loc('Not applied')
-                : $v ? $_[0]->loc('Disabled')
-                : $_[0]->loc('Enabled')
+            return
+                $_[0]->IsAddedToAny
+                ? ($_[0]->Disabled ? $_[0]->loc('Disabled') : $_[0]->loc('Enabled') )
+                : $_[0]->loc('Not Applied')
         },
     },
     RemoveCheckBox => {

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

    rename Apply to Add
    
    Apply and Add were used in CustomField to mean the same thing.
    
    Apply in Scrip is used to run scrip. So we went for Add* for
    scrips.
    
    After review we decided that it's better to unify API to use
    Add term.

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 355c97a..ddabb12 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -873,8 +873,8 @@ 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.
+this CF be 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
 
@@ -942,11 +942,11 @@ 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.
@@ -955,7 +955,7 @@ sub ValidateContextObject {
     # 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);
+    return $self->IsAdded($applied_to->id);
 }
 
 
@@ -1257,54 +1257,57 @@ sub CollectionClassFromLookupType {
     return $collection_class;
 }
 
-=head1 ApplyGlobally
+=head1 IsOnlyGlobal
 
-Certain custom fields (users, groups) should only be applied globally
+Certain custom fields (users, groups) should only be added globally
 but rather than regexing in code for LookupType =~ RT::Queue, we'll codify
 the rules here.
 
 =cut
 
-sub ApplyGlobally {
+sub IsOnlyGlobal {
     my $self = shift;
 
     return ($self->LookupType =~ /^RT::(?:Group|User)/io);
 
 }
+sub ApplyGlobally { warn "DEPRECATED, use IsOnlyGlobal"; return shift->IsOnlyGlobal(@_) }
 
-=head1 AppliedTo
+=head1 AddedTo
 
 Returns collection with objects this custom field is applied 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.
 
 =cut
 
-sub AppliedTo {
+sub AddedTo {
     my $self = shift;
     return RT::ObjectCustomField->new( $self->CurrentUser )
-        ->AppliedTo( CustomField => $self );
+        ->AddedTo( CustomField => $self );
 }
+sub AppliedTo { warn "DEPRECATED: use AddedTo"; shift->AddedTo(@_) };
 
-=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> .
+See all L</AddedTo> .
 
 Doesn't takes into account if object is applied globally.
 
 =cut
 
-sub NotAppliedTo {
+sub NotAddedTo {
     my $self = shift;
     return RT::ObjectCustomField->new( $self->CurrentUser )
-        ->NotAppliedTo( CustomField => $self );
+        ->NotAddedTo( CustomField => $self );
 }
+sub NotAppliedTo { warn "DEPRECATED: use NotAddedTo"; shift->NotAddedTo(@_) };
 
-=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
@@ -1312,7 +1315,7 @@ if custom field is applied globally.
 
 =cut
 
-sub IsApplied {
+sub IsAdded {
     my $self = shift;
     my $id = shift;
     my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
@@ -1320,14 +1323,15 @@ sub IsApplied {
     return undef unless $ocf->id;
     return $ocf;
 }
+sub IsApplied { warn "DEPRECATED: use IsAdded"; shift->IsAdded(@_) };
 
-=head2 IsAppliedToAny
+=head2 IsAddedToAny
 
 Returns true if custom field is applied to any object.
 
 =cut
 
-sub IsAppliedToAny {
+sub IsAddedToAny {
     my $self = shift;
     my $id = shift;
     my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
@@ -1357,7 +1361,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 );
@@ -1385,7 +1389,7 @@ 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") );
     }
diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index 37ca251..5bd281a 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -169,7 +169,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
@@ -177,23 +177,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 5f838e3..0992cc9 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 72b1894..8fd40b8 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/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index 10e4500..f5546b6 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -50,20 +50,20 @@ use strict;
 use warnings;
 
 package RT::ObjectScrip;
-use base 'RT::Record::ApplyAndSort';
+use base 'RT::Record::AddAndSort';
 
 use RT::Scrip;
 use RT::ObjectScrips;
 
 =head1 NAME
 
-RT::ObjectScrip - record representing application of a scrip to a queue
+RT::ObjectScrip - record representing addition of a scrip to a queue
 
 =head1 DESCRIPTION
 
-This record is created if you want to apply a scrip to a queue or globally.
+This record is created when you add a scrip to a queue or globally.
 
-Inherits methods from L<RT::Record::ApplyAndSort>.
+Inherits methods from L<RT::Record::AddAndSort>.
 
 For most operations it's better to use methods in L<RT::Scrip>.
 
@@ -88,7 +88,7 @@ sub ObjectCollectionClass {'RT::Queues'}
 
 =head2 Create
 
-Creates a record, e.g. applies scrip to a queue or globally.
+Creates a record, e.g. adds a scrip to a queue or globally.
 
 It's better to use L<RT::Scrip/AddToObject> method instead.
 
@@ -106,13 +106,13 @@ Stage of the scrip. The same scrip can be run in different stages.
 
 =item ObjectId
 
-Id or an object this scrip should be applied to. For now it's a queue.
+Id or an object this scrip should be added to. For now it's a queue.
 Use 0 to mean globally.
 
 =item Disabled
 
-Boolean indicator whether this new application is active or not. For now
-all applications of the same Scrip should be either disabled or active.
+Boolean indicator whether this new addition is active or not. For now
+all additions of the same Scrip should be either disabled or active.
 
 =item Created, Creator, LastUpdated, LastUpdatedBy
 
@@ -153,7 +153,7 @@ sub ScripObj {
 
 =head2 Neighbors
 
-Stage splits scrips into neighborhoods. See L<RT::Record::ApplyAndSort/Neighbors and Siblings>.
+Stage splits scrips into neighborhoods. See L<RT::Record::AddAndSort/Neighbors and Siblings>.
 
 =cut
 
diff --git a/lib/RT/ObjectScrips.pm b/lib/RT/ObjectScrips.pm
index 278c5e2..7201454 100644
--- a/lib/RT/ObjectScrips.pm
+++ b/lib/RT/ObjectScrips.pm
@@ -50,7 +50,7 @@ use strict;
 use warnings;
 
 package RT::ObjectScrips;
-use base 'RT::SearchBuilder::ApplyAndSort';
+use base 'RT::SearchBuilder::AddAndSort';
 
 use RT::Scrips;
 use RT::ObjectScrip;
@@ -61,7 +61,7 @@ RT::ObjectScrips - collection of RT::ObjectScrip records
 
 =head1 DESCRIPTION
 
-Collection of L<RT::ObjectScrip> records. Inherits methods from L<RT::SearchBuilder::ApplyAndSort>.
+Collection of L<RT::ObjectScrip> records. Inherits methods from L<RT::SearchBuilder::AddAndSort>.
 
 =head1 METHODS
 
diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/AddAndSort.pm
similarity index 96%
rename from lib/RT/Record/ApplyAndSort.pm
rename to lib/RT/Record/AddAndSort.pm
index a8b5e70..aefe0d5 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/AddAndSort.pm
@@ -49,12 +49,12 @@
 use strict;
 use warnings;
 
-package RT::Record::ApplyAndSort;
+package RT::Record::AddAndSort;
 use base 'RT::Record';
 
 =head1 NAME
 
-RT::Record::ApplyAndSort - base class for records that can be applied and sorted
+RT::Record::AddAndSort - base class for records that can be applied and sorted
 
 =head1 DESCRIPTION
 
@@ -126,7 +126,7 @@ Example:
     my $ocf = RT::ObjectCustomField->new( RT->SystemUser );
     my ($id, $msg) = $ocf->Create( CustomField => 1, ObjectId => 0 );
 
-See L</Apply> which has more error checks. Also, L<RT::Scrip> and L<RT::CustomField>
+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
@@ -168,7 +168,7 @@ sub Create {
     );
 }
 
-=head3 Apply
+=head3 Add
 
 Helper method that wraps L</Create> and does more checks to make sure result
 is consistent. Doesn't allow to apply a record to an object if the record
@@ -177,7 +177,7 @@ to apply the record globally.
 
 =cut
 
-sub Apply {
+sub Add {
     my $self = shift;
     my %args = (@_);
 
@@ -191,14 +191,14 @@ sub Apply {
     $oid = $oid->id if ref $oid;
     $oid ||= 0;
 
-    if ( $self->IsApplied( $tid => $oid ) ) {
+    if ( $self->IsAdded( $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 );
+            if $self->IsAdded( $tid => 0 );
     }
     else {
         $self->DeleteAll( $field => $tid );
@@ -287,7 +287,7 @@ sub SetDisabledOnAll {
     return (1, $self->loc("Disabled all applications") );
 }
 
-sub IsApplied {
+sub IsAdded {
     my $self = shift;
     my ($tid, $oid) = @_;
     my $record = $self->new( $self->CurrentUser );
@@ -426,11 +426,11 @@ sub TargetObj {
     return $self->$method( $id );
 }
 
-=head3 AppliedTo
+=head3 AddedTo
 
 Returns collection with objects target of this record is applied to.
 Class of the collection depends on L</ObjectCollectionClass>.
-See all L</NotAppliedTo>.
+See all L</NotAddedTo>.
 
 For example returns L<RT::Queues> collection if the target is L<RT::Scrip>.
 
@@ -438,10 +438,10 @@ Returns empty collection if target is applied 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(
@@ -454,20 +454,20 @@ sub AppliedTo {
     return $res;
 }
 
-=head3 NotAppliedTo
+=head3 NotAddedTo
 
 Returns collection with objects target of this record is not applied to.
 Class of the collection depends on L</ObjectCollectionClass>.
-See all L</AppliedTo>.
+See all L</AddedTo>.
 
 Returns empty collection if target is applied 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(
@@ -480,7 +480,7 @@ sub NotAppliedTo {
     return $res;
 }
 
-sub _AppliedTo {
+sub _AddedTo {
     my $self = shift;
     my %args = (@_);
 
diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index a648f51..49b2dee 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -189,13 +189,13 @@ sub Create {
         $args{'Disabled'} = 1;
     }
 
-    (my $status, $msg) = RT::ObjectScrip->new( $self->CurrentUser )->Apply(
+    (my $status, $msg) = RT::ObjectScrip->new( $self->CurrentUser )->Add(
         Scrip    => $self,
         Stage    => $args{'Stage'},
         ObjectId => $args{'Queue'},
         Disabled => $args{'Disabled'},
     );
-    $RT::Logger->error( "Couldn't apply scrip: $msg" ) unless $status;
+    $RT::Logger->error( "Couldn't add scrip: $msg" ) unless $status;
 
     return ( $id, $self->loc('Scrip Created') );
 }
@@ -240,13 +240,13 @@ sub IsAddedToAny {
 sub AddedTo {
     my $self = shift;
     return RT::ObjectScrip->new( $self->CurrentUser )
-        ->AppliedTo( Scrip => $self );
+        ->AddedTo( Scrip => $self );
 }
 
 sub NotAddedTo {
     my $self = shift;
     return RT::ObjectScrip->new( $self->CurrentUser )
-        ->NotAppliedTo( Scrip => $self );
+        ->NotAddedTo( Scrip => $self );
 }
 
 sub AddToObject {
@@ -281,7 +281,7 @@ sub AddToObject {
     }
 
     my $rec = RT::ObjectScrip->new( $self->CurrentUser );
-    return $rec->Apply( %args, Scrip => $self );
+    return $rec->Add( %args, Scrip => $self );
 }
 
 sub RemoveFromObject {
@@ -303,7 +303,7 @@ sub RemoveFromObject {
 
     my $rec = RT::ObjectScrip->new( $self->CurrentUser );
     $rec->LoadByCols( Scrip => $self->id, ObjectId => $args{'ObjectId'} );
-    return (0, $self->loc('Scrip is not applied') ) unless $rec->id;
+    return (0, $self->loc('Scrip is not added') ) unless $rec->id;
     return $rec->Delete;
 }
 
@@ -379,7 +379,7 @@ sub TemplateObj {
 =head2 Stage
 
 Takes TicketObj named argument and returns scrip's stage when
-applied to ticket's queue.
+added to ticket's queue.
 
 =cut
 
diff --git a/lib/RT/Scrips.pm b/lib/RT/Scrips.pm
index fde846f..8456136 100644
--- a/lib/RT/Scrips.pm
+++ b/lib/RT/Scrips.pm
@@ -117,13 +117,13 @@ sub LimitToGlobal  {
 sub LimitToAdded {
     my $self = shift;
     return RT::ObjectScrips->new( $self->CurrentUser )
-        ->LimitTargetToApplied( $self => @_ );
+        ->LimitTargetToAdded( $self => @_ );
 }
 
 sub LimitToNotAdded {
     my $self = shift;
     return RT::ObjectScrips->new( $self->CurrentUser )
-        ->LimitTargetToNotApplied( $self => @_ );
+        ->LimitTargetToNotAdded( $self => @_ );
 }
 
 sub LimitByStage  {
@@ -142,8 +142,8 @@ sub LimitByStage  {
 
 =head2 LimitToEnabled
 
-Limits scrips to that are applied to any queue or globally
-and application is not disabled.
+Limits scrips to that are added to any queue or globally
+and it is not disabled.
 
 =cut
 
diff --git a/lib/RT/SearchBuilder/ApplyAndSort.pm b/lib/RT/SearchBuilder/AddAndSort.pm
similarity index 91%
rename from lib/RT/SearchBuilder/ApplyAndSort.pm
rename to lib/RT/SearchBuilder/AddAndSort.pm
index 425d394..a3f16f0 100644
--- a/lib/RT/SearchBuilder/ApplyAndSort.pm
+++ b/lib/RT/SearchBuilder/AddAndSort.pm
@@ -49,17 +49,17 @@
 use strict;
 use warnings;
 
-package RT::SearchBuilder::ApplyAndSort;
+package RT::SearchBuilder::AddAndSort;
 use base 'RT::SearchBuilder';
 
 =head1 NAME
 
-RT::SearchBuilder::ApplyAndSort - base class for 'apply and sort' collections
+RT::SearchBuilder::AddAndSort - base class for 'apply and sort' collections
 
 =head1 DESCRIPTION
 
 Base class for collections where records can be applied to objects with order.
-See also L<RT::Record::ApplyAndSort>. Used by L<RT::ObjectScrips> and
+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.
@@ -126,7 +126,7 @@ 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 LimitTargetToNotApplied
+=head2 LimitTargetToNotAdded
 
 Takes a collection object and optional list of object ids. Limits
 the collection to records not applied to listed objects or if
@@ -134,12 +134,12 @@ the list is empty then any object. Use 0 (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',
@@ -151,19 +151,19 @@ sub LimitTargetToNotApplied {
     return $alias;
 }
 
-=head2 LimitTargetToApplied
+=head2 LimitTargetToAdded
 
-L</LimitTargetToNotApplied> with reverse meaning. Takes the same
+L</LimitTargetToNotAdded> with reverse meaning. Takes the same
 arguments.
 
 =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',
@@ -175,7 +175,7 @@ sub LimitTargetToApplied {
     return $alias;
 }
 
-=head2 JoinTargetToApplied
+=head2 JoinTargetToAdded
 
 Joins collection to this table using left join, limits joined table
 by ids if those are provided.
@@ -185,7 +185,7 @@ multiple calls.
 
 =cut
 
-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 838af10..095702f 100644
--- a/share/html/Admin/CustomFields/Modify.html
+++ b/share/html/Admin/CustomFields/Modify.html
@@ -92,7 +92,7 @@
         Default => $CustomFieldObj->LookupType || $LookupType, &>
 </td></tr>
 
-% if ( $CustomFieldObj->id && !$CustomFieldObj->IsAppliedToAny ) {
+% if ( $CustomFieldObj->id && !$CustomFieldObj->IsAddedToAny ) {
 <tr><td></td>
 <td><i><&|/l&>Custom field is not added to any object.</&></i>
 </td></tr>
@@ -136,7 +136,7 @@
 
 % $m->callback(CallbackName => 'BeforeEnabled', CustomField => $CustomFieldObj, CFvalidations => \@CFvalidations);
 
-% if ( $CustomFieldObj->id && $CustomFieldObj->IsAppliedToAny ) {
+% if ( $CustomFieldObj->id && $CustomFieldObj->IsAddedToAny ) {
 <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%> />
@@ -285,10 +285,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..050b080 100644
--- a/share/html/Admin/CustomFields/Objects.html
+++ b/share/html/Admin/CustomFields/Objects.html
@@ -63,7 +63,7 @@
 <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',
@@ -141,10 +141,10 @@ if ( $UpdateObjs ) {
     }
 }
 
-my $is_global = $CF->IsApplied(0);
+my $is_global = $CF->IsAdded(0);
 
-my $applied = $CF->AppliedTo;
-my $not_applied = $CF->NotAppliedTo;
+my $applied = $CF->AddedTo;
+my $not_applied = $CF->NotAddedTo;
 
 my $collection_class = ref($applied);
 $collection_class =~ s/^RT:://;
diff --git a/share/html/Admin/Elements/EditCustomFields b/share/html/Admin/Elements/EditCustomFields
index d9d9134..93fc192 100644
--- a/share/html/Admin/Elements/EditCustomFields
+++ b/share/html/Admin/Elements/EditCustomFields
@@ -160,7 +160,7 @@ $applied_cfs->ApplySortOrder;
 
 my $not_applied_cfs = RT::CustomFields->new( $session{'CurrentUser'} );
 $not_applied_cfs->LimitToLookupType($lookup);
-$not_applied_cfs->LimitToNotApplied( $id ? ($id, 0) : (0) );
+$not_applied_cfs->LimitToNotAdded( $id ? ($id, 0) : (0) );
 
 my $format = RT->Config->Get('AdminSearchResultFormat')->{'CustomFields'};
 
diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index b72a645..b422909 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -73,7 +73,7 @@
 &>
 
 <h2><&|/l&>Not applied scrips</&></h2>
-% $scrips = $find_scrips->(Applied => 0);
+% $scrips = $find_scrips->(Added => 0);
 <& /Elements/CollectionList,
     Rows => 50,
     Page => 1,
@@ -111,13 +111,13 @@ if ( $id ) {
 $id ||= 0;
 
 my $find_scrips = sub {
-    my %args = (Applied => 1, @_);
+    my %args = (Added => 1, @_);
     my $scrips = RT::Scrips->new($session{'CurrentUser'});
     $scrips->LimitByStage( $args{'Stage'} )
         if $args{'Stage'};
-    my $method = $args{'Applied'}? 'LimitToAdded' : 'LimitToNotAdded';
+    my $method = $args{'Added'}? 'LimitToAdded' : 'LimitToNotAdded';
     $scrips->$method(0, $id);
-    $scrips->ApplySortOrder if $args{'Applied'};
+    $scrips->ApplySortOrder if $args{'Added'};
     return $scrips;
 };
 
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 );
diff --git a/share/html/Elements/RT__Scrip/ColumnMap b/share/html/Elements/RT__Scrip/ColumnMap
index 9959d67..fe36d19 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -99,7 +99,7 @@ my $COLUMN_MAP = {
             return
                 $_[0]->IsAddedToAny
                 ? ($_[0]->Disabled ? $_[0]->loc('Disabled') : $_[0]->loc('Enabled') )
-                : $_[0]->loc('Not Applied')
+                : $_[0]->loc('Not Added')
         },
     },
     RemoveCheckBox => {

commit 3ba5ef05fe40af173e9eb77e974fcc6cd32698b4
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Tue Oct 16 12:03:50 2012 -0700

    Let defaults in RT::Record::AddAndSort->Create take effect
    
    Avoids "autovivifying" explicit undef values for columns which were
    omitted in the original call to Create.  Now the default of
    
        ObjectId => 0
    
    in SUPER::Create is actually used.
    
    The dropping of the default ObjectId meant non-queue/ticket custom
    fields created from an initialdata failed to be applied globally (the
    intended behaviour).

diff --git a/lib/RT/ObjectCustomField.pm b/lib/RT/ObjectCustomField.pm
index 0992cc9..c4b362f 100644
--- a/lib/RT/ObjectCustomField.pm
+++ b/lib/RT/ObjectCustomField.pm
@@ -68,7 +68,8 @@ sub Create {
     my $self = shift;
     my %args = (@_);
     return $self->SUPER::Create(
-        map { $_ => $args{ $_ } } qw(
+         map { $_ => $args{ $_ } }
+        grep { exists $args{$_} } qw(
             CustomField ObjectId
             SortOrder Disabled
             Created Creator
diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index f5546b6..301188b 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -128,7 +128,8 @@ sub Create {
     my %args = (@_);
     $args{'Stage'} ||= 'TransactionCreate'; #XXX: why don't we turn undef into default?
     return $self->SUPER::Create(
-        map { $_ => $args{ $_ } } qw(
+         map { $_ => $args{ $_ } }
+        grep { exists $args{$_} } qw(
             Scrip Stage ObjectId
             SortOrder Disabled
             Created Creator

commit 1e68f2dfa206f0b40fcd9ba7c2158796e71c602e
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Nov 1 15:37:47 2012 -0700

    Switch to the new column map name AddedTo in the default format

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 709766b..75cf296 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/Scrips/Modify.html?id=__id__">__id__</a>/TITLE:#'}

commit fcbfe0ef12ee4aa48b82a5361ceac0ac15eaa96a
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Nov 1 15:39:35 2012 -0700

    carp() deprecation warnings for easier tracing to the origin callsite

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index ddabb12..cbf56b3 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1271,7 +1271,7 @@ sub IsOnlyGlobal {
     return ($self->LookupType =~ /^RT::(?:Group|User)/io);
 
 }
-sub ApplyGlobally { warn "DEPRECATED, use IsOnlyGlobal"; return shift->IsOnlyGlobal(@_) }
+sub ApplyGlobally { Carp::carp("DEPRECATED, use IsOnlyGlobal"); return shift->IsOnlyGlobal(@_) }
 
 =head1 AddedTo
 
@@ -1288,7 +1288,7 @@ sub AddedTo {
     return RT::ObjectCustomField->new( $self->CurrentUser )
         ->AddedTo( CustomField => $self );
 }
-sub AppliedTo { warn "DEPRECATED: use AddedTo"; shift->AddedTo(@_) };
+sub AppliedTo { Carp::carp("DEPRECATED: use AddedTo"); shift->AddedTo(@_) };
 
 =head1 NotAddedTo
 
@@ -1305,7 +1305,7 @@ sub NotAddedTo {
     return RT::ObjectCustomField->new( $self->CurrentUser )
         ->NotAddedTo( CustomField => $self );
 }
-sub NotAppliedTo { warn "DEPRECATED: use NotAddedTo"; shift->NotAddedTo(@_) };
+sub NotAppliedTo { Carp::carp("DEPRECATED: use NotAddedTo"); shift->NotAddedTo(@_) };
 
 =head2 IsAdded
 
@@ -1323,7 +1323,7 @@ sub IsAdded {
     return undef unless $ocf->id;
     return $ocf;
 }
-sub IsApplied { warn "DEPRECATED: use IsAdded"; shift->IsAdded(@_) };
+sub IsApplied { Carp::carp("DEPRECATED: use IsAdded"); shift->IsAdded(@_) };
 
 =head2 IsAddedToAny
 

commit 3971c98c38dda892cd861008b93db0fd130194b6
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Nov 2 12:47:17 2012 -0700

    Tests: Avoid uninitialized warnings from the User Defined condition/action

diff --git a/t/api/scrip.t b/t/api/scrip.t
index 7c9bf4a..ddd0158 100644
--- a/t/api/scrip.t
+++ b/t/api/scrip.t
@@ -133,6 +133,9 @@ note 'check applications vs. templates';
         ScripAction    => 'User Defined',
         ScripCondition => 'User Defined',
         Template       => 'foo',
+        CustomIsApplicableCode  => "1;",
+        CustomPrepareCode       => "1;",
+        CustomCommitCode        => "1;",
     );
     ok($status, 'created a scrip') or diag "error: $msg";
     main->check_applications($scrip, [$queue], [0, $queue_B]);

commit 40ae51ad9fc70713f36211623df71ea41cb1b87d
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 5fc4619fb732762cf14c39b18408b947cd08e158
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..16c65ce 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -1097,6 +1097,44 @@ sub clean_caught_mails {
     unlink $tmp{'mailbox'};
 }
 
+=head2 object_scrips_are
+
+Takes an L<RT::Scrip> object 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;
+
+    Test::More::ok($scrip->IsAdded(ref $_? $_->id : $_), 'added to queue' ) foreach @$to;
+    Test::More::is_deeply(
+        [sort map $_->id, @{ $scrip->AddedTo->ItemsArrayRef }],
+        [sort grep $_, map ref $_? $_->id : $_, @$to],
+        'correct list of added to queues',
+    );
+
+    if ($not_to) {
+        Test::More::ok(!$scrip->IsAdded(ref $_? $_->id : $_), 'not added to queue' ) foreach @$not_to;
+        Test::More::is_deeply(
+            [sort map $_->id, @{ $scrip->NotAddedTo->ItemsArrayRef }],
+            [sort grep $_, map ref $_? $_->id : $_, @$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
diff --git a/t/api/scrip.t b/t/api/scrip.t
index ddd0158..279be5d 100644
--- a/t/api/scrip.t
+++ b/t/api/scrip.t
@@ -138,11 +138,11 @@ note 'check applications vs. templates';
         CustomCommitCode        => "1;",
     );
     ok($status, 'created a scrip') or diag "error: $msg";
-    main->check_applications($scrip, [$queue], [0, $queue_B]);
+    RT::Test->object_scrips_are($scrip, [$queue], [0, $queue_B]);
 
     ($status, $msg) = $scrip->AddToObject( $queue_B->id );
     ok(!$status, $msg);
-    main->check_applications($scrip, [$queue], [0, $queue_B]);
+    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' );
@@ -150,7 +150,7 @@ note 'check applications vs. templates';
 
     ($status, $msg) = $scrip->AddToObject( $queue_B->id );
     ok($status, 'added scrip to another queue');
-    main->check_applications($scrip, [$queue, $queue_B], [0]);
+    RT::Test->object_scrips_are($scrip, [$queue, $queue_B], [0]);
 
     ($status, $msg) = $scrip->RemoveFromObject( $queue_B->id );
     ok($status, 'removed scrip from queue');
@@ -160,14 +160,14 @@ note 'check applications vs. templates';
 
     ($status, $msg) = $scrip->AddToObject( $queue_B->id );
     ok(!$status, $msg);
-    main->check_applications($scrip, [$queue], [0, $queue_B]);
+    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');
-    main->check_applications($scrip, [$queue, $queue_B], [0]);
+    RT::Test->object_scrips_are($scrip, [$queue, $queue_B], [0]);
 }
 
 note 'basic check for disabling scrips';
@@ -212,25 +212,3 @@ note 'basic check for disabling scrips';
 
     ok(!$scrip->Disabled, "not applied");
 }
-
-sub check_applications {
-    local $Test::Builder::Level = $Test::Builder::Level + 1;
-    my $self = shift;
-    my $scrip = shift;
-    my $to = shift || [];
-    my $not_to = shift || [];
-
-    ok($scrip->IsAdded(ref $_? $_->id : $_), 'added to queue' ) foreach @$to;
-    ok(!$scrip->IsAdded(ref $_? $_->id : $_), 'not added' ) foreach @$not_to;
-    is_deeply(
-        [sort map $_->id, @{ $scrip->AddedTo->ItemsArrayRef }],
-        [sort grep $_, map ref $_? $_->id : $_, @$to],
-        'correct list of queues',
-    );
-    is_deeply(
-        [sort map $_->id, @{ $scrip->NotAddedTo->ItemsArrayRef }],
-        [sort grep $_, map ref $_? $_->id : $_, @$not_to],
-        'correct list of queues',
-    );
-}
-

commit d1498532a85e5f486102b466bd6fc3760e351f0e
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Nov 5 16:33:39 2012 -0800

    Tests: Improve convenience of object_scrips_are
    
    Accepts a scrip ID instead of an object as the first argument.
    
    The individual test descriptions include the queue ID for easier
    debugging.

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 16c65ce..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);
 
@@ -1099,7 +1100,7 @@ sub clean_caught_mails {
 
 =head2 object_scrips_are
 
-Takes an L<RT::Scrip> object as the first argument and an arrayref of
+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
@@ -1118,18 +1119,26 @@ sub object_scrips_are {
     my $to      = shift || [];
     my $not_to  = shift;
 
-    Test::More::ok($scrip->IsAdded(ref $_? $_->id : $_), 'added to queue' ) foreach @$to;
+    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 $_, map ref $_? $_->id : $_, @$to],
+        [sort grep $_, @$to ],
         'correct list of added to queues',
     );
 
     if ($not_to) {
-        Test::More::ok(!$scrip->IsAdded(ref $_? $_->id : $_), 'not added to queue' ) foreach @$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 $_, map ref $_? $_->id : $_, @$not_to],
+            [sort grep $_, @$not_to ],
             'correct list of not added to queues',
         );
     }

commit 29d38c0250c1a0bd57654fed12c3e3b7088e2483
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 9667116..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' );
@@ -186,3 +220,5 @@ note "apply scrip in different stage to different queues";
     is scalar @matches, 1, 'scrip mentioned only once';
 }
 
+undef $m;
+done_testing;

commit 341c3d7e3e6d908333ed2fbd225d805ea01e1c4a
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 75cf296..e04122a 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 29fe857ada78ab7630e7a22899059e84a5888f72
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 8d67804..662dd30 100644
--- a/share/html/Admin/Scrips/Objects.html
+++ b/share/html/Admin/Scrips/Objects.html
@@ -54,16 +54,20 @@
 
 % 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><% loc('Stage') %></h2>
 <& /Admin/Elements/SelectStage, Default => $Stage &>
 
 <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 1e093e0b57c5ce236aa52c4a4b9de9eb149426ef
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 644bbb4..fae2655 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 fe36d19..9d5ea31 100644
--- a/share/html/Elements/RT__Scrip/ColumnMap
+++ b/share/html/Elements/RT__Scrip/ColumnMap
@@ -108,7 +108,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 76412f9efaf04170a055195e84f7f3cd411eb85e
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 d31b1430896ce69ac2ce458cc7b0c3c0ba18f35f
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Nov 5 22:56:44 2012 -0800

    Standardize stage selector text and location when applying scrips

diff --git a/share/html/Admin/Elements/EditScrips b/share/html/Admin/Elements/EditScrips
index b422909..d89bc03 100644
--- a/share/html/Admin/Elements/EditScrips
+++ b/share/html/Admin/Elements/EditScrips
@@ -88,9 +88,7 @@
 <p><i><&|/l&>(No scrips)</&></i></p>
 % }
 
-<div style="text-align:right">
-<% loc('Select stage for scrips you add') %>: <& SelectStage &>
-</div>
+<& SelectStageForAdded &>
 
 <& /Elements/Submit,
     Name => 'AddScrips',
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>
diff --git a/share/html/Admin/Scrips/Objects.html b/share/html/Admin/Scrips/Objects.html
index 662dd30..bd7b658 100644
--- a/share/html/Admin/Scrips/Objects.html
+++ b/share/html/Admin/Scrips/Objects.html
@@ -59,9 +59,6 @@
 <&|/l&>check this box to remove this scrip from all objects and be able to choose specific objects.</&>
 </label>
 % } else {
-<h2><% loc('Stage') %></h2>
-<& /Admin/Elements/SelectStage, Default => $Stage &>
-
 <h2><&|/l&>Apply globally</&></h2>
 
 <label>
@@ -104,6 +101,8 @@
 
 % }
 
+<& /Admin/Elements/SelectStageForAdded, Default => $Stage &>
+
 <& /Elements/Submit, Name => 'Update' &>
 </form>
 

commit c7003ee7ec54e28f9b71f94b5b4f823c7f53a769
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Nov 5 23:30:57 2012 -0800

    Only show stage selector when the scrip isn't applied globally
    
    Otherwise individual queues aren't shown the stage selector is
    nonsensical.

diff --git a/share/html/Admin/Scrips/Objects.html b/share/html/Admin/Scrips/Objects.html
index bd7b658..784f4d5 100644
--- a/share/html/Admin/Scrips/Objects.html
+++ b/share/html/Admin/Scrips/Objects.html
@@ -99,9 +99,8 @@
     ],
 &>
 
-% }
-
 <& /Admin/Elements/SelectStageForAdded, Default => $Stage &>
+% }
 
 <& /Elements/Submit, Name => 'Update' &>
 </form>

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


More information about the Rt-commit mailing list