1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
5# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC
6#                                          <sales@bestpractical.com>
7#
8# (Except where explicitly superseded by other copyright notices)
9#
10#
11# LICENSE:
12#
13# This work is made available to you under the terms of Version 2 of
14# the GNU General Public License. A copy of that license should have
15# been provided with this software, but in any event can be snarfed
16# from www.gnu.org.
17#
18# This work is distributed in the hope that it will be useful, but
19# WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21# General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this program; if not, write to the Free Software
25# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26# 02110-1301 or visit their web page on the internet at
27# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28#
29#
30# CONTRIBUTION SUBMISSION POLICY:
31#
32# (The following paragraph is not intended to limit the rights granted
33# to you to modify and distribute this software under the terms of
34# the GNU General Public License and is only of importance to you if
35# you choose to contribute your changes and enhancements to the
36# community by submitting them to Best Practical Solutions, LLC.)
37#
38# By intentionally submitting any modifications, corrections or
39# derivatives to this work, or any other work intended for use with
40# Request Tracker, to Best Practical Solutions, LLC, you confirm that
41# you are the copyright holder for those contributions and you grant
42# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43# royalty-free, perpetual, license to use, copy, create derivative
44# works based on those contributions, and sublicense and distribute
45# those contributions and any derivatives thereof.
46#
47# END BPS TAGGED BLOCK }}}
48
49=head1 NAME
50
51RT::Interface::Web::MenuBuilder
52
53=cut
54
55use strict;
56use warnings;
57
58package RT::Interface::Web::MenuBuilder;
59
60sub loc { HTML::Mason::Commands::loc( @_ ); }
61
62sub QueryString {
63    my %args = @_;
64    my $u    = URI->new();
65    $u->query_form(map { $_ => $args{$_} } sort keys %args);
66    return $u->query;
67}
68
69sub BuildMainNav {
70    my $request_path = shift;
71    my $top          = shift;
72    my $widgets      = shift;
73    my $page         = shift;
74
75    my %args = ( @_ );
76
77    my $query_string = $args{QueryString};
78    my $query_args = $args{QueryArgs};
79
80    my $current_user = $HTML::Mason::Commands::session{CurrentUser};
81
82    if ($request_path =~ m{^/Asset/}) {
83        $widgets->child( asset_search => raw_html => $HTML::Mason::Commands::m->scomp('/Asset/Elements/Search') );
84        $widgets->child( create_asset => raw_html => $HTML::Mason::Commands::m->scomp('/Asset/Elements/CreateAsset') );
85    }
86    elsif ($request_path =~ m{^/Articles/}) {
87        $widgets->child( article_search => raw_html => $HTML::Mason::Commands::m->scomp('/Articles/Elements/GotoArticle') );
88        $widgets->child( create_article => raw_html => $HTML::Mason::Commands::m->scomp('/Articles/Elements/CreateArticleButton') );
89    } else {
90        $widgets->child( simple_search => raw_html => $HTML::Mason::Commands::m->scomp('SimpleSearch', Placeholder => loc('Search Tickets')) );
91        $widgets->child( create_ticket => raw_html => $HTML::Mason::Commands::m->scomp('CreateTicket') );
92    }
93
94    my $home = $top->child( home => title => loc('Homepage'), path => '/' );
95    $home->child( create_ticket => title => loc("Create Ticket"),
96                  path => "/Ticket/Create.html" );
97
98    my $search = $top->child( search => title => loc('Search'), path => '/Search/Simple.html' );
99
100    my $tickets = $search->child( tickets => title => loc('Tickets'), path => '/Search/Build.html' );
101    $tickets->child( simple => title => loc('Simple Search'), path => "/Search/Simple.html" );
102    $tickets->child( new    => title => loc('New Search'),    path => "/Search/Build.html?NewQuery=1" );
103
104    my $recents = $tickets->child( recent => title => loc('Recently Viewed'));
105    for my $ticket ( $current_user->RecentlyViewedTickets ) {
106        my $title = $ticket->{subject} || loc( "(No subject)" );
107        if ( length $title > 50 ) {
108            $title = substr($title, 0, 47);
109            $title =~ s/\s+$//;
110            $title .= "...";
111        }
112        $title = "#$ticket->{id}: " . $title;
113        $recents->child( "$ticket->{id}" => title => $title, path => "/Ticket/Display.html?id=" . $ticket->{id} );
114    }
115
116    $search->child( articles => title => loc('Articles'),   path => "/Articles/Article/Search.html" )
117        if $current_user->HasRight( Right => 'ShowArticlesMenu', Object => RT->System );
118
119    $search->child( users => title => loc('Users'),   path => "/User/Search.html" );
120
121    $search->child( groups      =>
122                    title       => loc('Groups'),
123                    path        => "/Group/Search.html",
124                    description => 'Group search'
125    );
126
127    my $search_assets;
128    if ($HTML::Mason::Commands::session{CurrentUser}->HasRight( Right => 'ShowAssetsMenu', Object => RT->System )) {
129        $search_assets = $search->child( assets => title => loc("Assets"), path => "/Search/Build.html?Class=RT::Assets" );
130        if (!RT->Config->Get('AssetHideSimpleSearch')) {
131            $search_assets->child("asset_simple", title => loc("Simple Search"), path => "/Asset/Search/");
132        }
133        $search_assets->child("assetsql", title => loc("New Search"), path => "/Search/Build.html?Class=RT::Assets;NewQuery=1");
134    }
135
136
137    my $txns = $search->child( transactions => title => loc('Transactions'), path => '/Search/Build.html?Class=RT::Transactions;ObjectType=RT::Ticket' );
138    my $txns_tickets = $txns->child( tickets => title => loc('Tickets'), path => "/Search/Build.html?Class=RT::Transactions;ObjectType=RT::Ticket" );
139    $txns_tickets->child( new => title => loc('New Search'), path => "/Search/Build.html?Class=RT::Transactions;ObjectType=RT::Ticket;NewQuery=1" );
140
141    my $reports = $top->child( reports =>
142        title       => loc('Reports'),
143        description => loc('Reports and Dashboards'),
144        path        => loc('/Reports'),
145    );
146
147    unless ($HTML::Mason::Commands::session{'dashboards_in_menu'}) {
148        my $dashboards_in_menu = $current_user->UserObj->Preferences(
149            'DashboardsInMenu',
150            {},
151        );
152
153        unless ($dashboards_in_menu->{dashboards}) {
154            my ($default_dashboards) =
155                RT::System->new( $current_user )
156                    ->Attributes
157                    ->Named('DashboardsInMenu');
158            if ($default_dashboards) {
159                $dashboards_in_menu = $default_dashboards->Content;
160            }
161        }
162
163        $HTML::Mason::Commands::session{'dashboards_in_menu'} = $dashboards_in_menu->{dashboards} || [];
164    }
165
166    my @dashboards;
167    for my $id ( @{$HTML::Mason::Commands::session{'dashboards_in_menu'}} ) {
168        my $dash = RT::Dashboard->new( $current_user );
169        my ( $status, $msg ) = $dash->LoadById($id);
170        if ( $status ) {
171            push @dashboards, $dash;
172        } else {
173            $RT::Logger->debug( "Failed to load dashboard $id: $msg, removing from menu" );
174            $home->RemoveDashboardMenuItem(
175                DashboardId => $id,
176                CurrentUser => $HTML::Mason::Commands::session{CurrentUser}->UserObj,
177            );
178            @{ $HTML::Mason::Commands::session{'dashboards_in_menu'} } =
179              grep { $_ != $id } @{ $HTML::Mason::Commands::session{'dashboards_in_menu'} };
180        }
181    }
182
183    if (@dashboards) {
184        for my $dash (@dashboards) {
185            $reports->child( 'dashboard-' . $dash->id,
186                title => $dash->Name,
187                path  => '/Dashboards/' . $dash->id . '/' . $dash->Name
188            );
189        }
190    }
191
192    # Get the list of reports in the Reports menu
193    unless (   $HTML::Mason::Commands::session{'reports_in_menu'}
194            && ref( $HTML::Mason::Commands::session{'reports_in_menu'}) eq 'ARRAY'
195            && @{$HTML::Mason::Commands::session{'reports_in_menu'}}
196           ) {
197        my $reports_in_menu = $current_user->UserObj->Preferences(
198            'ReportsInMenu',
199            {},
200        );
201        unless ( $reports_in_menu && ref $reports_in_menu eq 'ARRAY' ) {
202            my ($default_reports) =
203                RT::System->new( RT->SystemUser )
204                    ->Attributes
205                    ->Named('ReportsInMenu');
206            if ($default_reports) {
207                $reports_in_menu = $default_reports->Content;
208            }
209            else {
210                $reports_in_menu = [];
211            }
212        }
213
214        $HTML::Mason::Commands::session{'reports_in_menu'} = $reports_in_menu || [];
215    }
216
217    for my $report ( @{$HTML::Mason::Commands::session{'reports_in_menu'}} ) {
218        $reports->child(  $report->{id} =>
219            title       => $report->{title},
220            path        => $report->{path},
221        );
222    }
223
224    $reports->child( edit => title => loc('Update This Menu'), path => '/Prefs/DashboardsInMenu.html' );
225    $reports->child( more => title => loc('All Dashboards'),   path => '/Dashboards/index.html' );
226    my $dashboard = RT::Dashboard->new( $current_user );
227    if ( $dashboard->CurrentUserCanCreateAny ) {
228        $reports->child('dashboard_create' => title => loc('New Dashboard'), path => "/Dashboards/Modify.html?Create=1" );
229    }
230
231
232    if ($current_user->HasRight( Right => 'ShowArticlesMenu', Object => RT->System )) {
233        my $articles = $top->child( articles => title => loc('Articles'), path => "/Articles/index.html");
234        $articles->child( articles => title => loc('Overview'), path => "/Articles/index.html" );
235        $articles->child( topics   => title => loc('Topics'),   path => "/Articles/Topics.html" );
236        $articles->child( create   => title => loc('Create'),   path => "/Articles/Article/PreCreate.html" );
237        $articles->child( search   => title => loc('Search'),   path => "/Articles/Article/Search.html" );
238    }
239
240    if ($current_user->HasRight( Right => 'ShowAssetsMenu', Object => RT->System )) {
241        my $assets = $top->child(
242            "assets",
243            title => loc("Assets"),
244            path  => RT->Config->Get('AssetHideSimpleSearch') ? "/Search/Build.html?Class=RT::Assets;NewQuery=1" : "/Asset/Search/",
245        );
246        $assets->child( "create", title => loc("Create"), path => "/Asset/Create.html" );
247        if (!RT->Config->Get('AssetHideSimpleSearch')) {
248            $assets->child( "simple_search", title => loc("Simple Search"), path => "/Asset/Search/" );
249        }
250        $assets->child( "search", title => loc("New Search"), path => "/Search/Build.html?Class=RT::Assets;NewQuery=1" );
251    }
252
253    my $tools = $top->child( tools => title => loc('Tools'), path => '/Tools/index.html' );
254
255    $tools->child( my_day =>
256        title       => loc('My Day'),
257        description => loc('Easy updating of your open tickets'),
258        path        => '/Tools/MyDay.html',
259    );
260
261    if ( RT->Config->Get('EnableReminders') ) {
262        $tools->child( my_reminders =>
263            title       => loc('My Reminders'),
264            description => loc('Easy viewing of your reminders'),
265            path        => '/Tools/MyReminders.html',
266        );
267    }
268
269    if ( $current_user->HasRight( Right => 'ShowApprovalsTab', Object => RT->System ) ) {
270        $tools->child( approval =>
271            title       => loc('Approval'),
272            description => loc('My Approvals'),
273            path        => '/Approvals/',
274        );
275    }
276
277    if ( $current_user->HasRight( Right => 'ShowConfigTab', Object => RT->System ) )
278    {
279        _BuildAdminMenu( $request_path, $top, $widgets, $page, %args );
280    }
281
282    my $username = '<span class="current-user">'
283                 . $HTML::Mason::Commands::m->interp->apply_escapes($current_user->Name, 'h')
284                 . '</span>';
285    my $about_me = $top->child( 'preferences' =>
286        title        => loc('Logged in as [_1]', $username),
287        escape_title => 0,
288        path         => '/User/Summary.html?id=' . $current_user->id,
289        sort_order   => 99,
290    );
291
292    $about_me->child( rt_name => title => loc("RT for [_1]", RT->Config->Get('rtname')), path => '/' );
293
294    if ( $current_user->UserObj
295         && $current_user->HasRight( Right => 'ModifySelf', Object => RT->System )) {
296        my $settings = $about_me->child( settings => title => loc('Settings'), path => '/Prefs/Other.html' );
297        $settings->child( options        => title => loc('Preferences'),        path => '/Prefs/Other.html' );
298        $settings->child( about_me       => title => loc('About me'),       path => '/Prefs/AboutMe.html' );
299        if ( $current_user->HasRight( Right => 'ManageAuthTokens', Object => RT->System ) ) {
300            $settings->child( auth_tokens => title => loc('Auth Tokens'), path => '/Prefs/AuthTokens.html' );
301        }
302        $settings->child( search_options => title => loc('Search options'), path => '/Prefs/SearchOptions.html' );
303        $settings->child( myrt           => title => loc('RT at a glance'), path => '/Prefs/MyRT.html' );
304        $settings->child( dashboards_in_menu =>
305            title => loc('Modify Reports menu'),
306            path  => '/Prefs/DashboardsInMenu.html',
307        );
308        $settings->child( queue_list    => title => loc('Queue list'),   path => '/Prefs/QueueList.html' );
309
310        my $search_menu = $settings->child( 'saved-searches' => title => loc('Saved Searches') );
311        my $searches = [ $HTML::Mason::Commands::m->comp( "/Search/Elements/SearchesForObject",
312                          Object => RT::System->new( $current_user )) ];
313        my $i = 0;
314
315        for my $search (@$searches) {
316            $search_menu->child( "search-" . $i++ =>
317                title => $search->[1],
318                path  => "/Prefs/Search.html?"
319                       . QueryString( name => ref( $search->[2] ) . '-' . $search->[2]->Id ),
320            );
321
322        }
323
324        if ( $request_path =~ qr{/Prefs/(?:SearchOptions|CustomDateRanges)\.html} ) {
325            $page->child(
326                search_options => title => loc('Search Preferences'),
327                path               => "/Prefs/SearchOptions.html"
328            );
329            $page->child(
330                custom_date_ranges => title => loc('Custom Date Ranges'),
331                path               => "/Prefs/CustomDateRanges.html"
332            )
333        }
334
335        if ( $request_path =~ m{^/Prefs/AuthTokens\.html} ) {
336            $page->child( create_auth_token => title => loc('Create'),
337                raw_html => q[<a class="btn menu-item" href="#create-auth-token" data-toggle="modal" rel="modal:open">].loc("Create")."</a>"
338            );
339        }
340    }
341    my $logout_url = RT->Config->Get('LogoutURL');
342    if ( $current_user->Name
343         && (   !RT->Config->Get('WebRemoteUserAuth')
344              || RT->Config->Get('WebFallbackToRTLogin') )) {
345        $about_me->child( logout => title => loc('Logout'), path => $logout_url );
346    }
347    if ( $request_path =~ m{^/Dashboards/(\d+)?}) {
348        if ( my $id = ( $1 || $HTML::Mason::Commands::DECODED_ARGS->{'id'} ) ) {
349            my $obj = RT::Dashboard->new( $current_user );
350            $obj->LoadById($id);
351            if ( $obj and $obj->id ) {
352                $page->child( basics       => title => loc('Basics'),       path => "/Dashboards/Modify.html?id=" . $obj->id);
353                $page->child( content      => title => loc('Content'),      path => "/Dashboards/Queries.html?id=" . $obj->id);
354                $page->child( subscription => title => loc('Subscription'), path => "/Dashboards/Subscription.html?id=" . $obj->id)
355                    if $obj->CurrentUserCanSubscribe;
356                $page->child( show         => title => loc('Show'),         path => "/Dashboards/" . $obj->id . "/" . $obj->Name)
357            }
358        }
359    }
360
361
362    my $search_results_page_menu;
363    if ( $request_path =~ m{^/Ticket/} ) {
364        if ( ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} || '' ) =~ /^(\d+)$/ ) {
365            my $id  = $1;
366            my $obj = RT::Ticket->new( $current_user );
367            $obj->Load($id);
368
369            if ( $obj and $obj->id ) {
370                my $actions = $page->child( actions => title => loc('Actions'), sort_order  => 95 );
371
372                my %can = %{ $obj->CurrentUser->PrincipalObj->HasRights( Object => $obj ) };
373                # since CurrentUserCanSetOwner returns ($ok, $msg), the parens ensure that $can{} gets $ok
374                ( $can{'_ModifyOwner'} ) = $obj->CurrentUserCanSetOwner();
375                my $can = sub {
376                    unless ($_[0] eq 'ExecuteCode') {
377                        return $can{$_[0]} || $can{'SuperUser'};
378                    } else {
379                        return !RT->Config->Get('DisallowExecuteCode')
380                            && ( $can{'ExecuteCode'} || $can{'SuperUser'} );
381                    }
382                };
383
384                $page->child( bookmark => raw_html => $HTML::Mason::Commands::m->scomp( '/Ticket/Elements/Bookmark', id => $id ), sort_order => 98 );
385
386                if ($can->('ModifyTicket')) {
387                    $page->child( timer => raw_html => $HTML::Mason::Commands::m->scomp( '/Ticket/Elements/PopupTimerLink', id => $id ), sort_order => 99 );
388                }
389
390                $page->child( display => title => loc('Display'), path => "/Ticket/Display.html?id=" . $id );
391                $page->child( history => title => loc('History'), path => "/Ticket/History.html?id=" . $id );
392
393                # comment out until we can do it for an individual custom field
394                #if ( $can->('ModifyTicket') || $can->('ModifyCustomField') ) {
395                $page->child( basics => title => loc('Basics'), path => "/Ticket/Modify.html?id=" . $id );
396
397                #}
398
399                if ( $can->('ModifyTicket') || $can->('_ModifyOwner') || $can->('Watch') || $can->('WatchAsAdminCc') ) {
400                    $page->child( people => title => loc('People'), path => "/Ticket/ModifyPeople.html?id=" . $id );
401                }
402
403                if ( $can->('ModifyTicket') ) {
404                    $page->child( dates => title => loc('Dates'), path => "/Ticket/ModifyDates.html?id=" . $id );
405                    $page->child( links => title => loc('Links'), path => "/Ticket/ModifyLinks.html?id=" . $id );
406                }
407
408                #if ( $can->('ModifyTicket') || $can->('ModifyCustomField') || $can->('_ModifyOwner') ) {
409                $page->child( jumbo => title => loc('Jumbo'), path => "/Ticket/ModifyAll.html?id=" . $id );
410                #}
411
412                if ( RT->Config->Get('EnableReminders') ) {
413                    $page->child( reminders => title => loc('Reminders'), path => "/Ticket/Reminders.html?id=" . $id );
414                }
415
416                if ( $can->('ModifyTicket') or $can->('ReplyToTicket') ) {
417                    $actions->child( reply => title => loc('Reply'), path => "/Ticket/Update.html?Action=Respond;id=" . $id );
418                }
419
420                if ( $can->('ModifyTicket') or $can->('CommentOnTicket') ) {
421                    $actions->child( comment => title => loc('Comment'), path => "/Ticket/Update.html?Action=Comment;id=" . $id );
422                }
423
424                if ( $can->('ForwardMessage') ) {
425                    $actions->child( forward => title => loc('Forward'), path => "/Ticket/Forward.html?id=" . $id );
426                }
427
428                my $hide_resolve_with_deps = RT->Config->Get('HideResolveActionsWithDependencies')
429                    && $obj->HasUnresolvedDependencies;
430
431                my $current   = $obj->Status;
432                my $lifecycle = $obj->LifecycleObj;
433                my $i         = 1;
434                foreach my $info ( $lifecycle->Actions($current) ) {
435                    my $next = $info->{'to'};
436                    next unless $lifecycle->IsTransition( $current => $next );
437
438                    my $check = $lifecycle->CheckRight( $current => $next );
439                    next unless $can->($check);
440
441                    next if $hide_resolve_with_deps
442                        && $lifecycle->IsInactive($next)
443                        && !$lifecycle->IsInactive($current);
444
445                    my $action = $info->{'update'} || '';
446                    my $url = '/Ticket/';
447                    $url .= "Update.html?". QueryString(
448                        $action
449                            ? (Action        => $action)
450                            : (SubmitTicket  => 1, Status => $next),
451                        DefaultStatus => $next,
452                        id            => $id,
453                    );
454                    my $key = $info->{'label'} || ucfirst($next);
455                    $actions->child( $key => title => loc( $key ), path => $url);
456                }
457
458                my ($can_take, $tmsg) = $obj->CurrentUserCanSetOwner( Type => 'Take' );
459                my ($can_steal, $smsg) = $obj->CurrentUserCanSetOwner( Type => 'Steal' );
460                my ($can_untake, $umsg) = $obj->CurrentUserCanSetOwner( Type => 'Untake' );
461                if ( $can_take ){
462                    $actions->child( take => title => loc('Take'), path => "/Ticket/Display.html?Action=Take;id=" . $id );
463                }
464                elsif ( $can_steal ){
465                    $actions->child( steal => title => loc('Steal'), path => "/Ticket/Display.html?Action=Steal;id=" . $id );
466                }
467                elsif ( $can_untake ){
468                    $actions->child( untake => title => loc('Untake'), path => "/Ticket/Display.html?Action=Untake;id=" . $id );
469                }
470
471                # TODO needs a "Can extract article into a class applied to this queue" check
472                $actions->child( 'extract-article' =>
473                    title => loc('Extract Article'),
474                    path  => "/Articles/Article/ExtractIntoClass.html?Ticket=".$obj->id,
475                ) if $current_user->HasRight( Right => 'ShowArticlesMenu', Object => RT->System );
476
477                $actions->child( 'edit_assets' =>
478                    title => loc('Edit Assets'),
479                     path => "/Asset/Search/Bulk.html?Query=Linked=" . $obj->id,
480                ) if $can->('ModifyTicket')
481                  && $HTML::Mason::Commands::session{CurrentUser}->HasRight( Right => 'ShowAssetsMenu', Object => RT->System );
482
483                if ( RT::Config->Get( 'ShowSearchNavigation', $current_user )
484                    && defined $HTML::Mason::Commands::session{"collection-RT::Tickets"} )
485                {
486                    # we have to update session data if we get new ItemMap
487                    my $updatesession;
488                    $updatesession = 1 unless ( $HTML::Mason::Commands::session{"collection-RT::Tickets"}->{'item_map'} );
489
490                    my $item_map = $HTML::Mason::Commands::session{"collection-RT::Tickets"}->ItemMap;
491
492                    if ($updatesession) {
493                        $HTML::Mason::Commands::session{"collection-RT::Tickets"}->PrepForSerialization();
494                    }
495
496                    my $search = $search_results_page_menu = $HTML::Mason::Commands::m->notes('search-results-page-menu', RT::Interface::Web::Menu->new());
497
498                    # Don't display prev links if we're on the first ticket
499                    if ( $item_map->{$id}->{prev} ) {
500                        $search->child(
501                            first        => title => q{<span class="fas fa-angle-double-left"></span>},
502                            escape_title => 0,
503                            class        => "nav",
504                            path         => "/Ticket/Display.html?id=" . $item_map->{first},
505                            attributes   => {
506                                'data-toggle'         => 'tooltip',
507                                'data-original-title' => loc('First'),
508                                alt                   => loc('First'),
509                            },
510                        );
511                        $search->child(
512                            prev         => title => q{<span class="fas fa-angle-left"></span>},
513                            escape_title => 0,
514                            class        => "nav",
515                            path         => "/Ticket/Display.html?id=" . $item_map->{$id}->{prev},
516                            attributes   => {
517                                'data-toggle'         => 'tooltip',
518                                'data-original-title' => loc('Prev'),
519                                alt                   => loc('Prev'),
520                            },
521                        );
522                    }
523                    # Don't display next links if we're on the last ticket
524                    if ( $item_map->{$id}->{next} ) {
525                        $search->child(
526                            next         => title => q{<span class="fas fa-angle-right"></span>},
527                            escape_title => 0,
528                            class        => "nav",
529                            path         => "/Ticket/Display.html?id=" . $item_map->{$id}->{next},
530                            attributes   => {
531                                'data-toggle'         => 'tooltip',
532                                'data-original-title' => loc('Next'),
533                                alt                   => loc('Next'),
534                            },
535                        );
536                        if ( $item_map->{last} ) {
537                            $search->child(
538                                last         => title => q{<span class="fas fa-angle-double-right"></span>},
539                                escape_title => 0,
540                                class        => "nav",
541                                path         => "/Ticket/Display.html?id=" . $item_map->{last},
542                                attributes   => {
543                                    'data-toggle'         => 'tooltip',
544                                    'data-original-title' => loc('Last'),
545                                    alt                   => loc('Last'),
546                                },
547                            );
548                        }
549                    }
550                }
551            }
552        }
553    }
554
555    # display "View ticket" link in transactions
556    if ( $request_path =~ m{^/Transaction/Display.html} ) {
557        if ( ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} || '' ) =~ /^(\d+)$/ ) {
558            my $txn_id = $1;
559            my $txn = RT::Transaction->new( $current_user );
560            $txn->Load( $txn_id );
561            my $object = $txn->Object;
562            if ( $object->Id ) {
563                my $object_type = $object->RecordType;
564                if ( $object_type eq 'Ticket' ) {
565                    $page->child( view_ticket => title => loc("View ticket"), path => "/Ticket/Display.html?id=" . $object->Id );
566                }
567            }
568        }
569    }
570
571    # Scope here so we can share in the Privileged callback
572    my $args      = '';
573    my $has_query = '';
574    if (
575        (
576               $request_path =~ m{^/(?:Ticket|Transaction|Search)/}
577            && $request_path !~ m{^/Search/Simple\.html}
578        )
579        || (   $request_path =~ m{^/Search/Simple\.html}
580            && $HTML::Mason::Commands::DECODED_ARGS->{'q'} )
581
582        # TODO: Asset simple search and SQL search don't share query, we
583        # can't simply link to SQL search page on asset pages without
584        # identifying if it's from simple search or SQL search. For now,
585        # show "Current Search" only if asset simple search is disabled.
586
587        || ( $search_assets && $request_path =~ m{^/Asset/(?!Search/)} && RT->Config->Get('AssetHideSimpleSearch') )
588      )
589    {
590        my $class = $HTML::Mason::Commands::DECODED_ARGS->{Class}
591            || ( $request_path =~ m{^/(Transaction|Ticket|Asset)/} ? "RT::$1s" : 'RT::Tickets' );
592
593        my $search;
594        if ( $class eq 'RT::Tickets' ) {
595            $search = $top->child('search')->child('tickets');
596        }
597        elsif ( $class eq 'RT::Assets' ) {
598            $search = $search_assets;
599        }
600        else {
601            $search = $txns_tickets;
602        }
603
604        my $hash_name = join '-', 'CurrentSearchHash', $class,
605            $HTML::Mason::Commands::DECODED_ARGS->{ObjectType} || ( $class eq 'RT::Transactions' ? 'RT::Ticket' : () );
606        my $current_search = $HTML::Mason::Commands::session{$hash_name} || {};
607        my $search_id = $HTML::Mason::Commands::DECODED_ARGS->{'SavedSearchLoad'} || $HTML::Mason::Commands::DECODED_ARGS->{'SavedSearchId'} || $current_search->{'SearchId'} || '';
608        my $chart_id = $HTML::Mason::Commands::DECODED_ARGS->{'SavedChartSearchId'} || $current_search->{SavedChartSearchId};
609
610        $has_query = 1 if ( $HTML::Mason::Commands::DECODED_ARGS->{'Query'} or $current_search->{'Query'} );
611
612        my %query_args;
613        my %fallback_query_args = (
614            SavedSearchId => ( $search_id eq 'new' ) ? undef : $search_id,
615            SavedChartSearchId => $chart_id,
616            (
617                map {
618                    my $p = $_;
619                    $p => $HTML::Mason::Commands::DECODED_ARGS->{$p} || $current_search->{$p}
620                } qw(Query Format OrderBy Order Page Class ObjectType ResultPage ExtraQueryParams),
621            ),
622        );
623
624        if ( defined ( my $rows = $HTML::Mason::Commands::DECODED_ARGS->{'RowsPerPage'} // $current_search->{'RowsPerPage'} ) ) {
625            $fallback_query_args{RowsPerPage} = $rows;
626        }
627
628        if ( my $extra_params = $fallback_query_args{ExtraQueryParams} ) {
629            for my $param ( ref $extra_params eq 'ARRAY' ? @$extra_params : $extra_params ) {
630                $fallback_query_args{$param}
631                    = $HTML::Mason::Commands::DECODED_ARGS->{$param} || $current_search->{$param};
632            }
633        }
634
635        $fallback_query_args{Class} ||= $class;
636        $fallback_query_args{ObjectType} ||= 'RT::Ticket' if $class eq 'RT::Transactions';
637
638        if ($query_string) {
639            $args = '?' . $query_string;
640        }
641        else {
642            my %final_query_args = ();
643            # key => callback to avoid unnecessary work
644
645            if ( my $extra_params = $query_args->{ExtraQueryParams} ) {
646                $final_query_args{ExtraQueryParams} = $extra_params;
647                for my $param ( ref $extra_params eq 'ARRAY' ? @$extra_params : $extra_params ) {
648                    $final_query_args{$param} = $query_args->{$param};
649                }
650            }
651
652            for my $param (keys %fallback_query_args) {
653                $final_query_args{$param} = defined($query_args->{$param})
654                                          ? $query_args->{$param}
655                                          : $fallback_query_args{$param};
656            }
657
658            for my $field (qw(Order OrderBy)) {
659                if ( ref( $final_query_args{$field} ) eq 'ARRAY' ) {
660                    $final_query_args{$field} = join( "|", @{ $final_query_args{$field} } );
661                } elsif (not defined $final_query_args{$field}) {
662                    delete $final_query_args{$field};
663                }
664                else {
665                    $final_query_args{$field} ||= '';
666                }
667            }
668
669            $args = '?' . QueryString(%final_query_args);
670        }
671
672        my $current_search_menu;
673        if (   $class eq 'RT::Tickets' && $request_path =~ m{^/Ticket}
674            || $class eq 'RT::Transactions' && $request_path =~ m{^/Transaction}
675            || $class eq 'RT::Assets' && $request_path =~ m{^/Asset/(?!Search/)} )
676        {
677            $current_search_menu = $search->child( current_search => title => loc('Current Search') );
678            $current_search_menu->path("/Search/Results.html$args") if $has_query;
679
680            if ( $search_results_page_menu && $has_query ) {
681                $search_results_page_menu->child(
682                    current_search => title => q{<span class="fas fa-list"></span>},
683                    escape_title   => 0,
684                    sort_order     => -1,
685                    path           => "/Search/Results.html$args",
686                    attributes     => {
687                        'data-toggle'         => 'tooltip',
688                        'data-original-title' => loc('Return to Search Results'),
689                        alt                   => loc('Return to Search Results'),
690                    },
691                );
692            }
693        }
694        else {
695            $current_search_menu = $page;
696        }
697
698        $current_search_menu->child( edit_search =>
699            title => loc('Edit Search'), path => "/Search/Build.html" . ( ($has_query) ? $args : '' ) );
700        if ( $current_user->HasRight( Right => 'ShowSearchAdvanced', Object => RT->System ) ) {
701            $current_search_menu->child( advanced => title => loc('Advanced'), path => "/Search/Edit.html$args" );
702        }
703        if ($has_query) {
704            my $result_page = $HTML::Mason::Commands::DECODED_ARGS->{ResultPage};
705            if ( $result_page ) {
706                if ( my $web_path = RT->Config->Get('WebPath') ) {
707                    $result_page =~ s!^$web_path!!;
708                }
709            }
710            else {
711                $result_page = '/Search/Results.html';
712            }
713
714            $current_search_menu->child( results => title => loc('Show Results'), path => "$result_page$args" );
715        }
716
717        if ( $has_query ) {
718            if ( $class eq 'RT::Tickets' ) {
719                if ( $current_user->HasRight( Right => 'ShowSearchBulkUpdate', Object => RT->System ) ) {
720                    $current_search_menu->child( bulk  => title => loc('Bulk Update'), path => "/Search/Bulk.html$args" );
721                }
722                $current_search_menu->child( chart => title => loc('Chart'),       path => "/Search/Chart.html$args" );
723            }
724            elsif ( $class eq 'RT::Assets' ) {
725                $current_search_menu->child( bulk  => title => loc('Bulk Update'), path => "/Asset/Search/Bulk.html$args" );
726            }
727
728            my $more = $current_search_menu->child( more => title => loc('Feeds') );
729
730            $more->child( spreadsheet => title => loc('Spreadsheet'), path => "/Search/Results.tsv$args" );
731
732            if ( $class eq 'RT::Tickets' ) {
733                my %rss_data
734                    = map { $_ => $query_args->{$_} || $fallback_query_args{$_} || '' } qw(Query Order OrderBy);
735                my $RSSQueryString = "?"
736                    . QueryString(
737                    Query   => $rss_data{Query},
738                    Order   => $rss_data{Order},
739                    OrderBy => $rss_data{OrderBy}
740                    );
741                my $RSSPath = join '/', map $HTML::Mason::Commands::m->interp->apply_escapes( $_, 'u' ),
742                    $current_user->UserObj->Name,
743                    $current_user->UserObj->GenerateAuthString(
744                    $rss_data{Query} . $rss_data{Order} . $rss_data{OrderBy} );
745
746                $more->child( rss => title => loc('RSS'), path => "/NoAuth/rss/$RSSPath/$RSSQueryString" );
747                my $ical_path = join '/', map $HTML::Mason::Commands::m->interp->apply_escapes( $_, 'u' ),
748                    $current_user->UserObj->Name,
749                    $current_user->UserObj->GenerateAuthString( $rss_data{Query} ),
750                    $rss_data{Query};
751                $more->child( ical => title => loc('iCal'), path => '/NoAuth/iCal/' . $ical_path );
752
753                #XXX TODO better abstraction of SuperUser right check
754                if ( $current_user->HasRight( Right => 'SuperUser', Object => RT->System ) ) {
755                    my $shred_args = QueryString(
756                        Search          => 1,
757                        Plugin          => 'Tickets',
758                        'Tickets:query' => $rss_data{'Query'},
759                        'Tickets:limit' => $query_args->{'Rows'},
760                    );
761
762                    $more->child(
763                        shredder => title => loc('Shredder'),
764                        path     => '/Admin/Tools/Shredder/?' . $shred_args
765                    );
766                }
767            }
768        }
769    }
770
771    if ( $request_path =~ m{^/Article/} ) {
772        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) {
773            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'};
774            $page->child( display => title => loc('Display'), path => "/Articles/Article/Display.html?id=".$id );
775            $page->child( history => title => loc('History'), path => "/Articles/Article/History.html?id=".$id );
776            $page->child( modify  => title => loc('Modify'),  path => "/Articles/Article/Edit.html?id=".$id );
777        }
778    }
779
780    if ( $request_path =~ m{^/Articles/} ) {
781        $page->child( search => title => loc("Search"),       path => "/Articles/Article/Search.html" );
782        if ( $request_path =~ m{^/Articles/Article/} and ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} || '' ) =~ /^(\d+)$/ ) {
783            my $id  = $1;
784            my $obj = RT::Article->new( $current_user );
785            $obj->Load($id);
786
787            if ( $obj and $obj->id ) {
788                $page->child( display => title => loc("Display"), path => "/Articles/Article/Display.html?id=" . $id );
789                $page->child( history => title => loc('History'), path => '/Articles/Article/History.html?id=' . $id );
790
791                if ( $obj->CurrentUserHasRight('ModifyArticle') ) {
792                    $page->child(modify => title => loc('Modify'), path => '/Articles/Article/Edit.html?id=' . $id );
793                }
794            }
795        }
796
797    }
798
799    if ($request_path =~ m{^/Asset/} and $HTML::Mason::Commands::DECODED_ARGS->{id} and $HTML::Mason::Commands::DECODED_ARGS->{id} !~ /\D/) {
800        _BuildAssetMenu( $request_path, $top, $widgets, $page, %args );
801    } elsif ( $request_path =~ m{^/Asset/Search/(?:index\.html)?$}
802        || ( $request_path =~ m{^/Asset/Search/Bulk\.html$} && $HTML::Mason::Commands::DECODED_ARGS->{Catalog} ) ) {
803        my %search = map @{$_},
804            grep defined $_->[1] && length $_->[1],
805            map {ref $HTML::Mason::Commands::DECODED_ARGS->{$_} ? [$_, $HTML::Mason::Commands::DECODED_ARGS->{$_}[0]] : [$_, $HTML::Mason::Commands::DECODED_ARGS->{$_}] }
806            grep /^(?:q|SearchAssets|!?(Name|Description|Catalog|Status|Role\..+|CF\..+)|Order(?:By)?|Page)$/,
807            keys %$HTML::Mason::Commands::DECODED_ARGS;
808
809        if ( $request_path =~ /Bulk/) {
810            $page->child('search',
811                title => loc('Show Results'),
812                path => '/Asset/Search/?' . (keys %search ? QueryString(%search) : ''),
813            );
814        } else {
815            $page->child('bulk',
816                title => loc('Bulk Update'),
817                path => '/Asset/Search/Bulk.html?' . (keys %search ? QueryString(%search) : ''),
818            );
819        }
820
821        $page->child('csv',
822            title => loc('Download Spreadsheet'),
823            path  => '/Search/Results.tsv?' . QueryString(%search, Class => 'RT::Assets'),
824        );
825    } elsif ($request_path =~ m{^/Asset/Search/}) {
826        my %search = map @{$_},
827            grep defined $_->[1] && length $_->[1],
828            map {ref $HTML::Mason::Commands::DECODED_ARGS->{$_} ? [$_, $HTML::Mason::Commands::DECODED_ARGS->{$_}[0]] : [$_, $HTML::Mason::Commands::DECODED_ARGS->{$_}] }
829            grep /^(?:q|SearchAssets|!?(Name|Description|Catalog|Status|Role\..+|CF\..+)|Order(?:By)?|Page)$/,
830            keys %$HTML::Mason::Commands::DECODED_ARGS;
831
832        my $current_search = $HTML::Mason::Commands::session{"CurrentSearchHash-RT::Assets"} || {};
833        my $search_id = $HTML::Mason::Commands::DECODED_ARGS->{'SavedSearchLoad'} || $HTML::Mason::Commands::DECODED_ARGS->{'SavedSearchId'} || $current_search->{'SearchId'} || '';
834        my $args      = '';
835        my $has_query;
836        $has_query = 1 if ( $HTML::Mason::Commands::DECODED_ARGS->{'Query'} or $current_search->{'Query'} );
837
838        my %query_args;
839        my %fallback_query_args = (
840            Class => 'RT::Assets',
841            SavedSearchId => ( $search_id eq 'new' ) ? undef : $search_id,
842            (
843                map {
844                    my $p = $_;
845                    $p => $HTML::Mason::Commands::DECODED_ARGS->{$p} || $current_search->{$p}
846                } qw(Query Format OrderBy Order Page)
847            ),
848        );
849
850        if ( defined ( my $rows = $HTML::Mason::Commands::DECODED_ARGS->{'RowsPerPage'} // $current_search->{'RowsPerPage'} ) ) {
851            $fallback_query_args{RowsPerPage} = $rows;
852        }
853
854        if ($query_string) {
855            $args = '?' . $query_string;
856        }
857        else {
858            my %final_query_args = ();
859            # key => callback to avoid unnecessary work
860
861            for my $param (keys %fallback_query_args) {
862                $final_query_args{$param} = defined($query_args->{$param})
863                                          ? $query_args->{$param}
864                                          : $fallback_query_args{$param};
865            }
866
867            for my $field (qw(Order OrderBy)) {
868                if ( ref( $final_query_args{$field} ) eq 'ARRAY' ) {
869                    $final_query_args{$field} = join( "|", @{ $final_query_args{$field} } );
870                } elsif (not defined $final_query_args{$field}) {
871                    delete $final_query_args{$field};
872                }
873                else {
874                    $final_query_args{$field} ||= '';
875                }
876            }
877
878            $args = '?' . QueryString(%final_query_args);
879        }
880
881        $page->child('edit_search',
882            title      => loc('Edit Search'),
883            path       => '/Search/Build.html' . $args,
884        );
885        $page->child( advanced => title => loc('Advanced'), path => '/Search/Edit.html' . $args );
886        if ($has_query) {
887            $page->child( results => title => loc('Show Results'), path => '/Search/Results.html' . $args );
888            $page->child('bulk',
889                title => loc('Bulk Update'),
890                path => '/Asset/Search/Bulk.html' . $args,
891            );
892            my $more = $page->child( more => title => loc('Feeds') );
893            $more->child( spreadsheet => title => loc('Spreadsheet'), path => "/Search/Results.tsv$args" );
894        }
895    } elsif ($request_path =~ m{^/Admin/Global/CustomFields/Catalog-Assets\.html$}) {
896        $page->child("create", title => loc("Create New"), path => "/Admin/CustomFields/Modify.html?Create=1;LookupType=" . RT::Asset->CustomFieldLookupType);
897    } elsif ($request_path =~ m{^/Admin/CustomFields(/|/index\.html)?$}
898            and $HTML::Mason::Commands::DECODED_ARGS->{'Type'} and $HTML::Mason::Commands::DECODED_ARGS->{'Type'} eq RT::Asset->CustomFieldLookupType) {
899        $page->child("create")->path( $page->child("create")->path . ";LookupType=" . RT::Asset->CustomFieldLookupType );
900    } elsif ($request_path =~ m{^/Admin/Assets/Catalogs/}) {
901        my $actions = $request_path =~ m{/((index|Create)\.html)?$}
902            ? $page
903            : $page->child("catalogs", title => loc("Catalogs"), path => "/Admin/Assets/Catalogs/");
904
905        $actions->child("select", title => loc("Select"), path => "/Admin/Assets/Catalogs/");
906        $actions->child("create", title => loc("Create"), path => "/Admin/Assets/Catalogs/Create.html");
907
908        my $catalog = RT::Catalog->new( $current_user );
909        $catalog->Load($HTML::Mason::Commands::DECODED_ARGS->{id}) if $HTML::Mason::Commands::DECODED_ARGS->{id};
910
911        if ($catalog->id and $catalog->CurrentUserCanSee) {
912            my $query = "id=" . $catalog->id;
913            $page->child("modify", title => loc("Basics"), path => "/Admin/Assets/Catalogs/Modify.html?$query");
914            $page->child("people", title => loc("Roles"),  path => "/Admin/Assets/Catalogs/Roles.html?$query");
915
916            $page->child("cfs", title => loc("Asset Custom Fields"), path => "/Admin/Assets/Catalogs/CustomFields.html?$query");
917
918            $page->child("group-rights", title => loc("Group Rights"), path => "/Admin/Assets/Catalogs/GroupRights.html?$query");
919            $page->child("user-rights",  title => loc("User Rights"),  path => "/Admin/Assets/Catalogs/UserRights.html?$query");
920
921            $page->child("default-values", title => loc('Default Values'), path => "/Admin/Assets/Catalogs/DefaultValues.html?$query");
922        }
923    }
924
925    if ( $request_path =~ m{^/User/(Summary|History)\.html} ) {
926        if ($page->child('summary')) {
927            # Already set up from having AdminUser and ShowConfigTab;
928            # but rename "Basics" to "Edit" in this context
929            $page->child( 'basics' )->title( loc('Edit') );
930        } elsif ( $current_user->HasRight( Object => $RT::System, Right => 'ShowUserHistory' ) ) {
931            $page->child( display => title => loc('Summary'), path => '/User/Summary.html?id=' . $HTML::Mason::Commands::DECODED_ARGS->{'id'} );
932            $page->child( history => title => loc('History'), path => '/User/History.html?id=' . $HTML::Mason::Commands::DECODED_ARGS->{'id'} );
933        }
934    }
935
936    # Top menu already has the create link, adding it to page menu is just
937    # for convenience. As user admin page already has quite a few items in
938    # page menu and it's unlikely that admins want to create new dashboard
939    # when editing a user's preference, here we don't touch admin user page.
940    if ( $request_path =~ m{^/(Prefs|Admin/Global)/MyRT\.html} ) {
941        if ( RT::Dashboard->new($current_user)->CurrentUserCanCreateAny ) {
942            $page->child(
943                'dashboard_create' => title => loc('New Dashboard'),
944                path               => "/Dashboards/Modify.html?Create=1"
945            );
946        }
947    }
948
949
950    if ( $request_path =~ /^\/(?:index.html|$)/ ) {
951        my $alt = loc('Edit');
952        $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>] );
953    }
954
955    if ( $request_path =~ m{^/Admin/Tools/(Configuration|EditConfig|ConfigHistory)} ) {
956        $page->child( display => title => loc('View'), path => "/Admin/Tools/Configuration.html" );
957        $page->child( modify => title => loc('Edit'), path => "/Admin/Tools/EditConfig.html" ) if RT->Config->Get('ShowEditSystemConfig');
958        $page->child( history => title => loc('History'), path => "/Admin/Tools/ConfigHistory.html" );
959    }
960
961    # due to historical reasons of always having been in /Elements/Tabs
962    $HTML::Mason::Commands::m->callback( CallbackName => 'Privileged', Path => $request_path, Search_Args => $args, Has_Query => $has_query, ARGSRef => \%args, CallbackPage => '/Elements/Tabs' );
963}
964
965sub _BuildAssetMenu {
966    my $request_path = shift;
967    my $top          = shift;
968    my $widgets      = shift;
969    my $page         = shift;
970
971    my %args = ( @_ );
972
973    my $current_user = $HTML::Mason::Commands::session{CurrentUser};
974
975    my $id    = $HTML::Mason::Commands::DECODED_ARGS->{id};
976    my $asset = RT::Asset->new( $current_user );
977    $asset->Load($id);
978
979    if ($asset->id) {
980        $page->child("display",     title => HTML::Mason::Commands::loc("Display"),        path => "/Asset/Display.html?id=$id");
981        $page->child("history",     title => HTML::Mason::Commands::loc("History"),        path => "/Asset/History.html?id=$id");
982        $page->child("basics",      title => HTML::Mason::Commands::loc("Basics"),         path => "/Asset/Modify.html?id=$id");
983        $page->child("links",       title => HTML::Mason::Commands::loc("Links"),          path => "/Asset/ModifyLinks.html?id=$id");
984        $page->child("people",      title => HTML::Mason::Commands::loc("People"),         path => "/Asset/ModifyPeople.html?id=$id");
985        $page->child("dates",       title => HTML::Mason::Commands::loc("Dates"),          path => "/Asset/ModifyDates.html?id=$id");
986
987        for my $grouping (RT::CustomField->CustomGroupings($asset)) {
988            my $cfs = $asset->CustomFields;
989            $cfs->LimitToGrouping( $asset => $grouping );
990            next unless $cfs->Count;
991            $page->child(
992                "cf-grouping-$grouping",
993                title   => HTML::Mason::Commands::loc($grouping),
994                path    => "/Asset/ModifyCFs.html?id=$id;Grouping=" . $HTML::Mason::Commands::m->interp->apply_escapes($grouping, 'u'),
995            );
996        }
997
998        _BuildAssetMenuActionSubmenu( $request_path, $top, $widgets, $page, %args, Asset => $asset );
999    }
1000}
1001
1002sub _BuildAssetMenuActionSubmenu {
1003    my $request_path = shift;
1004    my $top          = shift;
1005    my $widgets      = shift;
1006    my $page         = shift;
1007
1008    my %args = (
1009        Asset => undef,
1010        @_
1011    );
1012
1013    my $asset = $args{Asset};
1014    my $id    = $asset->id;
1015
1016    my $actions = $page->child("actions", title => HTML::Mason::Commands::loc("Actions"));
1017    $actions->child("create-linked-ticket", title => HTML::Mason::Commands::loc("Create linked ticket"), path => "/Asset/CreateLinkedTicket.html?Asset=$id");
1018
1019    my $status    = $asset->Status;
1020    my $lifecycle = $asset->LifecycleObj;
1021    for my $action ( $lifecycle->Actions($status) ) {
1022        my $next = $action->{'to'};
1023        next unless $lifecycle->IsTransition( $status => $next );
1024
1025        my $check = $lifecycle->CheckRight( $status => $next );
1026        next unless $asset->CurrentUserHasRight($check);
1027
1028        my $label = $action->{'label'} || ucfirst($next);
1029        $actions->child(
1030            $label,
1031            title   => HTML::Mason::Commands::loc($label),
1032            path    => "/Asset/Modify.html?id=$id;Update=1;DisplayAfter=1;Status="
1033                        . $HTML::Mason::Commands::m->interp->apply_escapes($next, 'u'),
1034
1035            class       => "asset-lifecycle-action",
1036            attributes  => {
1037                'data-current-status'   => $status,
1038                'data-next-status'      => $next,
1039            },
1040        );
1041    }
1042}
1043
1044sub _BuildAdminMenu {
1045    my $request_path = shift;
1046    my $top          = shift;
1047    my $widgets      = shift;
1048    my $page         = shift;
1049
1050    my %args = ( @_ );
1051
1052    my $current_user = $HTML::Mason::Commands::session{CurrentUser};
1053
1054    my $admin = $top->child( admin => title => loc('Admin'), path => '/Admin/' );
1055    if ( $current_user->HasRight( Object => RT->System, Right => 'AdminUsers' ) ) {
1056        my $users = $admin->child( users =>
1057            title       => loc('Users'),
1058            description => loc('Manage users and passwords'),
1059            path        => '/Admin/Users/',
1060        );
1061        $users->child( select => title => loc('Select'), path => "/Admin/Users/" );
1062        $users->child( create => title => loc('Create'), path => "/Admin/Users/Modify.html?Create=1" );
1063    }
1064    my $groups = $admin->child( groups =>
1065        title       => loc('Groups'),
1066        description => loc('Manage groups and group membership'),
1067        path        => '/Admin/Groups/',
1068    );
1069    $groups->child( select => title => loc('Select'), path => "/Admin/Groups/" );
1070    $groups->child( create => title => loc('Create'), path => "/Admin/Groups/Modify.html?Create=1" );
1071
1072    my $queues = $admin->child( queues =>
1073        title       => loc('Queues'),
1074        description => loc('Manage queues and queue-specific properties'),
1075        path        => '/Admin/Queues/',
1076    );
1077    $queues->child( select => title => loc('Select'), path => "/Admin/Queues/" );
1078    $queues->child( create => title => loc('Create'), path => "/Admin/Queues/Modify.html?Create=1" );
1079
1080    if ( $current_user->HasRight( Object => RT->System, Right => 'AdminCustomField' ) ) {
1081        my $cfs = $admin->child( 'custom-fields' =>
1082            title       => loc('Custom Fields'),
1083            description => loc('Manage custom fields and custom field values'),
1084            path        => '/Admin/CustomFields/',
1085        );
1086        $cfs->child( select => title => loc('Select'), path => "/Admin/CustomFields/" );
1087        $cfs->child( create => title => loc('Create'), path => "/Admin/CustomFields/Modify.html?Create=1" );
1088    }
1089
1090    if ( $current_user->HasRight( Object => RT->System, Right => 'AdminCustomRoles' ) ) {
1091        my $roles = $admin->child( 'custom-roles' =>
1092            title       => loc('Custom Roles'),
1093            description => loc('Manage custom roles'),
1094            path        => '/Admin/CustomRoles/',
1095        );
1096        $roles->child( select => title => loc('Select'), path => "/Admin/CustomRoles/" );
1097        $roles->child( create => title => loc('Create'), path => "/Admin/CustomRoles/Modify.html?Create=1" );
1098    }
1099
1100    if ( $current_user->HasRight( Object => RT->System, Right => 'ModifyScrips' ) ) {
1101        my $scrips = $admin->child( 'scrips' =>
1102            title       => loc('Scrips'),
1103            description => loc('Manage scrips'),
1104            path        => '/Admin/Scrips/',
1105        );
1106        $scrips->child( select => title => loc('Select'), path => "/Admin/Scrips/" );
1107        $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html" );
1108    }
1109
1110    if ( RT->Config->Get('ShowEditLifecycleConfig')
1111        && $current_user->HasRight( Object => RT->System, Right => 'SuperUser' ) )
1112    {
1113        my $lifecycles = $admin->child(
1114            lifecycles => title => loc('Lifecycles'),
1115            path       => '/Admin/Lifecycles/',
1116        );
1117
1118        $lifecycles->child( select => title => loc('Select'), path => '/Admin/Lifecycles/' );
1119        $lifecycles->child( create => title => loc('Create'), path => '/Admin/Lifecycles/Create.html' );
1120    }
1121
1122    my $admin_global = $admin->child( global =>
1123        title       => loc('Global'),
1124        description => loc('Manage properties and configuration which apply to all queues'),
1125        path        => '/Admin/Global/',
1126    );
1127
1128    my $scrips = $admin_global->child( scrips =>
1129        title       => loc('Scrips'),
1130        description => loc('Modify scrips which apply to all queues'),
1131        path        => '/Admin/Global/Scrips.html',
1132    );
1133    $scrips->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" );
1134    $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Global=1" );
1135
1136    my $conditions = $admin_global->child( conditions =>
1137        title => loc('Conditions'),
1138        description => loc('Edit system conditions'),
1139        path        => '/Admin/Global/Conditions.html',
1140    );
1141    $conditions->child( select => title => loc('Select'), path => "/Admin/Global/Conditions.html" );
1142    $conditions->child( create => title => loc('Create'), path => "/Admin/Conditions/Create.html" );
1143
1144    my $actions   = $admin_global->child( actions =>
1145        title => loc('Actions'),
1146        description => loc('Edit system actions'),
1147        path        => '/Admin/Global/Actions.html',
1148    );
1149    $actions->child( select => title => loc('Select'), path => "/Admin/Global/Actions.html" );
1150    $actions->child( create => title => loc('Create'), path => "/Admin/Actions/Create.html" );
1151
1152    my $templates = $admin_global->child( templates =>
1153        title       => loc('Templates'),
1154        description => loc('Edit system templates'),
1155        path        => '/Admin/Global/Templates.html',
1156    );
1157    $templates->child( select => title => loc('Select'), path => "/Admin/Global/Templates.html" );
1158    $templates->child( create => title => loc('Create'), path => "/Admin/Global/Template.html?Create=1" );
1159
1160    my $cfadmin = $admin_global->child( 'custom-fields' =>
1161        title       => loc('Custom Fields'),
1162        description => loc('Modify global custom fields'),
1163        path        => '/Admin/Global/CustomFields/index.html',
1164    );
1165    $cfadmin->child( users =>
1166        title       => loc('Users'),
1167        description => loc('Select custom fields for all users'),
1168        path        => '/Admin/Global/CustomFields/Users.html',
1169    );
1170    $cfadmin->child( groups =>
1171        title       => loc('Groups'),
1172        description => loc('Select custom fields for all user groups'),
1173        path        => '/Admin/Global/CustomFields/Groups.html',
1174    );
1175    $cfadmin->child( queues =>
1176        title       => loc('Queues'),
1177        description => loc('Select custom fields for all queues'),
1178        path        => '/Admin/Global/CustomFields/Queues.html',
1179    );
1180    $cfadmin->child( tickets =>
1181        title       => loc('Tickets'),
1182        description => loc('Select custom fields for tickets in all queues'),
1183        path        => '/Admin/Global/CustomFields/Queue-Tickets.html',
1184    );
1185    $cfadmin->child( transactions =>
1186        title       => loc('Ticket Transactions'),
1187        description => loc('Select custom fields for transactions on tickets in all queues'),
1188        path        => '/Admin/Global/CustomFields/Queue-Transactions.html',
1189    );
1190    $cfadmin->child( 'custom-fields' =>
1191        title       => loc('Articles'),
1192        description => loc('Select Custom Fields for Articles in all Classes'),
1193        path        => '/Admin/Global/CustomFields/Class-Article.html',
1194    );
1195    $cfadmin->child( 'assets' =>
1196        title       => loc('Assets'),
1197        description => loc('Select Custom Fields for Assets in all Catalogs'),
1198        path        => '/Admin/Global/CustomFields/Catalog-Assets.html',
1199    );
1200
1201    my $article_admin = $admin->child( articles => title => loc('Articles'), path => "/Admin/Articles/index.html" );
1202    my $class_admin = $article_admin->child(classes => title => loc('Classes'), path => '/Admin/Articles/Classes/' );
1203    $class_admin->child( select =>
1204        title       => loc('Select'),
1205        description => loc('Modify and Create Classes'),
1206        path        => '/Admin/Articles/Classes/',
1207    );
1208    $class_admin->child( create =>
1209        title       => loc('Create'),
1210        description => loc('Modify and Create Custom Fields for Articles'),
1211        path        => '/Admin/Articles/Classes/Modify.html?Create=1',
1212    );
1213
1214
1215    my $cfs = $article_admin->child( 'custom-fields' =>
1216        title => loc('Custom Fields'),
1217        path  => '/Admin/CustomFields/index.html?'.$HTML::Mason::Commands::m->comp('/Elements/QueryString', Type => 'RT::Class-RT::Article'),
1218    );
1219    $cfs->child( select =>
1220        title => loc('Select'),
1221        path => '/Admin/CustomFields/index.html?'.$HTML::Mason::Commands::m->comp('/Elements/QueryString', Type => 'RT::Class-RT::Article'),
1222    );
1223    $cfs->child( create =>
1224        title => loc('Create'),
1225        path => '/Admin/CustomFields/Modify.html?'.$HTML::Mason::Commands::m->comp("/Elements/QueryString", Create=>1, LookupType=> "RT::Class-RT::Article" ),
1226    );
1227
1228    my $assets_admin = $admin->child( assets => title => loc("Assets"), path => '/Admin/Assets/' );
1229    my $catalog_admin = $assets_admin->child( catalogs =>
1230        title       => loc("Catalogs"),
1231        description => loc("Modify asset catalogs"),
1232        path        => "/Admin/Assets/Catalogs/"
1233    );
1234    $catalog_admin->child( "select", title => loc("Select"), path => $catalog_admin->path );
1235    $catalog_admin->child( "create", title => loc("Create"), path => "Create.html" );
1236
1237
1238    my $assets_cfs = $assets_admin->child( "cfs",
1239        title => loc("Custom Fields"),
1240        description => loc("Modify asset custom fields"),
1241        path => "/Admin/CustomFields/?Type=" . RT::Asset->CustomFieldLookupType
1242    );
1243    $assets_cfs->child( "select", title => loc("Select"), path => $assets_cfs->path );
1244    $assets_cfs->child( "create", title => loc("Create"), path => "/Admin/CustomFields/Modify.html?Create=1;LookupType=" . RT::Asset->CustomFieldLookupType);
1245
1246    $admin_global->child( 'group-rights' =>
1247        title       => loc('Group Rights'),
1248        description => loc('Modify global group rights'),
1249        path        => '/Admin/Global/GroupRights.html',
1250    );
1251    $admin_global->child( 'user-rights' =>
1252        title       => loc('User Rights'),
1253        description => loc('Modify global user rights'),
1254        path        => '/Admin/Global/UserRights.html',
1255    );
1256    $admin_global->child( 'my-rt' =>
1257        title       => loc('RT at a glance'),
1258        description => loc('Modify the default "RT at a glance" view'),
1259        path        => '/Admin/Global/MyRT.html',
1260    );
1261
1262    if (RT->Config->Get('SelfServiceUseDashboard')) {
1263        if ($current_user->HasRight( Right => 'ModifyDashboard', Object => RT->System ) ) {
1264            my $self_service = $admin_global->child( selfservice_home =>
1265                                                     title       => loc('Self Service Home Page'),
1266                                                     description => loc('Edit self service home page dashboard'),
1267                                                     path        => '/Admin/Global/SelfServiceHomePage.html');
1268            if ( $request_path =~ m{^/Admin/Global/SelfServiceHomePage} ) {
1269                $page->child(content => title => loc('Content'), path => '/Admin/Global/SelfServiceHomePage.html');
1270                $page->child(show    => title => loc('Show'), path => '/SelfService');
1271            }
1272        }
1273    }
1274    $admin_global->child( 'dashboards-in-menu' =>
1275        title       => loc('Modify Reports menu'),
1276        description => loc('Customize dashboards in menu'),
1277        path        => '/Admin/Global/DashboardsInMenu.html',
1278    );
1279    $admin_global->child( 'topics' =>
1280        title       => loc('Topics'),
1281        description => loc('Modify global article topics'),
1282        path        => '/Admin/Global/Topics.html',
1283    );
1284
1285    my $admin_tools = $admin->child( tools =>
1286        title       => loc('Tools'),
1287        description => loc('Use other RT administrative tools'),
1288        path        => '/Admin/Tools/',
1289    );
1290    $admin_tools->child( configuration =>
1291        title       => loc('System Configuration'),
1292        description => loc('Detailed information about your RT setup'),
1293        path        => '/Admin/Tools/Configuration.html',
1294    );
1295    $admin_tools->child( theme =>
1296        title       => loc('Theme'),
1297        description => loc('Customize the look of your RT'),
1298        path        => '/Admin/Tools/Theme.html',
1299    );
1300    if (RT->Config->Get('StatementLog')
1301        && $current_user->HasRight( Right => 'SuperUser', Object => RT->System )) {
1302       $admin_tools->child( 'sql-queries' =>
1303           title       => loc('SQL Queries'),
1304           description => loc('Browse the SQL queries made in this process'),
1305           path        => '/Admin/Tools/Queries.html',
1306       );
1307    }
1308    $admin_tools->child( rights_inspector =>
1309        title => loc('Rights Inspector'),
1310        description => loc('Search your configured rights'),
1311        path  => '/Admin/Tools/RightsInspector.html',
1312    );
1313    $admin_tools->child( shredder =>
1314        title       => loc('Shredder'),
1315        description => loc('Permanently wipeout data from RT'),
1316        path        => '/Admin/Tools/Shredder',
1317    );
1318
1319    if ( RT->Config->Get('GnuPG')->{'Enable'}
1320        && $current_user->HasRight( Right => 'SuperUser', Object => RT->System ) )
1321    {
1322        $admin_tools->child(
1323            'gnupg'     => title => loc('Manage GnuPG Keys'),
1324            description => loc('Manage GnuPG keys'),
1325            path        => '/Admin/Tools/GnuPG.html',
1326        );
1327    }
1328
1329    if ( $request_path =~ m{^/Admin/(Queues|Users|Groups|CustomFields|CustomRoles)} ) {
1330        my $type = $1;
1331
1332        my %labels = (
1333            Queues       => loc("Queues"),
1334            Users        => loc("Users"),
1335            Groups       => loc("Groups"),
1336            CustomFields => loc("Custom Fields"),
1337            CustomRoles  => loc("Custom Roles"),
1338        );
1339
1340        my $section;
1341        if ( $request_path =~ m|^/Admin/$type/?(?:index.html)?$|
1342             || (    $request_path =~ m|^/Admin/$type/(?:Modify.html)$|
1343                  && $HTML::Mason::Commands::DECODED_ARGS->{'Create'} )
1344           )
1345        {
1346            $section = $page;
1347
1348        } else {
1349            $section = $page->child( select => title => $labels{$type},
1350                                     path => "/Admin/$type/" );
1351        }
1352
1353        $section->child( select => title => loc('Select'), path => "/Admin/$type/" );
1354        $section->child( create => title => loc('Create'), path => "/Admin/$type/Modify.html?Create=1" );
1355    }
1356
1357    if ( $request_path =~ m{^/Admin/Queues} ) {
1358        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/
1359                ||
1360              $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} && $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} =~ /^\d+$/
1361                ) {
1362            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} || $HTML::Mason::Commands::DECODED_ARGS->{'id'};
1363            my $queue_obj = RT::Queue->new( $current_user );
1364            $queue_obj->Load($id);
1365
1366            if ( $queue_obj and $queue_obj->id ) {
1367                my $queue = $page;
1368                $queue->child( basics => title => loc('Basics'),   path => "/Admin/Queues/Modify.html?id=" . $id );
1369                $queue->child( people => title => loc('Watchers'), path => "/Admin/Queues/People.html?id=" . $id );
1370
1371                my $templates = $queue->child(templates => title => loc('Templates'), path => "/Admin/Queues/Templates.html?id=" . $id);
1372                $templates->child( select => title => loc('Select'), path => "/Admin/Queues/Templates.html?id=".$id);
1373                $templates->child( create => title => loc('Create'), path => "/Admin/Queues/Template.html?Create=1;Queue=".$id);
1374
1375                my $scrips = $queue->child( scrips => title => loc('Scrips'), path => "/Admin/Queues/Scrips.html?id=" . $id);
1376                $scrips->child( select => title => loc('Select'), path => "/Admin/Queues/Scrips.html?id=" . $id );
1377                $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Queue=" . $id);
1378
1379                my $cfs = $queue->child( 'custom-fields' => title => loc('Custom Fields') );
1380                my $ticket_cfs = $cfs->child( 'tickets' => title => loc('Tickets'),
1381                    path => '/Admin/Queues/CustomFields.html?SubType=RT::Ticket;id=' . $id );
1382
1383                my $txn_cfs = $cfs->child( 'transactions' => title => loc('Transactions'),
1384                    path => '/Admin/Queues/CustomFields.html?SubType=RT::Ticket-RT::Transaction;id='.$id );
1385
1386                $queue->child( 'custom-roles' => title => loc('Custom Roles'), path => "/Admin/Queues/CustomRoles.html?id=".$id );
1387                $queue->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/Queues/GroupRights.html?id=".$id );
1388                $queue->child( 'user-rights' => title => loc('User Rights'), path => "/Admin/Queues/UserRights.html?id=" . $id );
1389                $queue->child( 'history' => title => loc('History'), path => "/Admin/Queues/History.html?id=" . $id );
1390                $queue->child( 'default-values' => title => loc('Default Values'), path => "/Admin/Queues/DefaultValues.html?id=" . $id );
1391
1392                # due to historical reasons of always having been in /Elements/Tabs
1393                $HTML::Mason::Commands::m->callback( CallbackName => 'PrivilegedQueue', queue_id => $id, page_menu => $queue, CallbackPage => '/Elements/Tabs' );
1394            }
1395        }
1396    }
1397    if ( $request_path =~ m{^(/Admin/Users|/User/(Summary|History)\.html)} and $admin->child("users") ) {
1398        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) {
1399            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'};
1400            my $obj = RT::User->new( $current_user );
1401            $obj->Load($id);
1402
1403            if ( $obj and $obj->id ) {
1404                $page->child( basics      => title => loc('Basics'),         path => "/Admin/Users/Modify.html?id=" . $id );
1405                $page->child( memberships => title => loc('Memberships'),    path => "/Admin/Users/Memberships.html?id=" . $id );
1406                $page->child( history     => title => loc('History'),        path => "/Admin/Users/History.html?id=" . $id );
1407                $page->child( 'my-rt'     => title => loc('RT at a glance'), path => "/Admin/Users/MyRT.html?id=" . $id );
1408                $page->child( 'dashboards-in-menu' =>
1409                    title => loc('Modify Reports menu'),
1410                    path  => '/Admin/Users/DashboardsInMenu.html?id=' . $id,
1411                );
1412                if ( RT->Config->Get('Crypt')->{'Enable'} ) {
1413                    $page->child( keys    => title => loc('Private keys'),   path => "/Admin/Users/Keys.html?id=" . $id );
1414                }
1415                $page->child( 'summary'   => title => loc('User Summary'),   path => "/User/Summary.html?id=" . $id );
1416
1417                if ( $current_user->HasRight( Right => 'ManageAuthTokens', Object => RT->System ) ) {
1418                    my $auth_tokens = $page->child(
1419                        auth_tokens => title => loc('Auth Tokens'),
1420                        path        => '/Admin/Users/AuthTokens.html?id=' . $id
1421                    );
1422
1423                    if ( $request_path =~ m{^/Admin/Users/AuthTokens\.html} ) {
1424                        $auth_tokens->child(
1425                            select_auth_token => title => loc('Select'),
1426                            path              => '/Admin/Users/AuthTokens.html?id=' . $id,
1427                        );
1428                        $auth_tokens->child(
1429                            create_auth_token => title => loc('Create'),
1430                            raw_html =>
1431                                q[<a class="btn menu-item" href="#create-auth-token" data-toggle="modal" rel="modal:open">]
1432                                . loc("Create") . "</a>"
1433                        );
1434                    }
1435                }
1436            }
1437        }
1438
1439    }
1440
1441    if ( $request_path =~ m{^(/Admin/Groups|/Group/(Summary|History)\.html)} ) {
1442        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) {
1443            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'};
1444            my $obj = RT::Group->new( $current_user );
1445            $obj->Load($id);
1446
1447            if ( $obj and $obj->id ) {
1448                $page->child( basics         => title => loc('Basics'),       path => "/Admin/Groups/Modify.html?id=" . $obj->id );
1449                $page->child( members        => title => loc('Members'),      path => "/Admin/Groups/Members.html?id=" . $obj->id );
1450                $page->child( memberships    => title => loc('Memberships'),  path => "/Admin/Groups/Memberships.html?id=" . $obj->id );
1451                $page->child( 'links'     =>
1452                              title       => loc("Links"),
1453                              path        => "/Admin/Groups/ModifyLinks.html?id=" . $obj->id,
1454                              description => loc("Group links"),
1455                );
1456                $page->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/Groups/GroupRights.html?id=" . $obj->id );
1457                $page->child( 'user-rights'  => title => loc('User Rights'),  path => "/Admin/Groups/UserRights.html?id=" . $obj->id );
1458                $page->child( history        => title => loc('History'),      path => "/Admin/Groups/History.html?id=" . $obj->id );
1459                $page->child( 'summary'   =>
1460                              title       => loc("Group Summary"),
1461                              path        => "/Group/Summary.html?id=" . $obj->id,
1462                              description => loc("Group summary page"),
1463                );
1464            }
1465        }
1466    }
1467
1468    if ( $request_path =~ m{^/Admin/CustomFields/} ) {
1469        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) {
1470            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'};
1471            my $obj = RT::CustomField->new( $current_user );
1472            $obj->Load($id);
1473
1474            if ( $obj and $obj->id ) {
1475                $page->child( basics           => title => loc('Basics'),       path => "/Admin/CustomFields/Modify.html?id=".$id );
1476                $page->child( 'group-rights'   => title => loc('Group Rights'), path => "/Admin/CustomFields/GroupRights.html?id=" . $id );
1477                $page->child( 'user-rights'    => title => loc('User Rights'),  path => "/Admin/CustomFields/UserRights.html?id=" . $id );
1478                unless ( $obj->IsOnlyGlobal ) {
1479                    $page->child( 'applies-to' => title => loc('Applies to'),   path => "/Admin/CustomFields/Objects.html?id=" . $id );
1480                }
1481            }
1482        }
1483    }
1484
1485    if ( $request_path =~ m{^/Admin/CustomRoles} ) {
1486        if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) {
1487            my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'};
1488            my $obj = RT::CustomRole->new( $current_user );
1489            $obj->Load($id);
1490
1491            if ( $obj and $obj->id ) {
1492                $page->child( basics       => title => loc('Basics'),       path => "/Admin/CustomRoles/Modify.html?id=".$id );
1493                $page->child( 'applies-to' => title => loc('Applies to'),   path => "/Admin/CustomRoles/Objects.html?id=" . $id );
1494                $page->child( 'visibility' => title => loc('Visibility'),   path => "/Admin/CustomRoles/Visibility.html?id=" . $id );
1495            }
1496        }
1497    }
1498
1499    if ( $request_path =~ m{^/Admin/Scrips/} ) {
1500        if ( $HTML::Mason::Commands::m->request_args->{'id'} && $HTML::Mason::Commands::m->request_args->{'id'} =~ /^\d+$/ ) {
1501            my $id = $HTML::Mason::Commands::m->request_args->{'id'};
1502            my $obj = RT::Scrip->new( $current_user );
1503            $obj->Load($id);
1504
1505            my ( $admin_cat, $create_path_arg, $from_query_param );
1506            my $from_arg = $HTML::Mason::Commands::DECODED_ARGS->{'From'} || q{};
1507            my ($from_queue) = $from_arg =~ /^(\d+)$/;
1508            if ( $from_queue ) {
1509                $admin_cat = "Queues/Scrips.html?id=$from_queue";
1510                $create_path_arg = "?Queue=$from_queue";
1511                $from_query_param = ";From=$from_queue";
1512            }
1513            elsif ( $from_arg eq 'Global' ) {
1514                $admin_cat = 'Global/Scrips.html';
1515                $create_path_arg = '?Global=1';
1516                $from_query_param = ';From=Global';
1517            }
1518            else {
1519                $admin_cat = 'Scrips';
1520                $from_query_param = $create_path_arg = q{};
1521            }
1522            my $scrips = $page->child( scrips => title => loc('Scrips'), path => "/Admin/${admin_cat}" );
1523            $scrips->child( select => title => loc('Select'), path => "/Admin/${admin_cat}" );
1524            $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html${create_path_arg}" );
1525
1526            $page->child( basics => title => loc('Basics') => path => "/Admin/Scrips/Modify.html?id=" . $id . $from_query_param );
1527            $page->child( 'applies-to' => title => loc('Applies to'), path => "/Admin/Scrips/Objects.html?id=" . $id . $from_query_param );
1528        }
1529        elsif ( $request_path =~ m{^/Admin/Scrips/(index\.html)?$} ) {
1530            HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Scrips/" );
1531            HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html" );
1532        }
1533        elsif ( $request_path =~ m{^/Admin/Scrips/Create\.html$} ) {
1534            my ($queue) = $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} && $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} =~ /^(\d+)$/;
1535            my $global_arg = $HTML::Mason::Commands::DECODED_ARGS->{'Global'};
1536            if ($queue) {
1537                HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Queues/Scrips.html?id=$queue" );
1538                HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html?Queue=$queue" );
1539            } elsif ($global_arg) {
1540                HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Global/Scrips.html" );
1541                HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html?Global=1" );
1542            } else {
1543                HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Scrips" );
1544                HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html" );
1545            }
1546        }
1547    }
1548
1549    if ( $request_path =~ m{^/Admin/Lifecycles} && $current_user->HasRight( Object => RT->System, Right => 'SuperUser' ) ) {
1550        if (defined($HTML::Mason::Commands::DECODED_ARGS->{'Name'}) && defined($HTML::Mason::Commands::DECODED_ARGS->{'Type'}) ) {
1551            my $lifecycles = $page->child( 'lifecycles' =>
1552                title       => loc('Lifecycles'),
1553                description => loc('Manage lifecycles'),
1554                path        => '/Admin/Lifecycles/',
1555            );
1556            $lifecycles->child( select => title => loc('Select'), path => "/Admin/Lifecycles/" );
1557            $lifecycles->child( create => title => loc('Create'), path => "/Admin/Lifecycles/Create.html" );
1558
1559            my $LifecycleObj = RT::Lifecycle->new();
1560            $LifecycleObj->Load(Name => $HTML::Mason::Commands::DECODED_ARGS->{'Name'}, Type => $HTML::Mason::Commands::DECODED_ARGS->{'Type'});
1561
1562            if ($LifecycleObj->Name && $LifecycleObj->{data}{type} eq $HTML::Mason::Commands::DECODED_ARGS->{'Type'}) {
1563                my $Name_uri = $LifecycleObj->Name;
1564                my $Type_uri = $LifecycleObj->Type;
1565                RT::Interface::Web::EscapeURI(\$Name_uri);
1566                RT::Interface::Web::EscapeURI(\$Type_uri);
1567
1568                unless ( RT::Interface::Web->ClientIsIE ) {
1569                    $page->child( basics => title => loc('Modify'),  path => "/Admin/Lifecycles/Modify.html?Type=" . $Type_uri . ";Name=" . $Name_uri );
1570                }
1571                $page->child( actions => title => loc('Actions'), path => "/Admin/Lifecycles/Actions.html?Type=" . $Type_uri . ";Name=" . $Name_uri );
1572                $page->child( rights => title => loc('Rights'), path => "/Admin/Lifecycles/Rights.html?Type=" . $Type_uri . ";Name=" . $Name_uri );
1573                $page->child( mappings => title => loc('Mappings'),  path => "/Admin/Lifecycles/Mappings.html?Type=" . $Type_uri . ";Name=" . $Name_uri );
1574                $page->child( advanced => title => loc('Advanced'),  path => "/Admin/Lifecycles/Advanced.html?Type=" . $Type_uri . ";Name=" . $Name_uri );
1575            }
1576        }
1577        else {
1578            $page->child( select => title => loc('Select'), path => "/Admin/Lifecycles/" );
1579            $page->child( create => title => loc('Create'), path => "/Admin/Lifecycles/Create.html" );
1580        }
1581    }
1582
1583    if ( $request_path =~ m{^/Admin/Global/Scrips\.html} ) {
1584        $page->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" );
1585        $page->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Global=1" );
1586    }
1587
1588    if ( $request_path =~ m{^/Admin(?:/Global)?/Conditions} ) {
1589        $page->child( select => title => loc('Select'), path => "/Admin/Global/Conditions.html" );
1590        $page->child( create => title => loc('Create'), path => "/Admin/Conditions/Create.html" );
1591    }
1592
1593    if ( $request_path =~ m{^/Admin(?:/Global)?/Actions} ) {
1594        $page->child( select => title => loc('Select'), path => "/Admin/Global/Actions.html" );
1595        $page->child( create => title => loc('Create'), path => "/Admin/Actions/Create.html" );
1596    }
1597
1598    if ( $request_path =~ m{^/Admin/Global/Templates?\.html} ) {
1599        $page->child( select => title => loc('Select'), path => "/Admin/Global/Templates.html" );
1600        $page->child( create => title => loc('Create'), path => "/Admin/Global/Template.html?Create=1" );
1601    }
1602
1603    if ( $request_path =~ m{^/Admin/Articles/Classes/} ) {
1604        if ( my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'} ) {
1605            my $obj = RT::Class->new( $current_user );
1606            $obj->Load($id);
1607
1608            if ( $obj and $obj->id ) {
1609                my $section = $page->child( select => title => loc("Classes"), path => "/Admin/Articles/Classes/" );
1610                $section->child( select => title => loc('Select'), path => "/Admin/Articles/Classes/" );
1611                $section->child( create => title => loc('Create'), path => "/Admin/Articles/Classes/Modify.html?Create=1" );
1612
1613                $page->child( basics          => title => loc('Basics'),        path => "/Admin/Articles/Classes/Modify.html?id=".$id );
1614                $page->child( topics          => title => loc('Topics'),        path => "/Admin/Articles/Classes/Topics.html?id=".$id );
1615                $page->child( 'custom-fields' => title => loc('Custom Fields'), path => "/Admin/Articles/Classes/CustomFields.html?id=".$id );
1616                $page->child( 'group-rights'  => title => loc('Group Rights'),  path => "/Admin/Articles/Classes/GroupRights.html?id=".$id );
1617                $page->child( 'user-rights'   => title => loc('User Rights'),   path => "/Admin/Articles/Classes/UserRights.html?id=".$id );
1618                $page->child( 'applies-to'    => title => loc('Applies to'),    path => "/Admin/Articles/Classes/Objects.html?id=$id" );
1619            }
1620        } else {
1621            $page->child( select => title => loc('Select'), path => "/Admin/Articles/Classes/" );
1622            $page->child( create => title => loc('Create'), path => "/Admin/Articles/Classes/Modify.html?Create=1" );
1623        }
1624    }
1625}
1626
1627sub BuildSelfServiceNav {
1628    my $request_path = shift;
1629    my $top          = shift;
1630    my $widgets      = shift;
1631    my $page         = shift;
1632
1633    my %args = ( @_ );
1634
1635    my $current_user = $HTML::Mason::Commands::session{CurrentUser};
1636
1637    if (   RT->Config->Get('SelfServiceUseDashboard')
1638        && $request_path =~ m{^/SelfService/(?:index\.html)?$}
1639        && $current_user->HasRight(
1640            Right  => 'ShowConfigTab',
1641            Object => RT->System
1642        )
1643        && $current_user->HasRight( Right => 'ModifyDashboard', Object => RT->System )
1644       )
1645    {
1646        $page->child( content => title => loc('Content'), path => '/Admin/Global/SelfServiceHomePage.html' );
1647        $page->child( show    => title => loc('Show'),    path => '/SelfService/' );
1648    }
1649
1650    my $queues = RT::Queues->new( $current_user );
1651    $queues->UnLimit;
1652
1653    my $queue_count = 0;
1654    my $queue_id;
1655
1656    while ( my $queue = $queues->Next ) {
1657        next unless $queue->CurrentUserHasRight('CreateTicket');
1658        $queue_id = $queue->id;
1659        $queue_count++;
1660        last if ( $queue_count > 1 );
1661    }
1662
1663    my $home = $top->child( home => title => loc('Homepage'), path => '/' );
1664
1665    if ( $queue_count > 1 ) {
1666        $home->child( new => title => loc('Create Ticket'), path => '/SelfService/CreateTicketInQueue.html' );
1667    } elsif ( $queue_id ) {
1668        $home->child( new => title => loc('Create Ticket'), path => '/SelfService/Create.html?Queue=' . $queue_id );
1669    }
1670
1671    my $menu_label = loc('Tickets');
1672    my $menu_path = '/SelfService/';
1673    if ( RT->Config->Get('SelfServiceUseDashboard') ) {
1674        $menu_path = '/SelfService/Open.html';
1675    }
1676    my $tickets = $top->child( tickets => title => $menu_label, path => $menu_path );
1677    $tickets->child( open   => title => loc('Open tickets'),   path => '/SelfService/Open.html' );
1678    $tickets->child( closed => title => loc('Closed tickets'), path => '/SelfService/Closed.html' );
1679
1680    $top->child( "assets", title => loc("Assets"), path => "/SelfService/Asset/" )
1681        if $current_user->HasRight( Right => 'ShowAssetsMenu', Object => RT->System );
1682
1683    my $username = '<span class="current-user">'
1684                 . $HTML::Mason::Commands::m->interp->apply_escapes($current_user->Name, 'h')
1685                 . '</span>';
1686    my $about_me = $top->child( preferences =>
1687        title        => loc('Logged in as [_1]', $username),
1688        escape_title => 0,
1689        sort_order   => 99,
1690    );
1691
1692    if ( ( RT->Config->Get('SelfServiceUserPrefs') || '' ) eq 'view-info' ||
1693        $current_user->HasRight( Right => 'ModifySelf', Object => RT->System ) ) {
1694        $about_me->child( prefs => title => loc('Preferences'), path => '/SelfService/Prefs.html' );
1695    }
1696
1697    my $logout_url = RT->Config->Get('LogoutURL');
1698    if ( $current_user->Name
1699         && (   !RT->Config->Get('WebRemoteUserAuth')
1700              || RT->Config->Get('WebFallbackToRTLogin') )) {
1701        $about_me->child( logout => title => loc('Logout'), path => $logout_url );
1702    }
1703
1704    if ( RT->Config->Get('SelfServiceShowArticleSearch') ) {
1705        $widgets->child( 'goto-article' => raw_html => $HTML::Mason::Commands::m->scomp('/SelfService/Elements/SearchArticle') );
1706    }
1707
1708    $widgets->child( goto => raw_html => $HTML::Mason::Commands::m->scomp('/SelfService/Elements/GotoTicket') );
1709
1710    if ($request_path =~ m{^/SelfService/Asset/} and $HTML::Mason::Commands::DECODED_ARGS->{id}) {
1711        my $id   = $HTML::Mason::Commands::DECODED_ARGS->{id};
1712        $page->child("display",     title => loc("Display"),        path => "/SelfService/Asset/Display.html?id=$id");
1713        $page->child("history",     title => loc("History"),        path => "/SelfService/Asset/History.html?id=$id");
1714
1715        if (Menu->child("new")) {
1716            my $actions = $page->child("actions", title => loc("Actions"));
1717            $actions->child("create-linked-ticket", title => loc("Create linked ticket"), path => "/SelfService/Asset/CreateLinkedTicket.html?Asset=$id");
1718        }
1719    }
1720
1721    # due to historical reasons of always having been in /Elements/Tabs
1722    $HTML::Mason::Commands::m->callback( CallbackName => 'SelfService', Path => $request_path, ARGSRef => \%args, CallbackPage => '/Elements/Tabs' );
1723}
1724
17251;
1726