From: Todd Chapman <todd@chaka.net>
Date: Mon, 3 Oct 2011 19:50:09 -0400
Subject: [PATCH] AT updates to run under RT4. (should still run under RT3)
Origin: upstream, https://github.com/chakatodd/rt-extension-assettracker/commit/2886fa0da01d5463c638bfb4793d30e495494420
Origin: upstream, https://github.com/chakatodd/rt-extension-assettracker/commit/8eff3c324cae3c562b4772a19a931437e52fafed

diff --git a/html/AssetTracker/Admin/Elements/ObjectCustomFields b/html/AssetTracker/Admin/Elements/ObjectCustomFields
index 6b6d658..019eb42 100644
--- a/html/AssetTracker/Admin/Elements/ObjectCustomFields
+++ b/html/AssetTracker/Admin/Elements/ObjectCustomFields
@@ -86,7 +86,10 @@ if ($SubType =~/^RTx::AssetTracker::(.*)$/)  {
     $QualifiedType = $Type;
 }
 
-if ($id) { 
+if ($RT::VERSION =~ /^4/) {
+    $ObjectTabs = "/Elements/Tabs";
+}
+elsif ($id) { 
     $Object->Load($id) || Abort(loc("Couldn't load object [_1]", $id));
     $ObjectTabs = "/AssetTracker/Admin/Elements/${Type}Tabs";
 } else {
diff --git a/html/AssetTracker/Admin/Elements/TypeTabs b/html/AssetTracker/Admin/Elements/TypeTabs
index 9cec762..41649c0 100755
--- a/html/AssetTracker/Admin/Elements/TypeTabs
+++ b/html/AssetTracker/Admin/Elements/TypeTabs
@@ -43,14 +43,24 @@
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
+% if (!$rt4) {
 <& /Admin/Elements/Tabs, 
     subtabs => $tabs, 
     current_tab => 'AssetTracker/Admin/Types/index.html', 
     current_subtab => $current_tab, 
     Title => $Title &>
+% } else {
+    <& /Elements/Tabs &>
+% }
 
 <%INIT>
 my $tabs;
+my $rt4;
+if ($RT::VERSION !~ /^3/) {
+    $rt4 = 1;
+}
+else {
+
 if ($id) {
   $tabs->{'this'}  = {
                 title => $AssetTypeObj->Name,
@@ -101,6 +111,7 @@ foreach my $tab ( sort keys %{$tabs->{'this'}->{'subtabs'}} ) {
     }                                                                           
 }   
   		     	$current_tab = "AssetTracker/Admin/Types/Modify.html?id=".$id if $id;
+}
 </%INIT>
   
 <%ARGS>
diff --git a/html/AssetTracker/Admin/Types/Modify.html b/html/AssetTracker/Admin/Types/Modify.html
index 646f2f6..501cd8f 100755
--- a/html/AssetTracker/Admin/Types/Modify.html
+++ b/html/AssetTracker/Admin/Types/Modify.html
@@ -112,10 +112,10 @@ if ($Create) {
        delete $session{'create_in_types'};
        if ($val == 0 ) {
            Abort("$msg");
-	}
-	else {
-		push @results, $msg;
-	}    
+	   }
+	   else {
+	        push @results, $msg;
+	   }    
      }
      else {
         $TypeObj->Load($id) || $TypeObj->Load($Name) || Abort("Couldn't load type '$Name'");
@@ -128,7 +128,7 @@ if ($TypeObj->Id()) {
     delete $session{'create_in_types'};
 my @attribs= qw(Description Name );
 
-  @results = UpdateRecordObject( AttributesRef => \@attribs, 
+  push @results, UpdateRecordObject( AttributesRef => \@attribs, 
 				    Object => $TypeObj, 
 				    ARGSRef => \%ARGS);
 
@@ -155,6 +155,12 @@ my @attribs= qw(Description Name );
 #	     RecordObj => $TypeObj, ARGSRef => \%ARGS, 
 #	     _CallbackName => 'ProcessLinks');
 #    push @results, @linkresults;
+
+# This code does automatic redirection if any updates happen.
+    ATMaybeRedirectForResults(
+        Actions   => \@results,
+        Arguments => { id => $TypeObj->Id },
+    );
 }
 </%INIT>
 
diff --git a/html/AssetTracker/Elements/SelectAsset b/html/AssetTracker/Elements/SelectAsset
index 3d159fa..65a7ab4 100755
--- a/html/AssetTracker/Elements/SelectAsset
+++ b/html/AssetTracker/Elements/SelectAsset
@@ -49,7 +49,7 @@
 % }
 % while (my $asset = $Assets->Next) {
 %    next unless $asset->CurrentUserHasRight($CheckTypeRight);
-<OPTION VALUE="<%$asset->Id%>" <%( $asset->Id eq $Default ? 'SELECTED' : '')%>><%$asset->Name%>
+<OPTION VALUE="<%$asset->$Method%>" <%( $asset->Id eq $Default ? 'SELECTED' : '')%>><%$asset->Name%>
 %   if (($Verbose) and ($asset->Description) ){
 (<%$asset->Description%>)
 %   }
@@ -66,6 +66,7 @@ $NamedValues => 0
 $Default => 0
 $Lite => 0
 $Exclude => undef
+$Method => 'id'
 </%ARGS>
 
 <%INIT>
diff --git a/html/AssetTracker/Tools/Import.html b/html/AssetTracker/Tools/Import.html
index 5fc6204..6e22977 100644
--- a/html/AssetTracker/Tools/Import.html
+++ b/html/AssetTracker/Tools/Import.html
@@ -43,7 +43,6 @@ if ($Import && $XML) {
     eval {
 	    ($rv, $results) = $assets->ImportXML($xml_data, $RunScrips, $Detailed);
         unshift @$results, loc("Asset import errors. No changes were made.") unless $rv;
-        use YAML; warn Dump([$rv, $results]);
     };
     if ($@) {
         unshift @$results, loc("Asset import exception: [_1]", $@) unless $rv;
diff --git a/html/Callbacks/AssetTracker/Elements/Tabs/Privileged b/html/Callbacks/AssetTracker/Elements/Tabs/Privileged
new file mode 100644
index 0000000..159c729
--- /dev/null
+++ b/html/Callbacks/AssetTracker/Elements/Tabs/Privileged
@@ -0,0 +1,247 @@
+<%INIT>
+my $request_path = $HTML::Mason::Commands::r->path_info;
+
+my $query_string = sub {
+    my %args = @_;
+    my $u    = URI->new();
+    $u->query_form(%args);
+    return $u->query;
+};
+
+warn keys %session;
+
+my $assets;
+eval {
+    $assets = Menu->child(  assets => title => loc('Assets'), path => '/AssetTracker/index.html', sort_order => 3 );
+};
+if ($@) {
+    return;
+}
+
+$assets->child( new => title => loc('New Search') => path => "/AssetTracker/Search/Build.html?NewQuery=1" );
+
+    if ( $request_path =~ m{^/AssetTracker/Asset/} ) {
+        if ( ( $m->request_args->{'id'} || '' ) =~ /^(\d+)$/ ) {
+            my $id  = $1;
+            my $obj = RTx::AssetTracker::Asset->new( $session{'CurrentUser'} );
+            $obj->Load($id);
+
+            my $tabs = PageMenu();
+
+            $tabs->child( display => title => loc('Display') => path => "/AssetTracker/Asset/Display.html?id=" . $id );
+
+            $tabs->child( history => title => loc('History') => path => "/AssetTracker/Asset/History.html?id=" . $id );
+
+            my %can = %{ $obj->CurrentUser->PrincipalObj->HasRights( Object => $obj ) };
+            $can{'_ModifyOwner'} = $can{'OwnAsset'} || $can{'TakeAsset'} || $can{'StealAsset'};
+            my $can = sub {
+                unless ($_[0] eq 'ExecuteCode') {
+                    return $can{$_[0]} || $can{'SuperUser'};
+                } else {
+                    return !RT->Config->Get('DisallowExecuteCode')
+                        && ( $can{'ExecuteCode'} || $can{'SuperUser'} );
+                }
+            };
+
+            # comment out until we can do it for an individual custom field
+            #if ( $can->('ModifyAsset') || $can->('ModifyCustomField') ) {
+            $tabs->child( basics => title => loc('Basics'), path => "/AssetTracker/Asset/Modify.html?id=" . $id, );
+
+            #}
+
+            if ( $can->('ModifyAsset') || $can->('_ModifyOwner') || $can->('Watch') || $can->('WatchAsAdminCc') ) {
+                $tabs->child( people => title => loc('People'), path => "/AssetTracker/Asset/ModifyPeople.html?id=" . $id,);
+            }
+
+            if ( $can->('ModifyAsset') ) {
+                $tabs->child( ips => title => loc('IPs'), path => "/AssetTracker/Asset/ModifyIPs.html?id=" . $id, );
+                $tabs->child( fields => title => loc('Fields'), path => "/AssetTracker/Asset/ModifyFields.html?id=" . $id, );
+                $tabs->child( links => title => loc('Links'), path => "/AssetTracker/Asset/ModifyLinks.html?id=" . $id, );
+            }
+            #if ( $can->('ModifyAsset') || $can->('ModifyCustomField') || $can->('_ModifyOwner') ) {
+            $tabs->child( jumbo => title => loc('Jumbo'), path => "/AssetTracker/Asset/ModifyAll.html?id=" . $id, );
+            #}
+
+            #AT removed lifecycle stuff
+
+            #AT removed ticket taking/stealing
+
+            if ( defined $session{"assets"} ) {
+                # we have to update session data if we get new ItemMap
+                my $updatesession = 1 unless ( $session{"assets"}->{'item_map'} );
+
+                my $item_map = $session{"assets"}->ItemMap;
+
+                if ($updatesession) {
+                    $session{"assets"}->PrepForSerialization();
+                }
+
+                my $search = $assets; #Menu()->child('search');
+                # Don't display prev links if we're on the first ticket
+                if ( $item_map->{$id}->{prev} ) {
+                    $search->child( 'first', title => ' ' . loc('First'), escape_title => 0, class => "nav",
+                                    path => "/AssetTracker/Asset/Display.html?id=" . $item_map->{first});
+                    $search->child( prev => title => '< ' . loc('Prev') => class => "nav",
+                                    path => "/AssetTracker/Asset/Display.html?id=" . $item_map->{$id}->{prev});
+                }
+                # Don't display next links if we're on the last ticket
+                if ( $item_map->{$id}->{next} ) {
+                    $search->child( next       => title => loc('Next') . ' >' => class => "nav",
+                                    path => "/AssetTracker/Asset/Display.html?id=" . $item_map->{$id}->{next});
+                    $search->child( last        => title => loc('Last') . ' >>' => class => "nav",
+                                    path => "/AssetTracker/Asset/Display.html?id=" . $item_map->{last});
+                }
+            }
+        }
+    }
+
+    if ( $request_path =~ m{^/AssetTracker/(?:Asset|Search)/} && $request_path !~ m{^/Search/index\.html}) {
+        my $search = $assets; #->child('search');
+        my $args      = '';
+        my $has_query = '';
+        my $current_search = $session{"CurrentSearchHash"} || {};
+        my $search_id = $m->request_args->{'SavedSearchLoad'} || $m->request_args->{'SavedSearchId'} || $search->{'SearchId'} || '';
+
+        $has_query = 1 if ( $m->request_args->{'Query'} or $current_search->{'Query'} );
+
+        my %query_args = (
+            SavedSearchId => ( $search_id eq 'new' ) ? undef : $search_id,
+            SavedChartSearchId => $m->request_args->{'SavedChartSearchId'} || $current_search->{SavedChartSearchId},
+            Query => $m->request_args->{'Query'} || $current_search->{'Query'},
+            Format => $m->request_args->{'Format'} || $current_search->{'Format'},
+            OrderBy => $m->request_args->{'OrderBy'} || $current_search->{'OrderBy'} ||'',
+            Order => $m->request_args->{'Order'} || $current_search->{'Order'} ||'',
+            Page => $m->request_args->{'Page'} || $current_search->{'Page'},
+            RowsPerPage => ( defined $m->request_args->{'RowsPerPage'}
+                             ? $m->request_args->{'RowsPerPage'}
+                             : $current_search->{'RowsPerPage'})
+                         );
+        for my $field (qw(Order OrderBy)) {
+            if ( ref( $query_args{$field} ) eq 'ARRAY' ) {
+                $query_args{$field} = join( "|", @{ $query_args{$field} } );
+            } elsif (not defined $query_args{$field}) {
+                delete $query_args{$field};
+            }
+        }
+
+        $args = "?" . ($QueryString || $query_string->(%query_args));
+
+
+        my $current_search_menu;
+        if ( $request_path =~ m{^/AssetTracker/Asset} ) {
+            $current_search_menu = $search->child( current_search => title => loc('Current Search') );
+            $current_search_menu->path("/Search/Results.html$args") if $has_query;
+        } else {
+            $current_search_menu = PageMenu();
+        }
+
+        $current_search_menu->child( edit_search => title => loc('Edit Search') =>
+                                     path => "/AssetTracker/Search/Build.html" . ( ($has_query) ? $args : '' ) );
+        $current_search_menu->child( loc('Advanced') => path => "/AssetTracker/Search/Edit.html$args" );
+        if ($has_query) {
+            $current_search_menu->child( results => title => loc('Show Results') => path => "/AssetTracker/Search/Results.html$args" );
+        }
+
+        if ( $has_query ) {
+            my $bulk = $current_search_menu->child( bulk => title => loc('Bulk Update'));
+            $bulk->child( batch => title => loc('Batch Update multiple assets'), path => "/AssetTracker/Search/Bulk.html$args" );
+            $bulk->child( grid  => title => loc('Grid Update multiple assets'),  path => "/AssetTracker/Search/Grid.html$args" );
+
+            my $more = $current_search_menu->child( more => title => loc('Feeds') );
+
+            $more->child( spreadsheet => title => loc('Spreadsheet'), path => "/AssetTracker/Search/Results.tsv$args" );
+            $more->child( importable => title => loc('Importable Spreadsheet'), path => "/AssetTracker/Search/Export.html$args" );
+
+            if ($request_path =~ m{^/AssetTracker/Search/Results.html}
+                &&                        #XXX TODO better abstraction
+                $session{'CurrentUser'}->HasRight( Right => 'SuperUser', Object => RT->System )) {
+                my $shred_args = $query_string->(
+                    search          => 1,
+                    plugin          => 'Assets',
+                    'Assets:query' => $query_args{'Query'},
+                    'Assets:limit' => $query_args{'Rows'},
+                );
+
+                $more->child( 'shredder' => title => loc('Shredder'),
+                                path => '/Admin/Tools/Shredder/?' . $shred_args);
+            }
+        }
+    }
+
+    if ( $request_path =~ m{^/AssetTracker/Admin/Types} ) {
+        my $type = 'Types';
+        my $tabs = PageMenu();
+
+        my $section;
+        if ( $request_path =~ m{^/AssetTracker/Admin/$type/?(?:index.html)?$}
+             || (    $request_path =~ m{^/AssetTracker/Admin/$type/(?:Modify.html)$}
+                  && $m->request_args->{'Create'} )
+           )
+
+        {
+            $section = $tabs;
+
+        } else {
+            $section = $tabs->child( select => title => loc('Asset Types'),
+                                     path => "/Admin/$type/" );
+        }
+
+        $section->child( select => title => loc('Select'),
+                         path   => "/AssetTracker/Admin/$type/" );
+        $section->child( create => title => loc('Create'),
+                         path => "/AssetTracker/Admin/$type/Modify.html?Create=1" );
+    }
+
+    if ( $request_path =~ m{^/AssetTracker/Admin/Types} ) {
+        if ( $m->request_args->{'id'} && $m->request_args->{'id'} =~ /^\d+$/
+                ||
+              $m->request_args->{'Type'} && $m->request_args->{'Type'} =~ /^\d+$/
+                ) {
+            my $id = $m->request_args->{'Type'} || $m->request_args->{'id'};
+            my $type_obj = RTx::AssetTracker::Type->new( $session{'CurrentUser'} );
+            $type_obj->Load($id);
+
+            my $type = PageMenu();
+            $type->child( basics => title => loc('Basics'), path => "/AssetTracker/Admin/Types/Modify.html?id=" . $id );
+            $type->child( people => title => loc('Watchers'), path => "/AssetTracker/Admin/Types/People.html?id=" . $id );
+            my $templates = $type->child(templates => title => loc('Templates'),
+                              path => "/AssetTracker/Admin/Types/Templates.html?id=" . $id);
+
+            $templates->child(select => title => loc('Select'), path => "/AssetTracker/Admin/Types/Templates.html?id=".$id);
+            $templates->child( create => title => loc('Create'), path => "/AssetTracker/Admin/Types/Template.html?Create=1;Queue=".$id);
+
+            my $scrips = $type->child( scrips => title => loc('Scrips'), path => "/AssetTracker/Admin/Types/Scrips.html?id=" . $id);
+
+            $scrips->child( select => title => loc('Select'), path => "/AssetTracker/Admin/Types/Scrips.html?id=" . $id );
+            $scrips->child( create => title => loc('Create'), path => "/AssetTracker/Admin/Types/Scrip.html?Create=1;Queue=" . $id);
+
+            my $ticket_cfs = $type->child( 'ticket-custom-fields' => title => loc('Asset Custom Fields'),
+                  path => '/AssetTracker/Admin/Types/CustomFields.html?SubType=RTx::AssetTracker::Asset&id=' . $id );
+
+            $type->child( 'group-rights' => title => loc('Group Rights'), path => "/AssetTracker/Admin/Types/GroupRights.html?id=".$id );
+            $type->child( 'user-rights' => title => loc('User Rights'), path => "/AssetTracker/Admin/Types/UserRights.html?id=" . $id );
+
+        }
+    }
+
+    my $tools = Menu->child("tools");
+    my $admin = $tools->child("config");
+    if ($admin) {
+        my $types = $admin->child( 'asset-types' => title => loc('Asset Types'), path => '/AssetTracker/Admin/Types/', description => loc('Manage asset types and asset-specific properties') );
+        $types->child( select => title => loc('Select'), path => "/AssetTracker/Admin/Types/index.html" );
+        $types->child( create => title => loc('Create'), path => "/AssetTracker/Admin/Types/Modify.html?Create=1" );
+
+        my $cfadmin = $admin->child("global")->child("custom-fields");
+        $cfadmin->child( assets => title => loc('Assets') => text => loc('Select custom fields for all assets'),
+                            path => '/AssetTracker/Admin/Global/CustomFields/Type-Assets.html');
+        $cfadmin->child( types => title => loc('Asset Types') => text => loc('Select custom fields for asset types'),
+                            path => '/AssetTracker/Admin/Global/CustomFields/Types.html');
+
+    }
+
+    PageWidgets()->child( create_asset => raw_html => $m->scomp('/AssetTracker/Elements/CreateAsset'), sort_order => 99 );
+</%INIT>
+<%ARGS>
+$show_menu => 1
+$QueryString => ''
+</%ARGS>
diff --git a/html/Elements/RTx__AssetTracker__Asset/ColumnMap b/html/Elements/RTx__AssetTracker__Asset/ColumnMap
index ad269fa..d33001d 100644
--- a/html/Elements/RTx__AssetTracker__Asset/ColumnMap
+++ b/html/Elements/RTx__AssetTracker__Asset/ColumnMap
@@ -97,7 +97,6 @@ sub AssetLinkCallback {
     my $mode_uri        = $mode.'URI';
     my $mode_is_local   = $mode.'IsLocal';
     my $local_type      = 'Local'.$mode;
-	use YAML;
 
 	{ export_value =>
         sub {
diff --git a/lib/RT/System_Vendor.pm b/lib/RT/System_Vendor.pm
new file mode 100644
index 0000000..e9f2a92
--- /dev/null
+++ b/lib/RT/System_Vendor.pm
@@ -0,0 +1,49 @@
+package RT::System;
+
+use strict;
+no warnings qw(redefine);
+
+sub AvailableRights {
+    my $self = shift;
+
+    my $queue = RT::Queue->new(RT->SystemUser);
+    my $group = RT::Group->new(RT->SystemUser);
+    my $cf    = RT::CustomField->new(RT->SystemUser);
+    my $class = RT::Class->new(RT->SystemUser);
+    my $type =  RTx::AssetTracker::Type->new(RT->SystemUser);
+
+    my $qr = $queue->AvailableRights();
+    my $gr = $group->AvailableRights();
+    my $cr = $cf->AvailableRights();
+    my $clr = $class->AvailableRights();
+    my $tr = $type->AvailableRights();
+
+    # Build a merged list of all system wide rights, queue rights and group rights.
+    my %rights = (%{$RT::System::RIGHTS}, %{$gr}, %{$qr}, %{$cr}, %{$clr}, %$tr);
+    delete $rights{ExecuteCode} if RT->Config->Get('DisallowExecuteCode');
+
+    return(\%rights);
+}
+
+sub RightCategories {
+    my $self = shift;
+
+    my $queue = RT::Queue->new(RT->SystemUser);
+    my $group = RT::Group->new(RT->SystemUser);
+    my $cf    = RT::CustomField->new(RT->SystemUser);
+    my $class = RT::Class->new(RT->SystemUser);
+    my $type =  RTx::AssetTracker::Type->new(RT->SystemUser);
+
+    my $qr = $queue->RightCategories();
+    my $gr = $group->RightCategories();
+    my $cr = $cf->RightCategories();
+    my $clr = $class->RightCategories();
+    my $tr = $type->RightCategories();
+
+    # Build a merged list of all system wide rights, queue rights and group rights.
+    my %rights = (%{$RT::System::RIGHT_CATEGORIES}, %{$gr}, %{$qr}, %{$cr}, %{$clr}, %$tr);
+
+    return(\%rights);
+}
+
+1;
diff --git a/lib/RTx/AssetTracker/Interface/Web.pm b/lib/RTx/AssetTracker/Interface/Web.pm
index 8479b57..2084d1e 100755
--- a/lib/RTx/AssetTracker/Interface/Web.pm
+++ b/lib/RTx/AssetTracker/Interface/Web.pm
@@ -1021,6 +1021,39 @@ sub ActiveRoleArray {
 
 }
 
+sub ATMaybeRedirectForResults {
+    my %args = (
+        Path      => $HTML::Mason::Commands::m->request_comp->path,
+        Arguments => {},
+        Anchor    => undef,
+        Actions   => undef,
+        Force     => 0,
+        @_
+    );
+    my $has_actions = $args{'Actions'} && grep( defined, @{ $args{'Actions'} } );
+
+    return unless $has_actions || $args{'Force'};
+
+    my %arguments = %{ $args{'Arguments'} };
+
+    if ( $has_actions ) {
+        my $key = Digest::MD5::md5_hex( rand(1024) );
+        push @{ $session{"Actions"}{ $key } ||= [] }, @{ $args{'Actions'} };
+        $session{'i'}++;
+        $arguments{'results'} = $key;
+    }
+
+    $args{'Path'} =~ s!^/+!!;
+    my $url = RT->Config->Get('WebURL') . $args{Path};
+
+    if ( keys %arguments ) {
+        $url .= '?'. $m->comp( '/Elements/QueryString', %arguments );
+    }
+    if ( $args{'Anchor'} ) {
+        $url .= "#". $args{'Anchor'};
+    }
+    return RT::Interface::Web::Redirect($url);
+}
 
 eval "require RTx::AssetTracker::Interface::Web_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RTx/AssetTracker/Interface/Web_Vendor.pm});
diff --git a/lib/RTx/AssetTracker/Type_Overlay.pm b/lib/RTx/AssetTracker/Type_Overlay.pm
index c4dcc00..f495a18 100755
--- a/lib/RTx/AssetTracker/Type_Overlay.pm
+++ b/lib/RTx/AssetTracker/Type_Overlay.pm
@@ -39,6 +39,7 @@ $RIGHTS = {
     AdminType          => 'Create, delete and modify asset types',        # loc_pair
     AssignCustomFields => 'Assign and remove custom fields',              # loc_pair
     ModifyTypeAdmins   => 'Modify administrators for type',              # loc_pair
+    ModifyTypeWatchers   => 'Modify watchers for type',              # loc_pair
     
     ShowAsset      => 'See asset details',                                # loc_pair
     CreateAsset    => 'Create assets of this type',                       # loc_pair
@@ -49,6 +50,19 @@ $RIGHTS = {
 
 };
 
+our $RIGHT_CATEGORIES = {
+        SeeType            => 'General',
+        ShowAsset          => 'General',
+        ModifyAsset        => 'General',
+        CreateAsset        => 'General',
+        AdminType          => 'Admin',
+        ModifyTypeAdmins   => 'Staff',
+        ModifyTypeWatchers   => 'Staff',
+        RetireAsset        => 'Staff',
+        OwnAsset           => 'General',
+        WatchAsAdmin       => 'General',
+};
+
 # Tell RT::ACE that this sort of object can get acls granted
 $RT::ACE::OBJECT_TYPES{'RTx::AssetTracker::Type'} = 1;
 
@@ -366,6 +380,10 @@ sub AvailableRights {
     return($RIGHTS);
 }
 
+sub RightCategories {
+        return $RIGHT_CATEGORIES;
+}
+
 # {{{ sub Create
 
 
diff --git a/README b/README
index 42ebef1..6ffa6f8 100644
--- a/README
+++ b/README
@@ -4,12 +4,12 @@ The work is released under the GPL
 
 PREREQUISITES
 
-A working RT 3.8 installation. (3.8.1 or greater)
+A working RT 3.8/4 installation. (3.8.1 or greater)
 
 WARNING
 
 Only mysql and SQLite are tested by the author. Feel free to send me a
-schema file for other databases supported by RT 3.8
+schema file for other databases supported by RT.
 
 Oracle schema provided by Joop van de Wege.
 Postgres schema provided by Rolf Schaufelberger.
@@ -27,7 +27,7 @@ Add RTx::AssetTracker to your Plugins in etc/RT_SiteConfig.pm and restart your w
 
 UPGRADE
 
-If you have AT 1.2.4 installed under RT 3.8, do the following:
+If you have AT 1.2.4 installed under RT, do the following:
 
 1. Make a backup of any AT customizations.
 
@@ -53,13 +53,13 @@ If you have AT 1.2.4 installed under RT 3.8, do the following:
     from etc/upgrade/<version> directory: If the dir has any schema
     files then run:
 
-            /opt/rt3/sbin/rt-setup-database --dba <dba> \
+            /opt/rt4/sbin/rt-setup-database --dba <dba> \
             --prompt-for-dba-password --action schema \
             --datadir etc/upgrade/<version>
 
     If the dir has a file named 'content' then run:
 
-        /opt/rt3/sbin/rt-setup-database --dba <dba> \
+        /opt/rt4/sbin/rt-setup-database --dba <dba> \
             --prompt-for-dba-password --action insert \
             --datadir etc/upgrade/<version>
 
-- 
1.7.5.4

