[Rt-commit] rt branch, 5.0-trunk, updated. rt-5.0.0alpha1-55-g5da73ebd50

? sunnavy sunnavy at bestpractical.com
Wed Apr 1 14:56:19 EDT 2020


The branch, 5.0-trunk has been updated
       via  5da73ebd505cf4d8ab54854ce0ced0c521c281c3 (commit)
       via  659fc7caa5afc8e22224256bfa71b7bd6a284a19 (commit)
       via  be6be835d01a39f595503dd7fd4480aed178b482 (commit)
       via  62536f6253316f948a3a590e192bc017fe92fcd4 (commit)
       via  ed92fa39c5b7efb903664edb1bf522b6a458a95d (commit)
       via  504e2e244a29df9fb714ee42b7ee46b6a9138392 (commit)
       via  d34d59696f0d1acc2df8a821fc5cf4a110bcba75 (commit)
       via  09ebb3b9e95d98bc0c1f4c1a576f0d0f53f46bd2 (commit)
       via  dd95c46b91697608e1ee9c8789deaeb6d74790d4 (commit)
       via  05e156db45945a07a6c5d02297308e96207ef44a (commit)
       via  840738e0259de886a7c7f56451952c6191c172a8 (commit)
      from  88bbcbe91f312bc27452362f95e3f814d8acb2f0 (commit)

Summary of changes:
 .travis.yml                         |  20 ++++++++
 Dockerfile                          |  12 +++++
 configure.ac                        |   2 +-
 docs/hacking.pod                    |  46 +++++++++++------
 lib/RT/Interface/Web/MenuBuilder.pm |  21 +++-----
 lib/RT/Test.pm                      |   4 ++
 lib/RT/User.pm                      |  49 +++++++++++++++---
 t/api/user_recently_visited.t       | 100 ++++++++++++++++++++++++++++++++++++
 t/web/shredder.t                    |   2 +-
 9 files changed, 218 insertions(+), 38 deletions(-)
 create mode 100644 .travis.yml
 create mode 100644 Dockerfile
 create mode 100644 t/api/user_recently_visited.t

- Log -----------------------------------------------------------------
commit 659fc7caa5afc8e22224256bfa71b7bd6a284a19
Merge: 88bbcbe91f be6be835d0
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Apr 1 23:17:23 2020 +0800

    Merge branch '4.4-trunk' into 5.0-trunk

diff --cc lib/RT/Interface/Web/MenuBuilder.pm
index 8852112603,0000000000..8d40911f17
mode 100644,000000..100644
--- a/lib/RT/Interface/Web/MenuBuilder.pm
+++ b/lib/RT/Interface/Web/MenuBuilder.pm
@@@ -1,1383 -1,0 +1,1378 @@@
 +# BEGIN BPS TAGGED BLOCK {{{
 +#
 +# COPYRIGHT:
 +#
 +# This software is Copyright (c) 1996-2019 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 }}}
 +
 +=head1 NAME
 +
 +RT::Interface::Web::MenuBuilder
 +
 +=cut
 +
 +use strict;
 +use warnings;
 +
 +package RT::Interface::Web::MenuBuilder;
 +
 +sub loc { HTML::Mason::Commands::loc( @_ ); }
 +
 +sub QueryString {
 +    my %args = @_;
 +    my $u    = URI->new();
 +    $u->query_form(map { $_ => $args{$_} } sort keys %args);
 +    return $u->query;
 +}
 +
 +sub BuildMainNav {
 +    my $request_path = shift;
 +    my $top          = shift;
 +    my $widgets      = shift;
 +    my $page         = shift;
 +
 +    my %args = ( @_ );
 +
 +    my $query_string = $args{QueryString};
 +    my $query_args = $args{QueryArgs};
 +
 +    my $current_user = $HTML::Mason::Commands::session{CurrentUser};
 +
 +    if ($request_path =~ m{^/Asset/}) {
 +        $widgets->child( asset_search => raw_html => $HTML::Mason::Commands::m->scomp('/Asset/Elements/Search') );
 +        $widgets->child( create_asset => raw_html => $HTML::Mason::Commands::m->scomp('/Asset/Elements/CreateAsset') );
 +    }
 +    elsif ($request_path =~ m{^/Articles/}) {
 +        $widgets->child( article_search => raw_html => $HTML::Mason::Commands::m->scomp('/Articles/Elements/GotoArticle') );
 +        $widgets->child( create_article => raw_html => $HTML::Mason::Commands::m->scomp('/Articles/Elements/CreateArticleButton') );
 +    } else {
 +        $widgets->child( simple_search => raw_html => $HTML::Mason::Commands::m->scomp('SimpleSearch', Placeholder => loc('Search Tickets')) );
 +        $widgets->child( create_ticket => raw_html => $HTML::Mason::Commands::m->scomp('CreateTicket') );
 +    }
 +
 +    my $home = $top->child( home => title => loc('Homepage'), path => '/' );
 +    unless ($HTML::Mason::Commands::session{'dashboards_in_menu'}) {
 +        my $dashboards_in_menu = $current_user->UserObj->Preferences(
 +            'DashboardsInMenu',
 +            {},
 +        );
 +
 +        unless ($dashboards_in_menu->{dashboards}) {
 +            my ($default_dashboards) =
 +                RT::System->new( $current_user )
 +                    ->Attributes
 +                    ->Named('DashboardsInMenu');
 +            if ($default_dashboards) {
 +                $dashboards_in_menu = $default_dashboards->Content;
 +            }
 +        }
 +
 +        $HTML::Mason::Commands::session{'dashboards_in_menu'} = $dashboards_in_menu->{dashboards} || [];
 +    }
 +
 +    my @dashboards;
 +    for my $id ( @{$HTML::Mason::Commands::session{'dashboards_in_menu'}} ) {
 +        my $dash = RT::Dashboard->new( $current_user );
 +        my ( $status, $msg ) = $dash->LoadById($id);
 +        if ( $status ) {
 +            push @dashboards, $dash;
 +        } else {
 +            $RT::Logger->debug( "Failed to load dashboard $id: $msg, removing from menu" );
 +            $home->RemoveDashboardMenuItem(
 +                DashboardId => $id,
 +                CurrentUser => $HTML::Mason::Commands::session{CurrentUser}->UserObj,
 +            );
 +            @{ $HTML::Mason::Commands::session{'dashboards_in_menu'} } =
 +              grep { $_ != $id } @{ $HTML::Mason::Commands::session{'dashboards_in_menu'} };
 +        }
 +    }
 +
 +    my $dashes = $top->child('home');
 +    if (@dashboards) {
 +        for my $dash (@dashboards) {
 +            $home->child( 'dashboard-' . $dash->id,
 +                title => $dash->Name,
 +                path  => '/Dashboards/' . $dash->id . '/' . $dash->Name
 +            );
 +        }
 +    }
 +    $dashes->child( edit => title => loc('Update This Menu'), path => 'Prefs/DashboardsInMenu.html' );
 +    $dashes->child( more => title => loc('All Dashboards'),   path => 'Dashboards/index.html' );
 +    my $dashboard = RT::Dashboard->new( $current_user );
 +    if ( $dashboard->CurrentUserCanCreateAny ) {
 +        $dashes->child('dashboard_create' => title => loc('New Dashboard'), path => "/Dashboards/Modify.html?Create=1" );
 +    }
 +
 +    my $search = $top->child( search => title => loc('Search'), path => '/Search/Simple.html' );
 +
 +    my $tickets = $search->child( tickets => title => loc('Tickets'), path => '/Search/Build.html' );
 +    $tickets->child( simple => title => loc('Simple Search'), path => "/Search/Simple.html" );
 +    $tickets->child( new    => title => loc('New Search'),    path => "/Search/Build.html?NewQuery=1" );
 +
 +    my $recents = $tickets->child( recent => title => loc('Recently Viewed'));
-     for ($current_user->RecentlyViewedTickets) {
-         my ($ticketId, $timestamp) = @$_;
-         my $ticket = RT::Ticket->new($current_user);
-         $ticket->Load($ticketId);
-         if ($ticket->Id) {
-             my $title = $ticket->Subject || loc("(No subject)");
-             if (length $title > 50) {
-                 $title = substr($title, 0, 47);
-                 $title =~ s/\s+$//;
-                 $title .= "...";
-             }
-             $title = "#$ticketId: " . $title;
-             $recents->child( "$ticketId" => title => $title, path => "/Ticket/Display.html?id=" . $ticket->Id );
++    for my $ticket ( $current_user->RecentlyViewedTickets ) {
++        my $title = $ticket->{subject} || loc( "(No subject)" );
++        if ( length $title > 50 ) {
++            $title = substr($title, 0, 47);
++            $title =~ s/\s+$//;
++            $title .= "...";
 +        }
++        $title = "#$ticket->{id}: " . $title;
++        $recents->child( "$ticket->{id}" => title => $title, path => "/Ticket/Display.html?id=" . $ticket->{id} );
 +    }
 +
 +    $search->child( articles => title => loc('Articles'),   path => "/Articles/Article/Search.html" )
 +        if $current_user->HasRight( Right => 'ShowArticlesMenu', Object => RT->System );
 +
 +    $search->child( users => title => loc('Users'),   path => "/User/Search.html" );
 +
 +    $search->child( groups      =>
 +                    title       => loc('Groups'),
 +                    path        => "/Group/Search.html",
 +                    description => 'Group search'
 +    );
 +
 +    $search->child( assets => title => loc("Assets"), path => "/Asset/Search/" )
 +        if $current_user->HasRight( Right => 'ShowAssetsMenu', Object => RT->System );
 +
 +    my $txns = $search->child( transactions => title => loc('Transactions'), path => '/Search/Build.html?Class=RT::Transactions&ObjectType=RT::Ticket' );
 +    my $txns_tickets = $txns->child( tickets => title => loc('Tickets'), path => "/Search/Build.html?Class=RT::Transactions&ObjectType=RT::Ticket" );
 +    $txns_tickets->child( new => title => loc('New Search'), path => "/Search/Build.html?Class=RT::Transactions&ObjectType=RT::Ticket&NewQuery=1" );
 +
 +    my $reports = $top->child( reports =>
 +        title       => loc('Reports'),
 +        description => loc('Reports summarizing ticket resolution and status'),
 +        path        => loc('/Reports'),
 +    );
 +    $reports->child( resolvedbyowner =>
 +        title       => loc('Resolved by owner'),
 +        path        => '/Reports/ResolvedByOwner.html',
 +        description => loc('Examine tickets resolved in a queue, grouped by owner'),
 +    );
 +    $reports->child( resolvedindaterange =>
 +        title       => loc('Resolved in date range'),
 +        path        => '/Reports/ResolvedByDates.html',
 +        description => loc('Examine tickets resolved in a queue between two dates'),
 +    );
 +    $reports->child( createdindaterange =>
 +        title       => loc('Created in a date range'),
 +        path        => '/Reports/CreatedByDates.html',
 +        description => loc('Examine tickets created in a queue between two dates'),
 +    );
 +
 +    if ($current_user->HasRight( Right => 'ShowArticlesMenu', Object => RT->System )) {
 +        my $articles = $top->child( articles => title => loc('Articles'), path => "/Articles/index.html");
 +        $articles->child( articles => title => loc('Overview'), path => "/Articles/index.html" );
 +        $articles->child( topics   => title => loc('Topics'),   path => "/Articles/Topics.html" );
 +        $articles->child( create   => title => loc('Create'),   path => "/Articles/Article/PreCreate.html" );
 +        $articles->child( search   => title => loc('Search'),   path => "/Articles/Article/Search.html" );
 +    }
 +
 +    if ($current_user->HasRight( Right => 'ShowAssetsMenu', Object => RT->System )) {
 +        my $assets = $top->child( "assets", title => loc("Assets"), path => "/Asset/Search/" );
 +        $assets->child( "create", title => loc("Create"), path => "/Asset/CreateInCatalog.html" );
 +        $assets->child( "search", title => loc("Search"), path => "/Asset/Search/" );
 +    }
 +
 +    my $tools = $top->child( tools => title => loc('Tools'), path => '/Tools/index.html' );
 +
 +    $tools->child( my_day =>
 +        title       => loc('My Day'),
 +        description => loc('Easy updating of your open tickets'),
 +        path        => '/Tools/MyDay.html',
 +    );
 +
 +    if ( RT->Config->Get('EnableReminders') ) {
 +        $tools->child( my_reminders =>
 +            title       => loc('My Reminders'),
 +            description => loc('Easy viewing of your reminders'),
 +            path        => '/Tools/MyReminders.html',
 +        );
 +    }
 +
 +    if ( $current_user->HasRight( Right => 'ShowApprovalsTab', Object => RT->System ) ) {
 +        $tools->child( approval =>
 +            title       => loc('Approval'),
 +            description => loc('My Approvals'),
 +            path        => '/Approvals/',
 +        );
 +    }
 +
 +    if ( $current_user->HasRight( Right => 'ShowConfigTab', Object => RT->System ) )
 +    {
 +        _BuildAdminMenu( $request_path, $top, $widgets, $page, %args );
 +    }
 +
 +    my $username = '<span class="current-user">'
 +                 . $HTML::Mason::Commands::m->interp->apply_escapes($current_user->Name, 'h')
 +                 . '</span>';
 +    my $about_me = $top->child( 'preferences' =>
 +        title        => loc('Logged in as [_1]', $username),
 +        escape_title => 0,
 +        path         => '/User/Summary.html?id=' . $current_user->id,
 +        sort_order   => 99,
 +    );
 +
 +    $about_me->child( rt_name => title => loc("RT for [_1]", RT->Config->Get('rtname')), path => '/' );
 +
 +    if ( $current_user->UserObj
 +         && $current_user->HasRight( Right => 'ModifySelf', Object => RT->System )) {
 +        my $settings = $about_me->child( settings => title => loc('Settings'), path => '/Prefs/Other.html' );
 +        $settings->child( options        => title => loc('Preferences'),        path => '/Prefs/Other.html' );
 +        $settings->child( about_me       => title => loc('About me'),       path => '/Prefs/AboutMe.html' );
 +        $settings->child( search_options => title => loc('Search options'), path => '/Prefs/SearchOptions.html' );
 +        $settings->child( myrt           => title => loc('RT at a glance'), path => '/Prefs/MyRT.html' );
 +        $settings->child( dashboards_in_menu =>
 +            title => loc('Dashboards in menu'),
 +            path  => '/Prefs/DashboardsInMenu.html',
 +        );
 +        $settings->child( queue_list    => title => loc('Queue list'),   path => '/Prefs/QueueList.html' );
 +
 +        my $search_menu = $settings->child( 'saved-searches' => title => loc('Saved Searches') );
 +        my $searches = [ $HTML::Mason::Commands::m->comp( "/Search/Elements/SearchesForObject",
 +                          Object => RT::System->new( $current_user )) ];
 +        my $i = 0;
 +
 +        for my $search (@$searches) {
 +            $search_menu->child( "search-" . $i++ =>
 +                title => $search->[1],
 +                path  => "/Prefs/Search.html?"
 +                       . QueryString( name => ref( $search->[2] ) . '-' . $search->[2]->Id ),
 +            );
 +
 +        }
 +    }
 +    my $logout_url = RT->Config->Get('LogoutURL');
 +    if ( $current_user->Name
 +         && (   !RT->Config->Get('WebRemoteUserAuth')
 +              || RT->Config->Get('WebFallbackToRTLogin') )) {
 +        $about_me->child( logout => title => loc('Logout'), path => $logout_url );
 +    }
 +    if ( $request_path =~ m{^/Dashboards/(\d+)?}) {
 +        if ( my $id = ( $1 || $HTML::Mason::Commands::DECODED_ARGS->{'id'} ) ) {
 +            my $obj = RT::Dashboard->new( $current_user );
 +            $obj->LoadById($id);
 +            if ( $obj and $obj->id ) {
 +                $page->child( basics       => title => loc('Basics'),       path => "/Dashboards/Modify.html?id=" . $obj->id);
 +                $page->child( content      => title => loc('Content'),      path => "/Dashboards/Queries.html?id=" . $obj->id);
 +                $page->child( subscription => title => loc('Subscription'), path => "/Dashboards/Subscription.html?id=" . $obj->id)
 +                    if $obj->CurrentUserCanSubscribe;
 +                $page->child( show         => title => loc('Show'),         path => "/Dashboards/" . $obj->id . "/" . $obj->Name)
 +            }
 +        }
 +    }
 +
 +
 +    if ( $request_path =~ m{^/Ticket/} ) {
 +        if ( ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} || '' ) =~ /^(\d+)$/ ) {
 +            my $id  = $1;
 +            my $obj = RT::Ticket->new( $current_user );
 +            $obj->Load($id);
 +
 +            if ( $obj and $obj->id ) {
 +                my $actions = $page->child( actions => title => loc('Actions'), sort_order  => 95 );
 +
 +                my %can = %{ $obj->CurrentUser->PrincipalObj->HasRights( Object => $obj ) };
 +                $can{'_ModifyOwner'} = $obj->CurrentUserCanSetOwner();
 +                my $can = sub {
 +                    unless ($_[0] eq 'ExecuteCode') {
 +                        return $can{$_[0]} || $can{'SuperUser'};
 +                    } else {
 +                        return !RT->Config->Get('DisallowExecuteCode')
 +                            && ( $can{'ExecuteCode'} || $can{'SuperUser'} );
 +                    }
 +                };
 +
 +                $page->child( bookmark => raw_html => $HTML::Mason::Commands::m->scomp( '/Ticket/Elements/Bookmark', id => $id ), sort_order => 98 );
 +
 +                if ($can->('ModifyTicket')) {
 +                    $page->child( timer => raw_html => $HTML::Mason::Commands::m->scomp( '/Ticket/Elements/PopupTimerLink', id => $id ), sort_order => 99 );
 +                }
 +
 +                $page->child( display => title => loc('Display'), path => "/Ticket/Display.html?id=" . $id );
 +                $page->child( history => title => loc('History'), path => "/Ticket/History.html?id=" . $id );
 +
 +                # comment out until we can do it for an individual custom field
 +                #if ( $can->('ModifyTicket') || $can->('ModifyCustomField') ) {
 +                $page->child( basics => title => loc('Basics'), path => "/Ticket/Modify.html?id=" . $id );
 +
 +                #}
 +
 +                if ( $can->('ModifyTicket') || $can->('_ModifyOwner') || $can->('Watch') || $can->('WatchAsAdminCc') ) {
 +                    $page->child( people => title => loc('People'), path => "/Ticket/ModifyPeople.html?id=" . $id );
 +                }
 +
 +                if ( $can->('ModifyTicket') ) {
 +                    $page->child( dates => title => loc('Dates'), path => "/Ticket/ModifyDates.html?id=" . $id );
 +                    $page->child( links => title => loc('Links'), path => "/Ticket/ModifyLinks.html?id=" . $id );
 +                }
 +
 +                #if ( $can->('ModifyTicket') || $can->('ModifyCustomField') || $can->('_ModifyOwner') ) {
 +                $page->child( jumbo => title => loc('Jumbo'), path => "/Ticket/ModifyAll.html?id=" . $id );
 +                #}
 +
 +                if ( RT->Config->Get('EnableReminders') ) {
 +                    $page->child( reminders => title => loc('Reminders'), path => "/Ticket/Reminders.html?id=" . $id );
 +                }
 +
 +                if ( $can->('ModifyTicket') or $can->('ReplyToTicket') ) {
 +                    $actions->child( reply => title => loc('Reply'), path => "/Ticket/Update.html?Action=Respond;id=" . $id );
 +                }
 +
 +                if ( $can->('ModifyTicket') or $can->('CommentOnTicket') ) {
 +                    $actions->child( comment => title => loc('Comment'), path => "/Ticket/Update.html?Action=Comment;id=" . $id );
 +                }
 +
 +                if ( $can->('ForwardMessage') ) {
 +                    $actions->child( forward => title => loc('Forward'), path => "/Ticket/Forward.html?id=" . $id );
 +                }
 +
 +                my $hide_resolve_with_deps = RT->Config->Get('HideResolveActionsWithDependencies')
 +                    && $obj->HasUnresolvedDependencies;
 +
 +                my $current   = $obj->Status;
 +                my $lifecycle = $obj->LifecycleObj;
 +                my $i         = 1;
 +                foreach my $info ( $lifecycle->Actions($current) ) {
 +                    my $next = $info->{'to'};
 +                    next unless $lifecycle->IsTransition( $current => $next );
 +
 +                    my $check = $lifecycle->CheckRight( $current => $next );
 +                    next unless $can->($check);
 +
 +                    next if $hide_resolve_with_deps
 +                        && $lifecycle->IsInactive($next)
 +                        && !$lifecycle->IsInactive($current);
 +
 +                    my $action = $info->{'update'} || '';
 +                    my $url = '/Ticket/';
 +                    $url .= "Update.html?". QueryString(
 +                        $action
 +                            ? (Action        => $action)
 +                            : (SubmitTicket  => 1, Status => $next),
 +                        DefaultStatus => $next,
 +                        id            => $id,
 +                    );
 +                    my $key = $info->{'label'} || ucfirst($next);
 +                    $actions->child( $key => title => loc( $key ), path => $url);
 +                }
 +
 +                my ($can_take, $tmsg) = $obj->CurrentUserCanSetOwner( Type => 'Take' );
 +                my ($can_steal, $smsg) = $obj->CurrentUserCanSetOwner( Type => 'Steal' );
 +                my ($can_untake, $umsg) = $obj->CurrentUserCanSetOwner( Type => 'Untake' );
 +                if ( $can_take ){
 +                    $actions->child( take => title => loc('Take'), path => "/Ticket/Display.html?Action=Take;id=" . $id );
 +                }
 +                elsif ( $can_steal ){
 +                    $actions->child( steal => title => loc('Steal'), path => "/Ticket/Display.html?Action=Steal;id=" . $id );
 +                }
 +                elsif ( $can_untake ){
 +                    $actions->child( untake => title => loc('Untake'), path => "/Ticket/Display.html?Action=Untake;id=" . $id );
 +                }
 +
 +                # TODO needs a "Can extract article into a class applied to this queue" check
 +                $actions->child( 'extract-article' =>
 +                    title => loc('Extract Article'),
 +                    path  => "/Articles/Article/ExtractIntoClass.html?Ticket=".$obj->id,
 +                ) if $current_user->HasRight( Right => 'ShowArticlesMenu', Object => RT->System );
 +
 +                if ( defined $HTML::Mason::Commands::session{"tickets"} ) {
 +                    # we have to update session data if we get new ItemMap
 +                    my $updatesession = 1 unless ( $HTML::Mason::Commands::session{"tickets"}->{'item_map'} );
 +
 +                    my $item_map = $HTML::Mason::Commands::session{"tickets"}->ItemMap;
 +
 +                    if ($updatesession) {
 +                        $HTML::Mason::Commands::session{"tickets"}->PrepForSerialization();
 +                    }
 +
 +                    my $search = $top->child('search')->child('tickets');
 +                    # Don't display prev links if we're on the first ticket
 +                    if ( $item_map->{$id}->{prev} ) {
 +                        $search->child( first =>
 +                            title => '<< ' . loc('First'), class => "nav", path => "/Ticket/Display.html?id=" . $item_map->{first});
 +                        $search->child( prev =>
 +                            title => '< ' . loc('Prev'),   class => "nav", path => "/Ticket/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 => "/Ticket/Display.html?id=" . $item_map->{$id}->{next});
 +                        if ( $item_map->{last} ) {
 +                            $search->child( last =>
 +                                title => loc('Last') . ' >>', class => "nav", path => "/Ticket/Display.html?id=" . $item_map->{last});
 +                        }
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    # display "View ticket" link in transactions
 +    if ( $request_path =~ m{^/Transaction/Display.html} ) {
 +        if ( ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} || '' ) =~ /^(\d+)$/ ) {
 +            my $txn_id = $1;
 +            my $txn = RT::Transaction->new( $current_user );
 +            $txn->Load( $txn_id );
 +            my $object = $txn->Object;
 +            if ( $object->Id ) {
 +                my $object_type = $object->RecordType;
 +                if ( $object_type eq 'Ticket' ) {
 +                    $page->child( view_ticket => title => loc("View ticket"), path => "/Ticket/Display.html?id=" . $object->Id );
 +                }
 +            }
 +        }
 +    }
 +
 +    # Scope here so we can share in the Privileged callback
 +    my $args      = '';
 +    my $has_query = '';
 +    if (
 +        (
 +               $request_path =~ m{^/(?:Ticket|Transaction|Search)/}
 +            && $request_path !~ m{^/Search/Simple\.html}
 +        )
 +        || (   $request_path =~ m{^/Search/Simple\.html}
 +            && $HTML::Mason::Commands::DECODED_ARGS->{'q'} )
 +      )
 +    {
 +        my $class = $HTML::Mason::Commands::DECODED_ARGS->{Class}
 +            || ( $request_path =~ m{^/Transaction/} ? 'RT::Transactions' : 'RT::Tickets' );
 +
 +        my ( $search, $hash_name );
 +        if ( $class eq 'RT::Tickets' ) {
 +            $search = $top->child('search')->child('tickets');
 +            $hash_name = 'CurrentSearchHash';
 +        }
 +        else {
 +            $search = $txns_tickets;
 +            $hash_name = join '-', 'CurrentSearchHash', $class, $HTML::Mason::Commands::DECODED_ARGS->{ObjectType} || 'RT::Ticket';
 +        }
 +
 +        my $current_search = $HTML::Mason::Commands::session{$hash_name} || {};
 +        my $search_id = $HTML::Mason::Commands::DECODED_ARGS->{'SavedSearchLoad'} || $HTML::Mason::Commands::DECODED_ARGS->{'SavedSearchId'} || $current_search->{'SearchId'} || '';
 +        my $chart_id = $HTML::Mason::Commands::DECODED_ARGS->{'SavedChartSearchId'} || $current_search->{SavedChartSearchId};
 +
 +        $has_query = 1 if ( $HTML::Mason::Commands::DECODED_ARGS->{'Query'} or $current_search->{'Query'} );
 +
 +        my %query_args;
 +        my %fallback_query_args = (
 +            SavedSearchId => ( $search_id eq 'new' ) ? undef : $search_id,
 +            SavedChartSearchId => $chart_id,
 +            (
 +                map {
 +                    my $p = $_;
 +                    $p => $HTML::Mason::Commands::DECODED_ARGS->{$p} || $current_search->{$p}
 +                } qw(Query Format OrderBy Order Page Class ObjectType)
 +            ),
 +            RowsPerPage => (
 +                defined $HTML::Mason::Commands::DECODED_ARGS->{'RowsPerPage'}
 +                ? $HTML::Mason::Commands::DECODED_ARGS->{'RowsPerPage'}
 +                : $current_search->{'RowsPerPage'}
 +            ),
 +        );
 +        $fallback_query_args{Class} ||= $class;
 +        $fallback_query_args{ObjectType} ||= 'RT::Ticket';
 +
 +        if ($query_string) {
 +            $args = '?' . $query_string;
 +        }
 +        else {
 +            my %final_query_args = ();
 +            # key => callback to avoid unnecessary work
 +
 +            for my $param (keys %fallback_query_args) {
 +                $final_query_args{$param} = defined($query_args->{$param})
 +                                          ? $query_args->{$param}
 +                                          : $fallback_query_args{$param};
 +            }
 +
 +            for my $field (qw(Order OrderBy)) {
 +                if ( ref( $final_query_args{$field} ) eq 'ARRAY' ) {
 +                    $final_query_args{$field} = join( "|", @{ $final_query_args{$field} } );
 +                } elsif (not defined $final_query_args{$field}) {
 +                    delete $final_query_args{$field};
 +                }
 +                else {
 +                    $final_query_args{$field} ||= '';
 +                }
 +            }
 +
 +            $args = '?' . QueryString(%final_query_args);
 +        }
 +
 +        my $current_search_menu;
 +        if (   $class eq 'RT::Tickets' && $request_path =~ m{^/Ticket}
 +            || $class eq 'RT::Transactions' && $request_path =~ m{^/Transaction} )
 +        {
 +            $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 = $page;
 +        }
 +
 +        $current_search_menu->child( edit_search =>
 +            title => loc('Edit Search'), path => "/Search/Build.html" . ( ($has_query) ? $args : '' ) );
 +        $current_search_menu->child( advanced =>
 +            title => loc('Advanced'),    path => "/Search/Edit.html$args" );
 +        $current_search_menu->child( custom_date_ranges =>
 +            title => loc('Custom Date Ranges'), path => "/Search/CustomDateRanges.html" ) if $class eq 'RT::Tickets';
 +        if ($has_query) {
 +            $current_search_menu->child( results => title => loc('Show Results'), path => "/Search/Results.html$args" );
 +        }
 +
 +        if ( $has_query ) {
 +            if ( $class eq 'RT::Tickets' ) {
 +                $current_search_menu->child( bulk  => title => loc('Bulk Update'), path => "/Search/Bulk.html$args" );
 +                $current_search_menu->child( chart => title => loc('Chart'),       path => "/Search/Chart.html$args" );
 +            }
 +
 +            my $more = $current_search_menu->child( more => title => loc('Feeds') );
 +
 +            $more->child( spreadsheet => title => loc('Spreadsheet'), path => "/Search/Results.tsv$args" );
 +
 +            if ( $class eq 'RT::Tickets' ) {
 +                my %rss_data
 +                    = map { $_ => $query_args->{$_} || $fallback_query_args{$_} || '' } qw(Query Order OrderBy);
 +                my $RSSQueryString = "?"
 +                    . QueryString(
 +                    Query   => $rss_data{Query},
 +                    Order   => $rss_data{Order},
 +                    OrderBy => $rss_data{OrderBy}
 +                    );
 +                my $RSSPath = join '/', map $HTML::Mason::Commands::m->interp->apply_escapes( $_, 'u' ),
 +                    $current_user->UserObj->Name,
 +                    $current_user->UserObj->GenerateAuthString(
 +                    $rss_data{Query} . $rss_data{Order} . $rss_data{OrderBy} );
 +
 +                $more->child( rss => title => loc('RSS'), path => "/NoAuth/rss/$RSSPath/$RSSQueryString" );
 +                my $ical_path = join '/', map $HTML::Mason::Commands::m->interp->apply_escapes( $_, 'u' ),
 +                    $current_user->UserObj->Name,
 +                    $current_user->UserObj->GenerateAuthString( $rss_data{Query} ),
 +                    $rss_data{Query};
 +                $more->child( ical => title => loc('iCal'), path => '/NoAuth/iCal/' . $ical_path );
 +
 +                if ($request_path =~ m{^/Search/Results.html}
 +                    &&    #XXX TODO better abstraction
 +                    $current_user->HasRight( Right => 'SuperUser', Object => RT->System )
 +                   )
 +                {
 +                    my $shred_args = QueryString(
 +                        Search          => 1,
 +                        Plugin          => 'Tickets',
 +                        'Tickets:query' => $rss_data{'Query'},
 +                        'Tickets:limit' => $query_args->{'Rows'},
 +                    );
 +
 +                    $more->child(
 +                        shredder => title => loc('Shredder'),
 +                        path     => '/Admin/Tools/Shredder/?' . $shred_args
 +                    );
 +                }
 +            }
 +        }
 +    }
 +
 +    if ( $request_path =~ m{^/Article/} ) {
 +        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) {
 +            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'};
 +            $page->child( display => title => loc('Display'), path => "/Articles/Article/Display.html?id=".$id );
 +            $page->child( history => title => loc('History'), path => "/Articles/Article/History.html?id=".$id );
 +            $page->child( modify  => title => loc('Modify'),  path => "/Articles/Article/Edit.html?id=".$id );
 +        }
 +    }
 +
 +    if ( $request_path =~ m{^/Articles/} ) {
 +        $page->child( search => title => loc("Search"),       path => "/Articles/Article/Search.html" );
 +        if ( $request_path =~ m{^/Articles/Article/} and ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} || '' ) =~ /^(\d+)$/ ) {
 +            my $id  = $1;
 +            my $obj = RT::Article->new( $current_user );
 +            $obj->Load($id);
 +
 +            if ( $obj and $obj->id ) {
 +                $page->child( display => title => loc("Display"), path => "/Articles/Article/Display.html?id=" . $id );
 +                $page->child( history => title => loc('History'), path => '/Articles/Article/History.html?id=' . $id );
 +
 +                if ( $obj->CurrentUserHasRight('ModifyArticle') ) {
 +                    $page->child(modify => title => loc('Modify'), path => '/Articles/Article/Edit.html?id=' . $id );
 +                }
 +            }
 +        }
 +
 +    }
 +
 +    if ($request_path =~ m{^/Asset/} and $HTML::Mason::Commands::DECODED_ARGS->{id} and $HTML::Mason::Commands::DECODED_ARGS->{id} !~ /\D/) {
 +        _BuildAssetMenu( $request_path, $top, $widgets, $page, %args );
 +    } elsif ($request_path =~ m{^/Asset/Search/}) {
 +        my %search = map @{$_},
 +            grep defined $_->[1] && length $_->[1],
 +            map {ref $HTML::Mason::Commands::DECODED_ARGS->{$_} ? [$_, $HTML::Mason::Commands::DECODED_ARGS->{$_}[0]] : [$_, $HTML::Mason::Commands::DECODED_ARGS->{$_}] }
 +            grep /^(?:q|SearchAssets|!?(Name|Description|Catalog|Status|Role\..+|CF\..+)|Order(?:By)?|Page)$/,
 +            keys %$HTML::Mason::Commands::DECODED_ARGS;
 +        if ( $request_path =~ /Bulk/) {
 +            $page->child('search',
 +                title => loc('Show Results'),
 +                path => '/Asset/Search/?' . (keys %search ? QueryString(%search) : ''),
 +            );
 +        } else {
 +            $page->child('bulk',
 +                title => loc('Bulk Update'),
 +                path => '/Asset/Search/Bulk.html?' . (keys %search ? QueryString(%search) : ''),
 +            );
 +        }
 +        $page->child('csv',
 +            title => loc('Download Spreadsheet'),
 +            path  => '/Asset/Search/Results.tsv?' . (keys %search ? QueryString(%search) : ''),
 +        );
 +    } elsif ($request_path =~ m{^/Admin/Global/CustomFields/Catalog-Assets\.html$}) {
 +        $page->child("create", title => loc("Create New"), path => "/Admin/CustomFields/Modify.html?Create=1;LookupType=" . RT::Asset->CustomFieldLookupType);
 +    } elsif ($request_path =~ m{^/Admin/CustomFields(/|/index\.html)?$}
 +            and $HTML::Mason::Commands::DECODED_ARGS->{'Type'} and $HTML::Mason::Commands::DECODED_ARGS->{'Type'} eq RT::Asset->CustomFieldLookupType) {
 +        $page->child("create")->path( $page->child("create")->path . "&LookupType=" . RT::Asset->CustomFieldLookupType );
 +    } elsif ($request_path =~ m{^/Admin/Assets/Catalogs/}) {
 +        my $actions = $request_path =~ m{/((index|Create)\.html)?$}
 +            ? $page
 +            : $page->child("catalogs", title => loc("Catalogs"), path => "/Admin/Assets/Catalogs/");
 +
 +        $actions->child("select", title => loc("Select"), path => "/Admin/Assets/Catalogs/");
 +        $actions->child("create", title => loc("Create"), path => "/Admin/Assets/Catalogs/Create.html");
 +
 +        my $catalog = RT::Catalog->new( $current_user );
 +        $catalog->Load($HTML::Mason::Commands::DECODED_ARGS->{id}) if $HTML::Mason::Commands::DECODED_ARGS->{id};
 +
 +        if ($catalog->id and $catalog->CurrentUserCanSee) {
 +            my $query = "id=" . $catalog->id;
 +            $page->child("modify", title => loc("Basics"), path => "/Admin/Assets/Catalogs/Modify.html?$query");
 +            $page->child("people", title => loc("Roles"),  path => "/Admin/Assets/Catalogs/Roles.html?$query");
 +
 +            $page->child("cfs", title => loc("Asset Custom Fields"), path => "/Admin/Assets/Catalogs/CustomFields.html?$query");
 +
 +            $page->child("group-rights", title => loc("Group Rights"), path => "/Admin/Assets/Catalogs/GroupRights.html?$query");
 +            $page->child("user-rights",  title => loc("User Rights"),  path => "/Admin/Assets/Catalogs/UserRights.html?$query");
 +
 +            $page->child("default-values", title => loc('Default Values'), path => "/Admin/Assets/Catalogs/DefaultValues.html?$query");
 +        }
 +    }
 +
 +    if ( $request_path =~ m{^/User/(Summary|History)\.html} ) {
 +        if ($page->child('summary')) {
 +            # Already set up from having AdminUser and ShowConfigTab;
 +            # but rename "Basics" to "Edit" in this context
 +            $page->child( 'basics' )->title( loc('Edit') );
 +        } elsif ( $current_user->HasRight( Object => $RT::System, Right => 'ShowUserHistory' ) ) {
 +            $page->child( display => title => loc('Summary'), path => '/User/Summary.html?id=' . $HTML::Mason::Commands::DECODED_ARGS->{'id'} );
 +            $page->child( history => title => loc('History'), path => '/User/History.html?id=' . $HTML::Mason::Commands::DECODED_ARGS->{'id'} );
 +        }
 +    }
 +
 +    if ( $request_path =~ /^\/(?:index.html|$)/ ) {
 +        my $alt = loc('Edit');
 +        $page->child( edit => raw_html => q[<a id="page-edit" class="menu-item" href="] . RT->Config->Get('WebPath') . qq[/Prefs/MyRT.html"><span class="fas fa-cog" alt="$alt" data-toggle="tooltip" data-placement="top" data-original-title="$alt"></span></a>] );
 +    }
 +
 +    if ( $request_path =~ m{^/Admin/Tools/(Configuration|EditConfig)} ) {
 +        $page->child( display => title => loc('View'), path => "/Admin/Tools/Configuration.html" );
 +        $page->child( history => title => loc('Edit'), path => "/Admin/Tools/EditConfig.html" );
 +    }
 +
 +    # due to historical reasons of always having been in /Elements/Tabs
 +    $HTML::Mason::Commands::m->callback( CallbackName => 'Privileged', Path => $request_path, Search_Args => $args, Has_Query => $has_query, ARGSRef => \%args, CallbackPage => '/Elements/Tabs' );
 +}
 +
 +sub _BuildAssetMenu {
 +    my $request_path = shift;
 +    my $top          = shift;
 +    my $widgets      = shift;
 +    my $page         = shift;
 +
 +    my %args = ( @_ );
 +
 +    my $current_user = $HTML::Mason::Commands::session{CurrentUser};
 +
 +    my $id    = $HTML::Mason::Commands::DECODED_ARGS->{id};
 +    my $asset = RT::Asset->new( $current_user );
 +    $asset->Load($id);
 +
 +    if ($asset->id) {
 +        $page->child("display",     title => HTML::Mason::Commands::loc("Display"),        path => "/Asset/Display.html?id=$id");
 +        $page->child("history",     title => HTML::Mason::Commands::loc("History"),        path => "/Asset/History.html?id=$id");
 +        $page->child("basics",      title => HTML::Mason::Commands::loc("Basics"),         path => "/Asset/Modify.html?id=$id");
 +        $page->child("links",       title => HTML::Mason::Commands::loc("Links"),          path => "/Asset/ModifyLinks.html?id=$id");
 +        $page->child("people",      title => HTML::Mason::Commands::loc("People"),         path => "/Asset/ModifyPeople.html?id=$id");
 +        $page->child("dates",       title => HTML::Mason::Commands::loc("Dates"),          path => "/Asset/ModifyDates.html?id=$id");
 +
 +        for my $grouping (RT::CustomField->CustomGroupings($asset)) {
 +            my $cfs = $asset->CustomFields;
 +            $cfs->LimitToGrouping( $asset => $grouping );
 +            next unless $cfs->Count;
 +            $page->child(
 +                "cf-grouping-$grouping",
 +                title   => HTML::Mason::Commands::loc($grouping),
 +                path    => "/Asset/ModifyCFs.html?id=$id;Grouping=" . $HTML::Mason::Commands::m->interp->apply_escapes($grouping, 'u'),
 +            );
 +        }
 +
 +        _BuildAssetMenuActionSubmenu( $request_path, $top, $widgets, $page, %args, Asset => $asset );
 +    }
 +}
 +
 +sub _BuildAssetMenuActionSubmenu {
 +    my $request_path = shift;
 +    my $top          = shift;
 +    my $widgets      = shift;
 +    my $page         = shift;
 +
 +    my %args = (
 +        Asset => undef,
 +        @_
 +    );
 +
 +    my $asset = $args{Asset};
 +    my $id    = $asset->id;
 +
 +    my $actions = $page->child("actions", title => HTML::Mason::Commands::loc("Actions"));
 +    $actions->child("create-linked-ticket", title => HTML::Mason::Commands::loc("Create linked ticket"), path => "/Asset/CreateLinkedTicket.html?Asset=$id");
 +
 +    my $status    = $asset->Status;
 +    my $lifecycle = $asset->LifecycleObj;
 +    for my $action ( $lifecycle->Actions($status) ) {
 +        my $next = $action->{'to'};
 +        next unless $lifecycle->IsTransition( $status => $next );
 +
 +        my $check = $lifecycle->CheckRight( $status => $next );
 +        next unless $asset->CurrentUserHasRight($check);
 +
 +        my $label = $action->{'label'} || ucfirst($next);
 +        $actions->child(
 +            $label,
 +            title   => HTML::Mason::Commands::loc($label),
 +            path    => "/Asset/Modify.html?id=$id;Update=1;DisplayAfter=1;Status="
 +                        . $HTML::Mason::Commands::m->interp->apply_escapes($next, 'u'),
 +
 +            class       => "asset-lifecycle-action",
 +            attributes  => {
 +                'data-current-status'   => $status,
 +                'data-next-status'      => $next,
 +            },
 +        );
 +    }
 +}
 +
 +sub _BuildAdminMenu {
 +    my $request_path = shift;
 +    my $top          = shift;
 +    my $widgets      = shift;
 +    my $page         = shift;
 +
 +    my %args = ( @_ );
 +
 +    my $current_user = $HTML::Mason::Commands::session{CurrentUser};
 +
 +    my $admin = $top->child( admin => title => loc('Admin'), path => '/Admin/' );
 +    if ( $current_user->HasRight( Object => RT->System, Right => 'AdminUsers' ) ) {
 +        my $users = $admin->child( users =>
 +            title       => loc('Users'),
 +            description => loc('Manage users and passwords'),
 +            path        => '/Admin/Users/',
 +        );
 +        $users->child( select => title => loc('Select'), path => "/Admin/Users/" );
 +        $users->child( create => title => loc('Create'), path => "/Admin/Users/Modify.html?Create=1" );
 +    }
 +    my $groups = $admin->child( groups =>
 +        title       => loc('Groups'),
 +        description => loc('Manage groups and group membership'),
 +        path        => '/Admin/Groups/',
 +    );
 +    $groups->child( select => title => loc('Select'), path => "/Admin/Groups/" );
 +    $groups->child( create => title => loc('Create'), path => "/Admin/Groups/Modify.html?Create=1" );
 +
 +    my $queues = $admin->child( queues =>
 +        title       => loc('Queues'),
 +        description => loc('Manage queues and queue-specific properties'),
 +        path        => '/Admin/Queues/',
 +    );
 +    $queues->child( select => title => loc('Select'), path => "/Admin/Queues/" );
 +    $queues->child( create => title => loc('Create'), path => "/Admin/Queues/Modify.html?Create=1" );
 +
 +    if ( $current_user->HasRight( Object => RT->System, Right => 'AdminCustomField' ) ) {
 +        my $cfs = $admin->child( 'custom-fields' =>
 +            title       => loc('Custom Fields'),
 +            description => loc('Manage custom fields and custom field values'),
 +            path        => '/Admin/CustomFields/',
 +        );
 +        $cfs->child( select => title => loc('Select'), path => "/Admin/CustomFields/" );
 +        $cfs->child( create => title => loc('Create'), path => "/Admin/CustomFields/Modify.html?Create=1" );
 +    }
 +
 +    if ( $current_user->HasRight( Object => RT->System, Right => 'AdminCustomRoles' ) ) {
 +        my $roles = $admin->child( 'custom-roles' =>
 +            title       => loc('Custom Roles'),
 +            description => loc('Manage custom roles'),
 +            path        => '/Admin/CustomRoles/',
 +        );
 +        $roles->child( select => title => loc('Select'), path => "/Admin/CustomRoles/" );
 +        $roles->child( create => title => loc('Create'), path => "/Admin/CustomRoles/Modify.html?Create=1" );
 +    }
 +
 +    if ( $current_user->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/Create.html" );
 +    }
 +
 +    my $admin_global = $admin->child( global =>
 +        title       => loc('Global'),
 +        description => loc('Manage properties and configuration which apply to all queues'),
 +        path        => '/Admin/Global/',
 +    );
 +
 +    my $scrips = $admin_global->child( scrips =>
 +        title       => loc('Scrips'),
 +        description => loc('Modify scrips which apply to all queues'),
 +        path        => '/Admin/Global/Scrips.html',
 +    );
 +    $scrips->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" );
 +    $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Global=1" );
 +
 +    my $conditions = $admin_global->child( conditions =>
 +        title => loc('Conditions'),
 +        description => loc('Edit system conditions'),
 +        path        => '/Admin/Global/Conditions.html',
 +    );
 +    $conditions->child( select => title => loc('Select'), path => "/Admin/Global/Conditions.html" );
 +    $conditions->child( create => title => loc('Create'), path => "/Admin/Conditions/Create.html" );
 +
 +    my $actions   = $admin_global->child( actions =>
 +        title => loc('Actions'),
 +        description => loc('Edit system actions'),
 +        path        => '/Admin/Global/Actions.html',
 +    );
 +    $actions->child( select => title => loc('Select'), path => "/Admin/Global/Actions.html" );
 +    $actions->child( create => title => loc('Create'), path => "/Admin/Actions/Create.html" );
 +
 +    my $templates = $admin_global->child( templates =>
 +        title       => loc('Templates'),
 +        description => loc('Edit system templates'),
 +        path        => '/Admin/Global/Templates.html',
 +    );
 +    $templates->child( select => title => loc('Select'), path => "/Admin/Global/Templates.html" );
 +    $templates->child( create => title => loc('Create'), path => "/Admin/Global/Template.html?Create=1" );
 +
 +    my $cfadmin = $admin_global->child( 'custom-fields' =>
 +        title       => loc('Custom Fields'),
 +        description => loc('Modify global custom fields'),
 +        path        => '/Admin/Global/CustomFields/index.html',
 +    );
 +    $cfadmin->child( users =>
 +        title       => loc('Users'),
 +        description => loc('Select custom fields for all users'),
 +        path        => '/Admin/Global/CustomFields/Users.html',
 +    );
 +    $cfadmin->child( groups =>
 +        title       => loc('Groups'),
 +        description => loc('Select custom fields for all user groups'),
 +        path        => '/Admin/Global/CustomFields/Groups.html',
 +    );
 +    $cfadmin->child( queues =>
 +        title       => loc('Queues'),
 +        description => loc('Select custom fields for all queues'),
 +        path        => '/Admin/Global/CustomFields/Queues.html',
 +    );
 +    $cfadmin->child( tickets =>
 +        title       => loc('Tickets'),
 +        description => loc('Select custom fields for tickets in all queues'),
 +        path        => '/Admin/Global/CustomFields/Queue-Tickets.html',
 +    );
 +    $cfadmin->child( transactions =>
 +        title       => loc('Ticket Transactions'),
 +        description => loc('Select custom fields for transactions on tickets in all queues'),
 +        path        => '/Admin/Global/CustomFields/Queue-Transactions.html',
 +    );
 +    $cfadmin->child( 'custom-fields' =>
 +        title       => loc('Articles'),
 +        description => loc('Select Custom Fields for Articles in all Classes'),
 +        path        => '/Admin/Global/CustomFields/Class-Article.html',
 +    );
 +    $cfadmin->child( 'assets' =>
 +        title       => loc('Assets'),
 +        description => loc('Select Custom Fields for Assets in all Catalogs'),
 +        path        => '/Admin/Global/CustomFields/Catalog-Assets.html',
 +    );
 +
 +    my $article_admin = $admin->child( articles => title => loc('Articles'), path => "/Admin/Articles/index.html" );
 +    my $class_admin = $article_admin->child(classes => title => loc('Classes'), path => '/Admin/Articles/Classes/' );
 +    $class_admin->child( select =>
 +        title       => loc('Select'),
 +        description => loc('Modify and Create Classes'),
 +        path        => '/Admin/Articles/Classes/',
 +    );
 +    $class_admin->child( create =>
 +        title       => loc('Create'),
 +        description => loc('Modify and Create Custom Fields for Articles'),
 +        path        => '/Admin/Articles/Classes/Modify.html?Create=1',
 +    );
 +
 +
 +    my $cfs = $article_admin->child( 'custom-fields' =>
 +        title => loc('Custom Fields'),
 +        path  => '/Admin/CustomFields/index.html?'.$HTML::Mason::Commands::m->comp('/Elements/QueryString', Type => 'RT::Class-RT::Article'),
 +    );
 +    $cfs->child( select =>
 +        title => loc('Select'),
 +        path => '/Admin/CustomFields/index.html?'.$HTML::Mason::Commands::m->comp('/Elements/QueryString', Type => 'RT::Class-RT::Article'),
 +    );
 +    $cfs->child( create =>
 +        title => loc('Create'),
 +        path => '/Admin/CustomFields/Modify.html?'.$HTML::Mason::Commands::m->comp("/Elements/QueryString", Create=>1, LookupType=> "RT::Class-RT::Article" ),
 +    );
 +
 +    my $assets_admin = $admin->child( assets => title => loc("Assets"), path => '/Admin/Assets/' );
 +    my $catalog_admin = $assets_admin->child( catalogs =>
 +        title       => loc("Catalogs"),
 +        description => loc("Modify asset catalogs"),
 +        path        => "/Admin/Assets/Catalogs/"
 +    );
 +    $catalog_admin->child( "select", title => loc("Select"), path => $catalog_admin->path );
 +    $catalog_admin->child( "create", title => loc("Create"), path => "Create.html" );
 +
 +
 +    my $assets_cfs = $assets_admin->child( "cfs",
 +        title => loc("Custom Fields"),
 +        description => loc("Modify asset custom fields"),
 +        path => "/Admin/CustomFields/?Type=" . RT::Asset->CustomFieldLookupType
 +    );
 +    $assets_cfs->child( "select", title => loc("Select"), path => $assets_cfs->path );
 +    $assets_cfs->child( "create", title => loc("Create"), path => "/Admin/CustomFields/Modify.html?Create=1&LookupType=" . RT::Asset->CustomFieldLookupType);
 +
 +    $admin_global->child( 'group-rights' =>
 +        title       => loc('Group Rights'),
 +        description => loc('Modify global group rights'),
 +        path        => '/Admin/Global/GroupRights.html',
 +    );
 +    $admin_global->child( 'user-rights' =>
 +        title       => loc('User Rights'),
 +        description => loc('Modify global user rights'),
 +        path        => '/Admin/Global/UserRights.html',
 +    );
 +    $admin_global->child( 'my-rt' =>
 +        title       => loc('RT at a glance'),
 +        description => loc('Modify the default "RT at a glance" view'),
 +        path        => '/Admin/Global/MyRT.html',
 +    );
 +    $admin_global->child( 'dashboards-in-menu' =>
 +        title       => loc('Dashboards in menu'),
 +        description => loc('Customize dashboards in menu'),
 +        path        => '/Admin/Global/DashboardsInMenu.html',
 +    );
 +    $admin_global->child( 'topics' =>
 +        title       => loc('Topics'),
 +        description => loc('Modify global article topics'),
 +        path        => '/Admin/Global/Topics.html',
 +    );
 +
 +    my $admin_tools = $admin->child( tools =>
 +        title       => loc('Tools'),
 +        description => loc('Use other RT administrative tools'),
 +        path        => '/Admin/Tools/',
 +    );
 +    $admin_tools->child( configuration =>
 +        title       => loc('System Configuration'),
 +        description => loc('Detailed information about your RT setup'),
 +        path        => '/Admin/Tools/Configuration.html',
 +    );
 +    $admin_tools->child( theme =>
 +        title       => loc('Theme'),
 +        description => loc('Customize the look of your RT'),
 +        path        => '/Admin/Tools/Theme.html',
 +    );
 +    if (RT->Config->Get('StatementLog')
 +        && $current_user->HasRight( Right => 'SuperUser', Object => RT->System )) {
 +       $admin_tools->child( 'sql-queries' =>
 +           title       => loc('SQL Queries'),
 +           description => loc('Browse the SQL queries made in this process'),
 +           path        => '/Admin/Tools/Queries.html',
 +       );
 +    }
 +    $admin_tools->child( rights_inspector =>
 +        title => loc('Rights Inspector'),
 +        description => loc('Search your configured rights'),
 +        path  => '/Admin/Tools/RightsInspector.html',
 +    );
 +    $admin_tools->child( shredder =>
 +        title       => loc('Shredder'),
 +        description => loc('Permanently wipeout data from RT'),
 +        path        => '/Admin/Tools/Shredder',
 +    );
 +
 +    if ( $request_path =~ m{^/Admin/(Queues|Users|Groups|CustomFields|CustomRoles)} ) {
 +        my $type = $1;
 +
 +        my %labels = (
 +            Queues       => loc("Queues"),
 +            Users        => loc("Users"),
 +            Groups       => loc("Groups"),
 +            CustomFields => loc("Custom Fields"),
 +            CustomRoles  => loc("Custom Roles"),
 +        );
 +
 +        my $section;
 +        if ( $request_path =~ m|^/Admin/$type/?(?:index.html)?$|
 +             || (    $request_path =~ m|^/Admin/$type/(?:Modify.html)$|
 +                  && $HTML::Mason::Commands::DECODED_ARGS->{'Create'} )
 +           )
 +        {
 +            $section = $page;
 +
 +        } else {
 +            $section = $page->child( select => title => $labels{$type},
 +                                     path => "/Admin/$type/" );
 +        }
 +
 +        $section->child( select => title => loc('Select'), path => "/Admin/$type/" );
 +        $section->child( create => title => loc('Create'), path => "/Admin/$type/Modify.html?Create=1" );
 +    }
 +
 +    if ( $request_path =~ m{^/Admin/Queues} ) {
 +        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/
 +                ||
 +              $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} && $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} =~ /^\d+$/
 +                ) {
 +            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} || $HTML::Mason::Commands::DECODED_ARGS->{'id'};
 +            my $queue_obj = RT::Queue->new( $current_user );
 +            $queue_obj->Load($id);
 +
 +            if ( $queue_obj and $queue_obj->id ) {
 +                my $queue = $page;
 +                $queue->child( basics => title => loc('Basics'),   path => "/Admin/Queues/Modify.html?id=" . $id );
 +                $queue->child( people => title => loc('Watchers'), path => "/Admin/Queues/People.html?id=" . $id );
 +
 +                my $templates = $queue->child(templates => title => loc('Templates'), path => "/Admin/Queues/Templates.html?id=" . $id);
 +                $templates->child( select => title => loc('Select'), path => "/Admin/Queues/Templates.html?id=".$id);
 +                $templates->child( create => title => loc('Create'), path => "/Admin/Queues/Template.html?Create=1;Queue=".$id);
 +
 +                my $scrips = $queue->child( scrips => title => loc('Scrips'), path => "/Admin/Queues/Scrips.html?id=" . $id);
 +                $scrips->child( select => title => loc('Select'), path => "/Admin/Queues/Scrips.html?id=" . $id );
 +                $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Queue=" . $id);
 +
 +                my $cfs = $queue->child( 'custom-fields' => title => loc('Custom Fields') );
 +                my $ticket_cfs = $cfs->child( 'tickets' => title => loc('Tickets'),
 +                    path => '/Admin/Queues/CustomFields.html?SubType=RT::Ticket&id=' . $id );
 +
 +                my $txn_cfs = $cfs->child( 'transactions' => title => loc('Transactions'),
 +                    path => '/Admin/Queues/CustomFields.html?SubType=RT::Ticket-RT::Transaction&id='.$id );
 +
 +                $queue->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/Queues/GroupRights.html?id=".$id );
 +                $queue->child( 'user-rights' => title => loc('User Rights'), path => "/Admin/Queues/UserRights.html?id=" . $id );
 +                $queue->child( 'history' => title => loc('History'), path => "/Admin/Queues/History.html?id=" . $id );
 +                $queue->child( 'default-values' => title => loc('Default Values'), path => "/Admin/Queues/DefaultValues.html?id=" . $id );
 +
 +                # due to historical reasons of always having been in /Elements/Tabs
 +                $HTML::Mason::Commands::m->callback( CallbackName => 'PrivilegedQueue', queue_id => $id, page_menu => $queue, CallbackPage => '/Elements/Tabs' );
 +            }
 +        }
 +    }
 +    if ( $request_path =~ m{^(/Admin/Users|/User/(Summary|History)\.html)} and $admin->child("users") ) {
 +        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) {
 +            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'};
 +            my $obj = RT::User->new( $current_user );
 +            $obj->Load($id);
 +
 +            if ( $obj and $obj->id ) {
 +                $page->child( basics      => title => loc('Basics'),         path => "/Admin/Users/Modify.html?id=" . $id );
 +                $page->child( memberships => title => loc('Memberships'),    path => "/Admin/Users/Memberships.html?id=" . $id );
 +                $page->child( history     => title => loc('History'),        path => "/Admin/Users/History.html?id=" . $id );
 +                $page->child( 'my-rt'     => title => loc('RT at a glance'), path => "/Admin/Users/MyRT.html?id=" . $id );
 +                $page->child( 'dashboards-in-menu' =>
 +                    title => loc('Dashboards in menu'),
 +                    path  => '/Admin/Users/DashboardsInMenu.html?id=' . $id,
 +                );
 +                if ( RT->Config->Get('Crypt')->{'Enable'} ) {
 +                    $page->child( keys    => title => loc('Private keys'),   path => "/Admin/Users/Keys.html?id=" . $id );
 +                }
 +                $page->child( 'summary'   => title => loc('User Summary'),   path => "/User/Summary.html?id=" . $id );
 +            }
 +        }
 +
 +    }
 +
 +    if ( $request_path =~ m{^(/Admin/Groups|/Group/(Summary|History)\.html)} ) {
 +        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) {
 +            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'};
 +            my $obj = RT::Group->new( $current_user );
 +            $obj->Load($id);
 +
 +            if ( $obj and $obj->id ) {
 +                $page->child( basics         => title => loc('Basics'),       path => "/Admin/Groups/Modify.html?id=" . $obj->id );
 +                $page->child( members        => title => loc('Members'),      path => "/Admin/Groups/Members.html?id=" . $obj->id );
 +                $page->child( memberships    => title => loc('Memberships'),  path => "/Admin/Groups/Memberships.html?id=" . $obj->id );
 +                $page->child( 'links'     =>
 +                              title       => loc("Links"),
 +                              path        => "/Admin/Groups/ModifyLinks.html?id=" . $obj->id,
 +                              description => loc("Group links"),
 +                );
 +                $page->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/Groups/GroupRights.html?id=" . $obj->id );
 +                $page->child( 'user-rights'  => title => loc('User Rights'),  path => "/Admin/Groups/UserRights.html?id=" . $obj->id );
 +                $page->child( history        => title => loc('History'),      path => "/Admin/Groups/History.html?id=" . $obj->id );
 +                $page->child( 'summary'   =>
 +                              title       => loc("Group Summary"),
 +                              path        => "/Group/Summary.html?id=" . $obj->id,
 +                              description => loc("Group summary page"),
 +                );
 +            }
 +        }
 +    }
 +
 +    if ( $request_path =~ m{^/Admin/CustomFields/} ) {
 +        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) {
 +            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'};
 +            my $obj = RT::CustomField->new( $current_user );
 +            $obj->Load($id);
 +
 +            if ( $obj and $obj->id ) {
 +                $page->child( basics           => title => loc('Basics'),       path => "/Admin/CustomFields/Modify.html?id=".$id );
 +                $page->child( 'group-rights'   => title => loc('Group Rights'), path => "/Admin/CustomFields/GroupRights.html?id=" . $id );
 +                $page->child( 'user-rights'    => title => loc('User Rights'),  path => "/Admin/CustomFields/UserRights.html?id=" . $id );
 +                unless ( $obj->IsOnlyGlobal ) {
 +                    $page->child( 'applies-to' => title => loc('Applies to'),   path => "/Admin/CustomFields/Objects.html?id=" . $id );
 +                }
 +            }
 +        }
 +    }
 +
 +    if ( $request_path =~ m{^/Admin/CustomRoles} ) {
 +        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) {
 +            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'};
 +            my $obj = RT::CustomRole->new( $current_user );
 +            $obj->Load($id);
 +
 +            if ( $obj and $obj->id ) {
 +                $page->child( basics       => title => loc('Basics'),       path => "/Admin/CustomRoles/Modify.html?id=".$id );
 +                $page->child( 'applies-to' => title => loc('Applies to'),   path => "/Admin/CustomRoles/Objects.html?id=" . $id );
 +
 +                if ( $current_user->HasRight( Object => $obj, Right => 'AdminCustomRoles' ) ) {
 +                    $page->child( 'visibility' => title => loc('Visibility'), path => "/Admin/CustomRoles/Visibility.html?id=" . $id );
 +                }
 +
 +            }
 +        }
 +    }
 +
 +    if ( $request_path =~ m{^/Admin/Scrips/} ) {
 +        if ( $HTML::Mason::Commands::m->request_args->{'id'} && $HTML::Mason::Commands::m->request_args->{'id'} =~ /^\d+$/ ) {
 +            my $id = $HTML::Mason::Commands::m->request_args->{'id'};
 +            my $obj = RT::Scrip->new( $current_user );
 +            $obj->Load($id);
 +
 +            my ( $admin_cat, $create_path_arg, $from_query_param );
 +            my $from_arg = $HTML::Mason::Commands::DECODED_ARGS->{'From'} || q{};
 +            my ($from_queue) = $from_arg =~ /^(\d+)$/;
 +            if ( $from_queue ) {
 +                $admin_cat = "Queues/Scrips.html?id=$from_queue";
 +                $create_path_arg = "?Queue=$from_queue";
 +                $from_query_param = "&From=$from_queue";
 +            }
 +            elsif ( $from_arg eq 'Global' ) {
 +                $admin_cat = 'Global/Scrips.html';
 +                $create_path_arg = '?Global=1';
 +                $from_query_param = '&From=Global';
 +            }
 +            else {
 +                $admin_cat = 'Scrips';
 +                $from_query_param = $create_path_arg = q{};
 +            }
 +            my $scrips = $page->child( scrips => title => loc('Scrips'), path => "/Admin/${admin_cat}" );
 +            $scrips->child( select => title => loc('Select'), path => "/Admin/${admin_cat}" );
 +            $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html${create_path_arg}" );
 +
 +            $page->child( basics => title => loc('Basics') => path => "/Admin/Scrips/Modify.html?id=" . $id . $from_query_param );
 +            $page->child( 'applies-to' => title => loc('Applies to'), path => "/Admin/Scrips/Objects.html?id=" . $id . $from_query_param );
 +        }
 +        elsif ( $request_path =~ m{^/Admin/Scrips/(index\.html)?$} ) {
 +            HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Scrips/" );
 +            HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html" );
 +        }
 +        elsif ( $request_path =~ m{^/Admin/Scrips/Create\.html$} ) {
 +            my ($queue) = $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} && $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} =~ /^(\d+)$/;
 +            my $global_arg = $HTML::Mason::Commands::DECODED_ARGS->{'Global'};
 +            if ($queue) {
 +                HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Queues/Scrips.html?id=$queue" );
 +                HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html?Queue=$queue" );
 +            } elsif ($global_arg) {
 +                HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Global/Scrips.html" );
 +                HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html?Global=1" );
 +            } else {
 +                HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Scrips" );
 +                HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html" );
 +            }
 +        }
 +    }
 +
 +    if ( $request_path =~ m{^/Admin/Global/Scrips\.html} ) {
 +        $page->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" );
 +        $page->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Global=1" );
 +    }
 +
 +    if ( $request_path =~ m{^/Admin(?:/Global)?/Conditions} ) {
 +        $page->child( select => title => loc('Select'), path => "/Admin/Global/Conditions.html" );
 +        $page->child( create => title => loc('Create'), path => "/Admin/Conditions/Create.html" );
 +    }
 +
 +    if ( $request_path =~ m{^/Admin(?:/Global)?/Actions} ) {
 +        $page->child( select => title => loc('Select'), path => "/Admin/Global/Actions.html" );
 +        $page->child( create => title => loc('Create'), path => "/Admin/Actions/Create.html" );
 +    }
 +
 +    if ( $request_path =~ m{^/Admin/Global/Templates?\.html} ) {
 +        $page->child( select => title => loc('Select'), path => "/Admin/Global/Templates.html" );
 +        $page->child( create => title => loc('Create'), path => "/Admin/Global/Template.html?Create=1" );
 +    }
 +
 +    if ( $request_path =~ m{^/Admin/Articles/Classes/} ) {
 +        if ( my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'} ) {
 +            my $obj = RT::Class->new( $current_user );
 +            $obj->Load($id);
 +
 +            if ( $obj and $obj->id ) {
 +                my $section = $page->child( select => title => loc("Classes"), path => "/Admin/Articles/Classes/" );
 +                $section->child( select => title => loc('Select'), path => "/Admin/Articles/Classes/" );
 +                $section->child( create => title => loc('Create'), path => "/Admin/Articles/Classes/Modify.html?Create=1" );
 +
 +                $page->child( basics          => title => loc('Basics'),        path => "/Admin/Articles/Classes/Modify.html?id=".$id );
 +                $page->child( topics          => title => loc('Topics'),        path => "/Admin/Articles/Classes/Topics.html?id=".$id );
 +                $page->child( 'custom-fields' => title => loc('Custom Fields'), path => "/Admin/Articles/Classes/CustomFields.html?id=".$id );
 +                $page->child( 'group-rights'  => title => loc('Group Rights'),  path => "/Admin/Articles/Classes/GroupRights.html?id=".$id );
 +                $page->child( 'user-rights'   => title => loc('User Rights'),   path => "/Admin/Articles/Classes/UserRights.html?id=".$id );
 +                $page->child( 'applies-to'    => title => loc('Applies to'),    path => "/Admin/Articles/Classes/Objects.html?id=$id" );
 +            }
 +        } else {
 +            $page->child( select => title => loc('Select'), path => "/Admin/Articles/Classes/" );
 +            $page->child( create => title => loc('Create'), path => "/Admin/Articles/Classes/Modify.html?Create=1" );
 +        }
 +    }
 +}
 +
 +sub BuildSelfServiceNav {
 +    my $request_path = shift;
 +    my $top          = shift;
 +    my $widgets      = shift;
 +    my $page         = shift;
 +
 +    my %args = ( @_ );
 +
 +    my $current_user = $HTML::Mason::Commands::session{CurrentUser};
 +
 +    my $queues = RT::Queues->new( $current_user );
 +    $queues->UnLimit;
 +
 +    my $queue_count = 0;
 +    my $queue_id;
 +
 +    while ( my $queue = $queues->Next ) {
 +        next unless $queue->CurrentUserHasRight('CreateTicket');
 +        $queue_id = $queue->id;
 +        $queue_count++;
 +        last if ( $queue_count > 1 );
 +    }
 +
 +
 +    if ( $queue_count > 1 ) {
 +        $top->child( new => title => loc('New ticket'), path => '/SelfService/CreateTicketInQueue.html' );
 +    } elsif ( $queue_id ) {
 +        $top->child( new => title => loc('New ticket'), path => '/SelfService/Create.html?Queue=' . $queue_id );
 +    }
 +    my $tickets = $top->child( tickets => title => loc('Tickets'), path => '/SelfService/' );
 +    $tickets->child( open   => title => loc('Open tickets'),   path => '/SelfService/' );
 +    $tickets->child( closed => title => loc('Closed tickets'), path => '/SelfService/Closed.html' );
 +
 +    $top->child( "assets", title => loc("Assets"), path => "/SelfService/Asset/" )
 +        if $current_user->HasRight( Right => 'ShowAssetsMenu', Object => RT->System );
 +
 +    my $username = '<span class="current-user">'
 +                 . $HTML::Mason::Commands::m->interp->apply_escapes($current_user->Name, 'h')
 +                 . '</span>';
 +    my $about_me = $top->child( preferences =>
 +        title        => loc('Logged in as [_1]', $username),
 +        escape_title => 0,
 +        sort_order   => 99,
 +    );
 +
 +    if ( ( RT->Config->Get('SelfServiceUserPrefs') || '' ) eq 'view-info' ||
 +        $current_user->HasRight( Right => 'ModifySelf', Object => RT->System ) ) {
 +        $about_me->child( prefs => title => loc('Preferences'), path => '/SelfService/Prefs.html' );
 +    }
 +
 +    my $logout_url = RT->Config->Get('LogoutURL');
 +    if ( $current_user->Name
 +         && (   !RT->Config->Get('WebRemoteUserAuth')
 +              || RT->Config->Get('WebFallbackToRTLogin') )) {
 +        $about_me->child( logout => title => loc('Logout'), path => $logout_url );
 +    }
 +
 +    if ($current_user->HasRight( Right => 'ShowArticle', Object => RT->System )) {
 +        $widgets->child( 'goto-article' => raw_html => $HTML::Mason::Commands::m->scomp('/SelfService/Elements/SearchArticle') );
 +    }
 +
 +    $widgets->child( goto => raw_html => $HTML::Mason::Commands::m->scomp('/SelfService/Elements/GotoTicket') );
 +
 +    if ($request_path =~ m{^/SelfService/Asset/} and $HTML::Mason::Commands::DECODED_ARGS->{id}) {
 +        my $id   = $HTML::Mason::Commands::DECODED_ARGS->{id};
 +        $page->child("display",     title => loc("Display"),        path => "/SelfService/Asset/Display.html?id=$id");
 +        $page->child("history",     title => loc("History"),        path => "/SelfService/Asset/History.html?id=$id");
 +
 +        if (Menu->child("new")) {
 +            my $actions = $page->child("actions", title => loc("Actions"));
 +            $actions->child("create-linked-ticket", title => loc("Create linked ticket"), path => "/SelfService/Asset/CreateLinkedTicket.html?Asset=$id");
 +        }
 +    }
 +
 +    # due to historical reasons of always having been in /Elements/Tabs
 +    $HTML::Mason::Commands::m->callback( CallbackName => 'SelfService', Path => $request_path, ARGSRef => \%args, CallbackPage => '/Elements/Tabs' );
 +}
 +
 +1;

commit 5da73ebd505cf4d8ab54854ce0ced0c521c281c3
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Fri Feb 28 15:01:26 2020 -0500

    Use same method for getting user as group
    
    In some cases USER and LOGNAME may not be defined,
    for example in a minimal server running in a docker
    container.

diff --git a/configure.ac b/configure.ac
index 69f6d0dbc3..c686cb56f2 100755
--- a/configure.ac
+++ b/configure.ac
@@ -223,7 +223,7 @@ AC_SUBST(RTGROUP)
 
 dnl INSTALL AS ME
 my_group=$($PERL -MPOSIX=getgid -le 'print scalar getgrgid getgid')
-my_user=${USER:-$LOGNAME}
+my_user=$($PERL -MPOSIX=getuid -le 'print scalar getpwuid getuid')
 AC_ARG_WITH(my-user-group,
             AC_HELP_STRING([--with-my-user-group],
                            [set all users and groups to current user/group]),

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


More information about the rt-commit mailing list