From bf79061f0802a84d3d90f6df9b80ed705b873aab Mon Sep 17 00:00:00 2001 From: Jason Crome Date: Thu, 9 May 2024 11:30:14 -0400 Subject: [PATCH 01/26] Always pass import flag within LDAP import When constructing a user object in the LDAP import, the import flag was not passed through as it is elsewhere in the import process. If this flag is needed in the LDAP field mapping, there was no guaranteed availability. Explicitly passing the import argument to _build_user_object ensures that LDAP mappings will always be aware of whether this is a live or test import. --- lib/RT/LDAPImport.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/RT/LDAPImport.pm b/lib/RT/LDAPImport.pm index 6baf7a1c82e..11f76fab781 100644 --- a/lib/RT/LDAPImport.pm +++ b/lib/RT/LDAPImport.pm @@ -601,7 +601,7 @@ sub _import_users { my $done = 0; my $count = scalar @$users; while (my $entry = shift @$users) { - my $user = $self->_build_user_object( ldap_entry => $entry ); + my $user = $self->_build_user_object( ldap_entry => $entry, import => $args{import} ); $self->_import_user( user => $user, ldap_entry => $entry, import => $args{import} ); $done++; $RT::Logger->debug("Imported $done/$count users"); From 60c76e37582312120a68bdc99119ed64d792361e Mon Sep 17 00:00:00 2001 From: Andrew Ruthven Date: Thu, 26 Sep 2024 16:19:18 +1200 Subject: [PATCH 02/26] Change free port detection to how PSGI binds to a port The previous method using socket/connect would allow us to bind to a port that PSGI then couldn't bind to. If a port is connected on a specific IP, then using connect with 0.0.0.0 would still connect okay. Using IO::Socket::INET this will fail, which is reasonable for 0.0.0.0, and then PSGI wouldn't be able to start and the test would fail. This may resolve the intermittant test failures. Debian bug: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1074781 Fixes: I#37886 --- lib/RT/Test.pm | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm index 14707643a64..b68e8b2184a 100644 --- a/lib/RT/Test.pm +++ b/lib/RT/Test.pm @@ -69,6 +69,7 @@ my $Test_NoWarnings_Catcher = $SIG{__WARN__}; my $check_warnings_in_end = 1; use Socket; +use IO::Socket::INET; use File::Temp qw(tempfile); use File::Path qw(mkpath); use File::Spec; @@ -256,14 +257,17 @@ sub find_idle_port { # server binds. However, since we mostly care about race # conditions with ourselves under high concurrency, this is # generally good enough. - my $paddr = sockaddr_in( $port, inet_aton('localhost') ); - socket( SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp') ) - or die "socket: $!"; - if ( connect( SOCK, $paddr ) ) { - close(SOCK); + + if (! IO::Socket::INET->new( + Listen => SOMAXCONN, + LocalPort => $port, + LocalAddr => '0.0.0.0', + Proto => 'tcp', + ReuseAddr => 1, + )) { + # Port is probably busy. redo; } - close(SOCK); } $ports{$port}++; From cf0e218983dd53378fc5fe9c52ee2d78168c7522 Mon Sep 17 00:00:00 2001 From: sunnavy Date: Wed, 9 Oct 2024 12:05:50 -0400 Subject: [PATCH 03/26] Fix typo in shredder pod --- lib/RT/Shredder.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/RT/Shredder.pm b/lib/RT/Shredder.pm index 911bcedae66..a8bffe56d17 100644 --- a/lib/RT/Shredder.pm +++ b/lib/RT/Shredder.pm @@ -149,7 +149,7 @@ sometimes when you have big mail loops you may hit it. Defaults to 1000. To change this (for example, to 10000) add the following to your F: - Set( $DependenciesLimit, 10_000 );> + Set( $DependenciesLimit, 10_000 ); =head2 $ShredderStoragePath @@ -160,7 +160,7 @@ F (assuming an /opt/rt5 installation). To change this (for example, to /some/backup/path) add the following to your F: - Set( $ShredderStoragePath, "/some/backup/path" );> + Set( $ShredderStoragePath, "/some/backup/path" ); Be sure to specify an absolute path. From b5f5b209423498168a232359fde42d3a1791e6e5 Mon Sep 17 00:00:00 2001 From: sunnavy Date: Fri, 17 Aug 2018 21:12:47 +0800 Subject: [PATCH 04/26] Cache `clear` output to avoid unnecessary system calls for better performance Repeated system calls is expensive enough to slow down the process a lot when database is huge. In some worst cases, it even could make rt-importer 1000x slower. --- lib/RT/Migrate.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/RT/Migrate.pm b/lib/RT/Migrate.pm index 6a020ed8ccf..6c82c9cff75 100644 --- a/lib/RT/Migrate.pm +++ b/lib/RT/Migrate.pm @@ -105,6 +105,7 @@ sub progress { my $start; my $left; my $offset; + my $clear = `clear`; return sub { my $obj = shift; my $force = shift; @@ -117,7 +118,7 @@ sub progress { my $elapsed = $now - $start; # Determine terminal size - print `clear`; + print $clear; my ($cols, $rows) = (80, 25); eval { require Term::ReadKey; From 855fe581784557b7627a6a54c6d4600f0cceb5dd Mon Sep 17 00:00:00 2001 From: Brad Embree Date: Thu, 10 Oct 2024 11:07:56 -0700 Subject: [PATCH 05/26] Fix endless loop when using --ids It was possible to create an endless loop when using --ids so make sure we exit the loop when using --ids. --- sbin/rt-externalize-attachments.in | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sbin/rt-externalize-attachments.in b/sbin/rt-externalize-attachments.in index 022910348c0..0ee478a6489 100644 --- a/sbin/rt-externalize-attachments.in +++ b/sbin/rt-externalize-attachments.in @@ -159,7 +159,8 @@ for my $class (qw/RT::Attachments RT::ObjectCustomFieldValues/) { $attach->{'find_disabled_rows'} = 1; } - $attach->RowsPerPage(100); + # if using ids arg process them all at once + $attach->RowsPerPage(100) unless $opts{ids}; while ( my $a = $attach->Next and $batchsize > 0) { $id = $a->id; @@ -221,7 +222,13 @@ for my $class (qw/RT::Attachments RT::ObjectCustomFieldValues/) { } } - last unless $attach->Count and $batchsize > 0; + # an endless loop was observed when using --ids + # if any one of the ids is for an attachment that will not be externalized + # (perhaps coming from an RT with a smaller ExternalStorageCutoffSize) + # the code would keep looping over the same ids + # the loops also occurs if run using --dry-run and --ids + # the fix is to break out of the loop if --ids was used + last unless !$opts{ids} and $attach->Count and $batchsize > 0; } # Do not affect the normal externalize process when handing specific ones $last->{$class} = $id unless $opts{ids}; From 70bb9d846255bec115c7165d8cb71c6850fadfae Mon Sep 17 00:00:00 2001 From: Craig Kaiser Date: Fri, 18 Oct 2024 08:59:15 -0400 Subject: [PATCH 06/26] Show scroll if needed for wide content This is specially useful for wide tables where wrapping can break the structure. --- share/static/css/elevator-light/history.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/static/css/elevator-light/history.css b/share/static/css/elevator-light/history.css index 296b4254359..38c0af8b309 100644 --- a/share/static/css/elevator-light/history.css +++ b/share/static/css/elevator-light/history.css @@ -78,7 +78,7 @@ div.history-container { margin-top: 0.5em; padding-top: 0.5em; border-top: 1px solid #ccc; - /*overflow: auto; */ + overflow: auto; min-height: 2.5em; /* To avoid overlapping of "downloadattachment" by messagebody */ clear: left; From d2828008144a78d014f736a439074ae9e6d21e09 Mon Sep 17 00:00:00 2001 From: Jim Brandt Date: Mon, 21 Oct 2024 15:25:34 -0400 Subject: [PATCH 07/26] Add missing Link columnmap definitions for assets --- share/html/Elements/RT__Asset/ColumnMap | 31 +++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/share/html/Elements/RT__Asset/ColumnMap b/share/html/Elements/RT__Asset/ColumnMap index 9479349e902..dff74da195e 100644 --- a/share/html/Elements/RT__Asset/ColumnMap +++ b/share/html/Elements/RT__Asset/ColumnMap @@ -72,6 +72,33 @@ my $linkUsers = RT::Util::RecursiveSub(sub { return @ret; } }); + +my $LinkCallback = sub { + my $method = shift; + + my $mode = $RT::Link::TYPEMAP{$method}{Mode}; + my $type = $RT::Link::TYPEMAP{$method}{Type}; + my $other_mode = ($mode eq "Target" ? "Base" : "Target"); + my $mode_uri = $mode.'URI'; + + return sub { + my $ObjectType = $_[2]||''; + map { + \'', + ( $_->$mode_uri->AsString ), + \'
', + } # if someone says __RefersTo.{Ticket}__ filter for only local links that are tickets + grep { $ObjectType + ? ( $_->$mode_uri->IsLocal + && ( $_->$mode_uri->Object->RecordType eq $ObjectType )) + : 1 + } + @{ $_[0]->Links($other_mode,$type)->ItemsArrayRef } + } +}; + my $COLUMN_MAP = { Name => { attribute => 'Name', @@ -115,6 +142,10 @@ my $COLUMN_MAP = { return \'Inactive'; } }, + # Everything from LINKTYPEMAP + (map { + $_ => { value => $LinkCallback->( $_ ) } + } keys %RT::Link::TYPEMAP), }; my $ranges = $m->notes('custom_date_ranges'); From bc22a87e8d998418b0878b35d4b7b74b02b7bf39 Mon Sep 17 00:00:00 2001 From: Jim Brandt Date: Mon, 21 Oct 2024 15:43:58 -0400 Subject: [PATCH 08/26] Document Link filtering feature in search result Format --- docs/query_builder.pod | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/query_builder.pod b/docs/query_builder.pod index 6bd28449397..953f013b0af 100644 --- a/docs/query_builder.pod +++ b/docs/query_builder.pod @@ -98,6 +98,8 @@ F] =for :man [Sorting and Display Columns F] +=head2 Sorting Results + There is more than one option for Sorting so you can view tickets in a search result set in a meaningful order. For example, let's say you start off by sorting tickets in a search by their owner. Tickets will then @@ -106,6 +108,8 @@ each owner. To further organize the tickets owned by each user, you could add a sort by Due to see tickets sorted first by owner, and then Due date within the tickets per owner. +=head2 Display Columns + The Display Columns tab allows you to add or remove information displayed in the results of your search. NEWLINE indicates a line break, or new row, in how the results are displayed. NBSP for adding an empty column (such as what shows @@ -143,6 +147,48 @@ the most important information and also to optimize the display for users. This formatting is saved when you save a search, so you can set different formats for different searches (see L). +=head2 Advanced Output Formatting + +There are some additional advanced features you can use to further customize +the appearance and functionality of your search results. You can enable +these by editing the underlying Format definitions directly. To see these +definitions, click the Advanced tab in the Query Builder. + +The Advanced page shows the full text of your query at the top, and has a +Format box on the bottom, which shows the actual format definition for the +columns you have selected. You can change these directly in the Format box +to refine how columns are shown in search results. Here are some examples +of changes you can make. + +=over + +=item Change the Column Title + +The default column format for the ticket ID looks like: + + '__id__/TITLE:#', + +If you want the column title to show "ID" rather than "#", you can change +it to: + + '__id__/TITLE:ID', + +=item Filter Link Types + +The default format for links, like Depends On, looks like this: + + '__DependsOn__', + +A ticket can have links to other tickets, assets, or other things. If you +want to filter the display to only show asset links, you can update the +format like this: + + '__DependsOn.{Asset}__', + +If you set "Ticket" instead of "Asset", it will show only ticket links. + +=back + =head1 Dynamic Filtering and Sorting After you perform a search, you can refine your results directly on the From a43f0bcf90bfd7b954cb7223ba148273966345d0 Mon Sep 17 00:00:00 2001 From: Brad Embree Date: Fri, 1 Nov 2024 08:31:57 -0700 Subject: [PATCH 09/26] Add cgm-only mode to rt-validator --- sbin/rt-validator.in | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sbin/rt-validator.in b/sbin/rt-validator.in index cac0cb30dad..31c8f95b3ba 100644 --- a/sbin/rt-validator.in +++ b/sbin/rt-validator.in @@ -75,6 +75,7 @@ Init( 'force', 'verbose|v', 'links-only', + 'cgm-only', ); Pod::Usage::pod2usage( { verbose => 2 } ) unless $opt{check}; @@ -1330,6 +1331,10 @@ if ($opt{'links-only'}) { @do_check = grep { /^Links:/ } @do_check; } +if ($opt{'cgm-only'}) { + @do_check = grep { /^CGM vs/ } @do_check; +} + my $status = 1; while ( my $check = shift @do_check ) { $status *= $CHECKS{ $check }->(); @@ -1670,4 +1675,8 @@ records or resurrect accidentally deleted. only run the Link validation routines, useful if you changed your Organization +=item cgm-only + + only run the CachedGroupMembers validation routines + =back From d42b388fbe2b7944d1b87fef787ef65d7e0c2ae1 Mon Sep 17 00:00:00 2001 From: sunnavy Date: Fri, 1 Nov 2024 14:32:49 -0400 Subject: [PATCH 10/26] Pass $self to RT::Group::_AddMember to connect created txns with current object This is mainly for ticket's scrips. By passing current ticket object, we can not just get DryRun mode, but also keep track of created ticket transactions for scrips in batch mode. --- lib/RT/Record/Role/Roles.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/RT/Record/Role/Roles.pm b/lib/RT/Record/Role/Roles.pm index 4d5c1d90131..a388dc0d1c9 100644 --- a/lib/RT/Record/Role/Roles.pm +++ b/lib/RT/Record/Role/Roles.pm @@ -527,7 +527,7 @@ sub AddRoleMember { return (0, $self->loc('[_1] cannot be a group', $group->Label) ) if $group->SingleMemberRoleGroup and $principal->IsGroup; - ( (my $ok), $msg ) = $group->_AddMember( %args, PrincipalId => $principal->Id, RecordTransaction => !$args{Silent} ); + ( (my $ok), $msg ) = $group->_AddMember( %args, Object => $self, PrincipalId => $principal->Id, RecordTransaction => !$args{Silent} ); unless ($ok) { $RT::Logger->error("Failed to add principal ".$principal->Id." as a member of group ".$group->Id.": ".$msg); From 3d2c6ee19fe1019f71698963a1f6c35c0b08830d Mon Sep 17 00:00:00 2001 From: sunnavy Date: Mon, 4 Nov 2024 09:42:15 -0500 Subject: [PATCH 11/26] Pass queue info to SelectOwner in FilterTickets This aligns with SelectOwner in query builder. Additionally, it enhances the performance of retrieving owner candidates, when the search query has queue limitations. --- share/html/Search/Elements/FilterTickets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/html/Search/Elements/FilterTickets b/share/html/Search/Elements/FilterTickets index f4e075152a0..4d44fb8a1b3 100644 --- a/share/html/Search/Elements/FilterTickets +++ b/share/html/Search/Elements/FilterTickets @@ -107,7 +107,7 @@ <&|/l&>Owner:
- <& /Elements/SelectOwner, Name => 'Owner', Default => $filter->{Owner}, Size => 6 &> + <& /Elements/SelectOwner, Name => 'Owner', Default => $filter->{Owner}, Size => 6, Queues => { map { $_->Id => 1 } @$queues } &>
% } elsif ( $Attribute eq 'SLA' ) { From 41fe8ff2de030854465897838eec4c269eadf9dc Mon Sep 17 00:00:00 2001 From: Jim Brandt Date: Tue, 22 Oct 2024 17:07:43 -0400 Subject: [PATCH 12/26] Don't return a disabled Default Queue If a user has a Default Queue selected in preferences and that queue is disabled, the Create Ticket page showed an error and did not allow the user to select a different queue, blocking them from creating new tickets. Detect if any of the Default Queue options is disabled and return undef, allowing the Create Ticket page to load and present other queues for the user to select. --- lib/RT/Interface/Web.pm | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm index e6a04783bc3..96e89ebc636 100644 --- a/lib/RT/Interface/Web.pm +++ b/lib/RT/Interface/Web.pm @@ -5063,19 +5063,37 @@ Accepts no arguments, returns the ID of the default queue, if found, or undef. sub GetDefaultQueue { my $queue; + my $queue_obj = RT::Queue->new( $session{'CurrentUser'} ); # RememberDefaultQueue tracks the last queue used by this user, if set. if ( $session{'DefaultQueue'} && RT->Config->Get( "RememberDefaultQueue", $session{'CurrentUser'} ) ) { $queue = $session{'DefaultQueue'}; + + # Confirm the user can see and load the queue + $queue_obj->Load($queue); } - else { + + # Check for a personal user preference + if ( !defined $queue_obj->Name || $queue_obj->Disabled ) { $queue = RT->Config->Get( "DefaultQueue", $session{'CurrentUser'} ); + + if ( $queue ) { + # Confirm the user can see and load the queue + $queue_obj->Load($queue); + } + } + + # Check for global system-level setting + if ( !defined $queue_obj->Name || $queue_obj->Disabled ) { + $queue = RT->Config->Get( "DefaultQueue" ); + + if ( $queue ) { + # Confirm the user can see and load the queue + $queue_obj->Load($queue); + } } - # Confirm the user can see and load the default queue - my $queue_obj = RT::Queue->new( $HTML::Mason::Commands::session{'CurrentUser'} ); - $queue_obj->Load($queue); - return defined $queue_obj->Name ? $queue_obj->Id : undef; + return ($queue_obj && defined $queue_obj->Name && !$queue_obj->Disabled) ? $queue_obj->Id : undef; } =head2 UpdateDashboard From b99ff2a71ab6b7b07c374d60f782a3632dc2c556 Mon Sep 17 00:00:00 2001 From: Jim Brandt Date: Mon, 28 Oct 2024 15:52:41 -0400 Subject: [PATCH 13/26] Notify the admin if they disabled the system DefaultQueue --- share/html/Admin/Queues/Modify.html | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/share/html/Admin/Queues/Modify.html b/share/html/Admin/Queues/Modify.html index 79f0b30673d..044acd38f9d 100644 --- a/share/html/Admin/Queues/Modify.html +++ b/share/html/Admin/Queues/Modify.html @@ -268,12 +268,26 @@ ARGSRef => \%ARGS, ); + # Stash the Disabled value before update + my $disabled_pre_update = $QueueObj->Disabled; + push @results, UpdateRecordObject( AttributesRef => \@attribs, Object => $QueueObj, ARGSRef => \%ARGS ); + if ( $QueueObj->Disabled + && $disabled_pre_update != $QueueObj->Disabled + && ( RT->Config->Get("DefaultQueue") // 0 ) == $QueueObj->Id ) + { + # The queue was disabled and it's set as the system default. Tell the admin + push @results, + loc( + 'Queue was disabled and it is set as the system DefaultQueue. Update DefaultQueue to an active queue or unset this configuration.' + ); + } + $Disabled = $ARGS{'Disabled'} = $Enabled? 0: 1; $EnabledChecked = "" if $QueueObj->Disabled; From d2a9abe7c36bf1261876e96ba98b2473a54cdb9f Mon Sep 17 00:00:00 2001 From: Jim Brandt Date: Thu, 7 Nov 2024 12:55:54 -0500 Subject: [PATCH 14/26] Add loading lazy attribute to img tags in transactions A ticket with a long history can have dozens of img tags, and if the full history loads on ticket display, the RT server is hit with dozens of requests because each is an attachment that needs to be served by RT. Add the loading="lazy" attribute to img tags to make the browser wait until the image is in the view port before loading it. This newish attribute is now supported in all major browsers. --- etc/cpanfile | 2 +- lib/RT/Interface/Web.pm | 11 ++++++++--- share/html/Elements/ShowTransactionAttachments | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/etc/cpanfile b/etc/cpanfile index 8384ee6bd03..4ffbdf501c9 100644 --- a/etc/cpanfile +++ b/etc/cpanfile @@ -46,7 +46,7 @@ requires 'HTML::Gumbo'; requires 'HTML::Mason', '>= 1.43'; requires 'HTML::Mason::PSGIHandler', '>= 0.52'; requires 'HTML::Quoted'; -requires 'HTML::RewriteAttributes', '>= 0.05'; +requires 'HTML::RewriteAttributes', '>= 0.06'; requires 'HTML::Scrubber', '>= 0.08'; requires 'HTTP::Message', '>= 6.07'; requires 'IPC::Run3'; diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm index e6a04783bc3..52196015598 100644 --- a/lib/RT/Interface/Web.pm +++ b/lib/RT/Interface/Web.pm @@ -1986,9 +1986,14 @@ sub RewriteInlineImages { $$content = HTML::RewriteAttributes::Resources->rewrite($$content, sub { my $cid = shift; my %meta = @_; - return $cid unless lc $meta{tag} eq 'img' - and lc $meta{attr} eq 'src' - and $cid =~ s/^cid://i; + return $cid unless lc $meta{tag} eq 'img'; + + if ( !$meta{attrs}{loading} ) { + $meta{attrs}{loading} = 'lazy'; + push @{ $meta{attr_list} }, 'loading'; + } + + return $cid unless lc $meta{attr} eq 'src' && $cid =~ s/^cid://i; for my $attach (@{$args{Related}}) { if (($attach->GetHeader('Content-ID') || '') =~ /^(<)?\Q$cid\E(?(1)>)$/) { diff --git a/share/html/Elements/ShowTransactionAttachments b/share/html/Elements/ShowTransactionAttachments index 03f0d01dc8c..756d5fd7970 100644 --- a/share/html/Elements/ShowTransactionAttachments +++ b/share/html/Elements/ShowTransactionAttachments @@ -349,7 +349,7 @@ my $render_attachment = sub { . $m->interp->apply_escapes( $filename, 'u', 'h' ); $m->out( - qq{$efilename} + qq{$efilename} ); } elsif ( $message->ContentLength && $message->ContentLength > 0 ) { From 185e058bb1fd9cbc7a3998598f352c512876fe51 Mon Sep 17 00:00:00 2001 From: Jim Brandt Date: Wed, 13 Nov 2024 13:39:13 -0500 Subject: [PATCH 15/26] Update test image with new HTML-RewriteAttributes --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e6abb286c99..965dd089699 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # This Dockerfile is for testing only. -FROM bpssysadmin/rt-base-debian:RT-5.0.6-bullseye-20240506 +FROM bpssysadmin/rt-base-debian:RT-5.0.8-bullseye-20241112 ENV RT_TEST_PARALLEL 1 ENV RT_TEST_DEVEL 1 From d4a977303250f060bffd414fcd20a87e7bae4f5f Mon Sep 17 00:00:00 2001 From: sunnavy Date: Thu, 14 Nov 2024 14:59:53 -0500 Subject: [PATCH 16/26] Fix WithMember arguments in CreateTickets template example RT::Groups::WithMember expects a key => value pair. --- lib/RT/Action/CreateTickets.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/RT/Action/CreateTickets.pm b/lib/RT/Action/CreateTickets.pm index ae0580426da..82cad6849f5 100644 --- a/lib/RT/Action/CreateTickets.pm +++ b/lib/RT/Action/CreateTickets.pm @@ -130,7 +130,7 @@ A convoluted example: my $groups = RT::Groups->new(RT->SystemUser); $groups->LimitToUserDefinedGroups(); $groups->Limit(FIELD => "Name", OPERATOR => "=", VALUE => $name, CASESENSITIVE => 0); - $groups->WithMember($TransactionObj->CreatorObj->Id); + $groups->WithMember(PrincipalId => $TransactionObj->CreatorObj->Id); my $groupid = $groups->First->Id; From aa2f68691418fa7841ec2046a41c27ede11de291 Mon Sep 17 00:00:00 2001 From: sunnavy Date: Thu, 14 Nov 2024 15:39:28 -0500 Subject: [PATCH 17/26] Set obsolete Pragma header only if the content should not be cached When both "Pragma: no-cache" and "Cache-Control: max-age=604800, private" exist, the former wins on Safari/Chromium :/ But for back compatibility, here we keep Pragma if it's consistent with Cache-Control. --- share/html/Elements/HttpResponseHeaders | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/html/Elements/HttpResponseHeaders b/share/html/Elements/HttpResponseHeaders index 5582bdc64e6..a08fd36dfae 100644 --- a/share/html/Elements/HttpResponseHeaders +++ b/share/html/Elements/HttpResponseHeaders @@ -54,10 +54,6 @@ # Pragma: no-cache # Expires: [a short time in the past to account for any time drift] -# Pragma is deprecated and usually ignored if Cache-control is sent. -# Should only be used by HTTP/1.0 clients. -$r->headers_out->{'Pragma'} = 'no-cache'; - my $cache_control = 'no-cache'; my $expires = RT::Date->new(RT->SystemUser); @@ -82,6 +78,10 @@ else { $cache_control .= ', max-age=0'; } +# Pragma is deprecated and usually ignored if Cache-control is sent. +# Should only be used by HTTP/1.0 clients. +$r->headers_out->{'Pragma'} = 'no-cache' if $cache_control eq 'no-cache'; + $r->headers_out->{'Expires'} = $expires->RFC2616; if ( RT->Config->Get('WebStrictBrowserCache') ) { From a58ff416c743276e98be01e874c1615eefa561ac Mon Sep 17 00:00:00 2001 From: sunnavy Date: Thu, 14 Nov 2024 15:45:32 -0500 Subject: [PATCH 18/26] Cache binary attachments for better performance Binary attachments are immutable. --- share/html/Ticket/Attachment/dhandler | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/share/html/Ticket/Attachment/dhandler b/share/html/Ticket/Attachment/dhandler index 14432f170be..0c77f4f79bf 100644 --- a/share/html/Ticket/Attachment/dhandler +++ b/share/html/Ticket/Attachment/dhandler @@ -93,7 +93,10 @@ my $iana = Encode::find_encoding($enc); require MIME::Types; my $mimetype = MIME::Types->new->type($content_type); -unless ( $mimetype && $mimetype->isBinary ) { +if ( $mimetype && $mimetype->isBinary ) { + $m->comp('/Elements/HttpResponseHeaders', MaxAgeSeconds => 24 * 3600); +} +else { $content_type .= ";charset=$iana"; } From 8a0bd9425afd9f77ab944962ee0435ea59027520 Mon Sep 17 00:00:00 2001 From: Craig Kaiser Date: Fri, 1 Nov 2024 09:37:05 -0400 Subject: [PATCH 19/26] Allow users to delete subscription --- share/html/Dashboards/Subscription.html | 60 ++++++++++++++++++------- t/mail/dashboards.t | 1 + 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/share/html/Dashboards/Subscription.html b/share/html/Dashboards/Subscription.html index 1021c7c5f91..401c4eff483 100644 --- a/share/html/Dashboards/Subscription.html +++ b/share/html/Dashboards/Subscription.html @@ -72,8 +72,16 @@ <&| /Widgets/TitleBox, title => loc('Subscription') &> +% if (!$SubscriptionObj) { + <&| /Elements/LabeledValue, Label => loc('No Subscription Set') &> + <&|/l&>Set a schedule below to receive this dashboard in email + +% } else { + <&| /Elements/LabeledValue, Label => loc('Subscription Set') &> + <&|/l&>RT will send email for this dashboard based on the schedule below + +% } <&| /Elements/LabeledValue, Label => loc('Frequency') &> -
@@ -248,14 +256,19 @@ IsFirstSubscription => $SubscriptionObj ? 0 : 1 &> -
-
+
% if ($SubscriptionObj) { +
+ <& /Elements/Submit, Name => "DeleteSubscription", Label => loc('Delete Subscription') &> +
+
<& /Elements/Submit, Name => "Save", Label => loc('Save Changes') &> +
% } else { +
<& /Elements/Submit, Name => "Save", Label => loc('Subscribe') &> -% }
+% }
% if ($SubscriptionObj) { @@ -348,12 +361,12 @@ my %fields = ( DashboardId => $id, - Frequency => 'daily', - Monday => 1, - Tuesday => 1, - Wednesday => 1, - Thursday => 1, - Friday => 1, + Frequency => '', + Monday => 0, + Tuesday => 0, + Wednesday => 0, + Thursday => 0, + Friday => 0, Saturday => 0, Sunday => 0, Hour => '06:00', @@ -371,18 +384,33 @@ $m->callback( %ARGS, CallbackName => 'SubscriptionFields', FieldsRef => \%fields, SubscriptionObj => $SubscriptionObj, DashboardObj => $Dashboard); +if ( defined $ARGS{DeleteSubscription} ) { + if ($SubscriptionObj) { + ($ok, $msg) = $SubscriptionObj->Delete; + if ($ok) { + push @results, loc("Subscription to dashboard [_1] deleted", $Dashboard->Name); + undef $SubscriptionObj; + } else { + push @results, loc("Unable to delete subscription: [_1]", $msg); + } + } +} + # update any fields with the values from the subscription object -if ($SubscriptionObj) { +if ($SubscriptionObj && $SubscriptionObj->Content) { for my $field (keys %fields) { $fields{$field} = $SubscriptionObj->SubValue($field); } } -# finally, update any fields with arguments passed in by the user -for my $field (keys %fields) { - next if $field eq 'DashboardId'; # but this one is immutable - $fields{$field} = $ARGS{$field} - if defined($ARGS{$field}) || $ARGS{$field.'-Magic'}; +# We do not want to process any magic args if we are deleting the subscription +unless ( defined $ARGS{DeleteSubscription} ) { + # finally, update any fields with arguments passed in by the user + for my $field (keys %fields) { + next if $field eq 'DashboardId'; # but this one is immutable + $fields{$field} = $ARGS{$field} + if defined($ARGS{$field}) || $ARGS{$field.'-Magic'}; + } } $m->callback( %ARGS, CallbackName => 'MassageSubscriptionFields', FieldsRef => \%fields, diff --git a/t/mail/dashboards.t b/t/mail/dashboards.t index e2eafaa2b26..7bee52b882a 100644 --- a/t/mail/dashboards.t +++ b/t/mail/dashboards.t @@ -89,6 +89,7 @@ is @mails, 0, "no mail yet since there's no subscription"; create_subscription($baseurl, $m, Frequency => 'daily', + Monday => 1, Hour => '06:00', ); From 66454452b88578eb8fca3f8dd13debc98c3b6141 Mon Sep 17 00:00:00 2001 From: Jim Brandt Date: Fri, 15 Nov 2024 10:23:43 -0500 Subject: [PATCH 20/26] Add tests for Rights Inspector Specifically to call code that references Groups without proper quoting for MySQL 8. --- t/api/rights_inspector.t | 96 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 t/api/rights_inspector.t diff --git a/t/api/rights_inspector.t b/t/api/rights_inspector.t new file mode 100644 index 00000000000..3624216658d --- /dev/null +++ b/t/api/rights_inspector.t @@ -0,0 +1,96 @@ + +use strict; +use warnings; +use RT; +use RT::Test tests => undef; + +use_ok('RT::RightsInspector'); +ok (my $cu = RT::CurrentUser->new('root'), 'Created root current user'); + +my $user = RT::Test->load_or_create_user( + Name => 'user', Password => 'password', +); +ok $user && $user->id, 'loaded or created user'; + +my $queue = RT::Test->load_or_create_queue( Name => 'TestingQueue' ); +ok $queue && $queue->id, 'loaded or created queue'; +my $qname = $queue->Name; + +diag 'Global Rights Inspector tests'; +{ + my %args = ( + continueAfter => 0, + object => "", + principal => "user:root", + right => "SuperUser", + user => $cu, + ); + + my $results = RT::RightsInspector->Search(%args); + + ok( scalar @{$results->{results}}, 'Got a record' ); + is( $results->{results}->[0]->{right}, 'SuperUser', 'Found SuperUser right' ); + is( $results->{results}->[0]->{principal}{id}, $cu->Id, 'User is root' ); +} + +diag 'Ticket rights test'; +{ + + # Set up AdminCc rights + my $group = $queue->RoleGroup( 'AdminCc' ); + ok( $group->Id, "load queue AdminCc role group" ); + my $ace = RT::ACE->new( RT->SystemUser ); + my ($ace_id, $msg) = $group->PrincipalObj->GrantRight( + Right => 'ModifyTicket', Object => $queue + ); + ok( $ace_id, "Granted queue AdminCc role group with ModifyTicket right: $msg" ); + ok( $group->PrincipalObj->HasRight( Right => 'ModifyTicket', Object => $queue ), + "role group can modify ticket" + ); + + my %args = ( + continueAfter => 0, + object => $qname, + principal => "", + right => "", + user => $cu, + ); + + my $results = RT::RightsInspector->Search(%args); + + ok( scalar @{$results->{results}}, 'Got a record' ); + is( $results->{results}->[0]->{right}, 'ModifyTicket', 'Found ModifyTicket right' ); + is( $results->{results}->[0]->{object}{id}, $queue->Id, "Object is $qname" ); + + my $ticket = RT::Ticket->new(RT->SystemUser); + my ($ticket_id) = $ticket->Create( Queue => $queue->id, Subject => 'test'); + ok( $ticket_id, 'new ticket created' ); + is( $ticket->Owner, RT->Nobody->Id, 'owner of the new ticket is nobody' ); + + my $status; + ($status, $msg) = $ticket->AddWatcher( + Type => 'AdminCc', PrincipalId => $user->PrincipalId + ); + ok( $status, "Successfully added user as AdminCc"); + ok( $user->HasRight( Right => 'ModifyTicket', Object => $ticket ), + "user is AdminCc and can modify ticket" + ); + + %args = ( + continueAfter => 0, + object => "", + principal => 'user:' . $user->Id, + right => "ModifyTicket", + user => $cu, + ); + + $results = RT::RightsInspector->Search(%args); + + ok( scalar @{$results->{results}}, 'Got a record' ); + is( $results->{results}->[0]->{right}, 'ModifyTicket', 'Found ModifyTicket right' ); + is( $results->{results}->[0]->{object}{id}, $queue->Id, "Object is $qname" ); + is( $results->{results}->[0]->{principal}{label}, 'AdminCc', 'Right label is AdminCc' ); + is( $results->{results}->[0]->{principal}{primary_record}{id}, $user->Id, 'primary_record is test user' ); +} + +done_testing; From 3e3b174f6eb5de779275294d535ee99873340dd3 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Tue, 12 Nov 2024 16:02:36 -0500 Subject: [PATCH 21/26] Quote new references to the Groups table for MySQL 8 --- lib/RT/Migrate/Importer.pm | 7 +++++-- lib/RT/RightsInspector.pm | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/RT/Migrate/Importer.pm b/lib/RT/Migrate/Importer.pm index 5a9e1e9df30..108def3e9e2 100644 --- a/lib/RT/Migrate/Importer.pm +++ b/lib/RT/Migrate/Importer.pm @@ -629,10 +629,13 @@ sub CloseStream { # Fill CGM + # $self here isn't an RT::Record + my $groups_table = RT::Group->can('QuotedTableName') ? RT::Group->QuotedTableName('Groups') : 'Groups'; + # Groups $self->RunSQL(<<'EOF'); INSERT INTO CachedGroupMembers (GroupId, MemberId, Via, ImmediateParentId, Disabled) - SELECT Groups.id, Groups.id, 0, Groups.id, Principals.Disabled FROM Groups + SELECT Groups.id, Groups.id, 0, Groups.id, Principals.Disabled FROM $groups_table LEFT JOIN Principals ON ( Groups.id = Principals.id ) LEFT JOIN CachedGroupMembers ON ( Groups.id = CachedGroupMembers.GroupId @@ -673,7 +676,7 @@ FROM AND cgm3.MemberId = gm2.MemberId AND cgm3.Via = cgm1.id AND cgm3.ImmediateParentId = cgm1.MemberId ) - LEFT JOIN Groups g ON ( + LEFT JOIN $groups_table g ON ( cgm1.GroupId = g.id ) WHERE cgm1.GroupId != cgm1.MemberId diff --git a/lib/RT/RightsInspector.pm b/lib/RT/RightsInspector.pm index 47b6601d80d..635c9a4c446 100644 --- a/lib/RT/RightsInspector.pm +++ b/lib/RT/RightsInspector.pm @@ -271,17 +271,20 @@ sub InnerRoleQuery { or die "No parent mapping specified for $inner_class"; my $parent_table = $parent_class->Table; + # $self here isn't an RT::Record + my $groups_table = RT::Group->can('QuotedTableName') ? RT::Group->QuotedTableName('Groups') : 'Groups'; + my @query = qq[ SELECT main.id, MIN(InnerRecords.id) AS example_record, COUNT(InnerRecords.id)-1 AS other_count FROM ACL main - JOIN Groups ParentRoles + JOIN $groups_table ParentRoles ON main.PrincipalId = ParentRoles.id JOIN $inner_table InnerRecords ON (ParentRoles.Domain = '$parent_class-Role' AND InnerRecords.$parent_column = ParentRoles.Instance) OR ParentRoles.Domain = 'RT::System-Role' - JOIN Groups InnerRoles + JOIN $groups_table InnerRoles ON InnerRoles.Instance = InnerRecords.Id AND InnerRoles.Name = main.PrincipalType ]; From b430130e6223895392cef285a5b865a0fea5ad1f Mon Sep 17 00:00:00 2001 From: Craig Kaiser Date: Wed, 4 Dec 2024 10:59:24 -0500 Subject: [PATCH 22/26] Add /Admin/Global/RightsHistory.html page --- share/html/Admin/Global/RightsHistory.html | 99 ++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 share/html/Admin/Global/RightsHistory.html diff --git a/share/html/Admin/Global/RightsHistory.html b/share/html/Admin/Global/RightsHistory.html new file mode 100644 index 00000000000..0f580bd21b7 --- /dev/null +++ b/share/html/Admin/Global/RightsHistory.html @@ -0,0 +1,99 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2024 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<& /Admin/Elements/Header, Title => $title &> +<& /Elements/Tabs &> + +<& /Elements/ShowHistory, + Object => $RT::System, + Transactions => $transactions, + ShowDisplayModes => 0, + DisplayPath => 'RightsHistory.html', +&> + +<%INIT> +unless ( $Type ) { + Abort( loc("No Type specified") ); +} + +my $title = loc("History for global [_1] rights", lc($Type)); + +# Create a new Transactions object +my $transactions = RT::Transactions->new( $session{CurrentUser} ); + +# Limit transactions to GrantRight or RevokeRight +$transactions->Limit( FIELD => 'Type', VALUE => [ 'GrantRight', 'RevokeRight' ], OPERATOR => 'IN' ); +$transactions->Limit( FIELD => 'ObjectType', VALUE => 'RT::System' ); + +my $expression = 'main.Field'; +if ( RT->Config->Get( 'DatabaseType' ) eq 'Pg' ) { + $expression = 'CAST(main.Field AS INTEGER)'; +} + +my $groups_alias = $transactions->Join( + EXPRESSION => $expression, + TABLE2 => 'Groups', + FIELD2 => 'id' +); + +my $operator = $Type eq 'Groups' ? '!=' : '='; +$transactions->Limit( + ALIAS => $groups_alias, + FIELD => 'Domain', + OPERATOR => $operator, + VALUE => 'ACLEquivalence', + CASESENSITIVE => 0, +); + +$transactions->OrderBy( + FIELD => RT->Config->Get('TransactionDefaultSearchResultOrderBy')->{'RT::ACE'} || "Created", + ORDER => RT->Config->Get('TransactionDefaultSearchResultOrder')->{'RT::ACE'} || "ASC" +); + +<%ARGS> +$Type => '' + From 248899535b7c2eb8d5141dbe3838a3038c9cb61d Mon Sep 17 00:00:00 2001 From: Craig Kaiser Date: Wed, 4 Dec 2024 11:05:50 -0500 Subject: [PATCH 23/26] Add menu page options for global rights changes history --- lib/RT/Interface/Web/MenuBuilder.pm | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm index f1ce9c17ac1..aba50204c8a 100644 --- a/lib/RT/Interface/Web/MenuBuilder.pm +++ b/lib/RT/Interface/Web/MenuBuilder.pm @@ -1699,6 +1699,27 @@ sub _BuildAdminMenu { $page->child( create => title => loc('Create'), path => "/Admin/Articles/Classes/Modify.html?Create=1" ); } } + + # Define a mapping for Rights and History paths + my %rights_pages = ( + 'Groups' => 'GroupRights.html', + 'Users' => 'UserRights.html', + ); + + # Match request paths for Rights and History pages + if ( $request_path =~ m{^/Admin/Global/(GroupRights|UserRights)\.html$} ) { + my $type = $1 eq 'GroupRights' ? 'Groups' : 'Users'; + $page->child( rights => title => loc('Rights'), path => "/Admin/Global/$1.html" ); + $page->child( history => title => loc('History'), path => "/Admin/Global/RightsHistory.html?Type=$type" ); + } + elsif ( $request_path =~ m{^/Admin/Global/RightsHistory\.html} ) { + # Extract type from request arguments + if ( my $type = $HTML::Mason::Commands::DECODED_ARGS->{'Type'} ) { + my $rights_page = $rights_pages{$type} || 'UserRights.html'; # Default to UserRights + $page->child( rights => title => loc('Rights'), path => "/Admin/Global/$rights_page" ); + $page->child( history => title => loc('History'), path => "/Admin/Global/RightsHistory.html?Type=$type" ); + } + } } sub BuildSelfServiceNav { From fceefdc72867fa6f53e1261846a36acdf022035f Mon Sep 17 00:00:00 2001 From: sunnavy Date: Tue, 10 Dec 2024 17:27:38 -0500 Subject: [PATCH 24/26] Support empty DisplayPath to indicate current URL This is initially for RightsHistory page, where we have specific Type argument and do not want Object argument in URL. --- share/html/Admin/Global/RightsHistory.html | 2 +- share/html/Elements/ShowTransaction | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/share/html/Admin/Global/RightsHistory.html b/share/html/Admin/Global/RightsHistory.html index 0f580bd21b7..62819792ffa 100644 --- a/share/html/Admin/Global/RightsHistory.html +++ b/share/html/Admin/Global/RightsHistory.html @@ -52,7 +52,7 @@ Object => $RT::System, Transactions => $transactions, ShowDisplayModes => 0, - DisplayPath => 'RightsHistory.html', + DisplayPath => '', # Current URL &> <%INIT> diff --git a/share/html/Elements/ShowTransaction b/share/html/Elements/ShowTransaction index e604ae3daaf..a9e65865952 100644 --- a/share/html/Elements/ShowTransaction +++ b/share/html/Elements/ShowTransaction @@ -52,8 +52,12 @@ % } path_info =~ m{^/SelfService/} ) { href="<% RT->Config->Get('WebPath') %>/SelfService/Transaction/Display.html?id=<% $Transaction->id %>" \ % } else { From 309c8fa94302d0bf2c26f2d16052f5483353ff19 Mon Sep 17 00:00:00 2001 From: sunnavy Date: Tue, 10 Dec 2024 17:31:18 -0500 Subject: [PATCH 25/26] Support to specify ReverseHistoryOrderLink in history menu On RightsHistory page, the transactions are already specifically sorted and we do not want the general reverse order($Object->SortedTransactions) in ShowHistoryPage to get involved, which returns all transactions of the object. --- share/html/Admin/Global/RightsHistory.html | 5 +++- share/html/Elements/ShowHistoryHeader | 31 +++++++++++----------- share/html/Elements/ShowHistoryPage | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/share/html/Admin/Global/RightsHistory.html b/share/html/Admin/Global/RightsHistory.html index 62819792ffa..3200beb44c7 100644 --- a/share/html/Admin/Global/RightsHistory.html +++ b/share/html/Admin/Global/RightsHistory.html @@ -53,6 +53,8 @@ Transactions => $transactions, ShowDisplayModes => 0, DisplayPath => '', # Current URL + ReverseTxns => '', # Force to ignore ReverseTxns parameter + ReverseHistoryOrderLink => '?' . QueryString( Type => $Type, Order => uc $Order eq 'ASC' ? 'DESC' : 'ASC' ), &> <%INIT> @@ -91,9 +93,10 @@ $transactions->OrderBy( FIELD => RT->Config->Get('TransactionDefaultSearchResultOrderBy')->{'RT::ACE'} || "Created", - ORDER => RT->Config->Get('TransactionDefaultSearchResultOrder')->{'RT::ACE'} || "ASC" + ORDER => $Order, ); <%ARGS> $Type => '' +$Order => RT->Config->Get('TransactionDefaultSearchResultOrder')->{'RT::ACE'} || "ASC" diff --git a/share/html/Elements/ShowHistoryHeader b/share/html/Elements/ShowHistoryHeader index d07dc65f590..92fd9704278 100644 --- a/share/html/Elements/ShowHistoryHeader +++ b/share/html/Elements/ShowHistoryHeader @@ -57,17 +57,6 @@ $Title => loc('History') <%INIT> my $record_type = $Object->RecordType; my $histid = "\L$record_type\E-" . $Object->id . "-history"; - -my $reverse_txns = $ARGS{'ReverseTxns'} || $DECODED_ARGS->{ReverseTxns}; -if ( $reverse_txns ) { - # If we got something, reverse it for the link - $reverse_txns = $reverse_txns eq 'ASC' ? 'DESC' : 'ASC'; -} -else { - # Default the link to the opposite of the config setting - # Oldest Txns first is ASC, so reverse it for this option default - $reverse_txns = RT->Config->Get("OldestTransactionsFirst", $session{'CurrentUser'}) ? 'DESC' : 'ASC'; -}
<%perl> @@ -107,10 +96,22 @@ if ( $ShowDisplayModes or $ShowTitle or $ScrollShowHistory ) { # Don't need to reverse history when showing a single transaction unless ( $SingleTransaction ) { - push( @elements, qq{} . - loc("Reverse history order") . - qq{} ); + my $href = $ARGS{ReverseHistoryOrderLink} || do { + my $reverse_txns = $ARGS{'ReverseTxns'} // $DECODED_ARGS->{ReverseTxns}; + if ($reverse_txns) { + + # If we got something, reverse it for the link + $reverse_txns = $reverse_txns eq 'ASC' ? 'DESC' : 'ASC'; + } + else { + # Default the link to the opposite of the config setting + # Oldest Txns first is ASC, so reverse it for this option default + $reverse_txns + = RT->Config->Get( "OldestTransactionsFirst", $session{'CurrentUser'} ) ? 'DESC' : 'ASC'; + } + qq{?ForceShowHistory=1;ReverseTxns=$reverse_txns;id=} . $Object->id; + }; + push @elements, qq{} . loc("Reverse history order") . qq{}; } my $titleright; diff --git a/share/html/Elements/ShowHistoryPage b/share/html/Elements/ShowHistoryPage index c376c0f4744..cacceb86e09 100644 --- a/share/html/Elements/ShowHistoryPage +++ b/share/html/Elements/ShowHistoryPage @@ -61,7 +61,7 @@ $m->callback( CallbackName => 'Initial', ARGSRef => \%ARGS, Object => $Object, T my $trans_content = {}; my $trans_attachments = {}; -$ARGS{'ReverseTxns'} ||= $DECODED_ARGS->{ReverseTxns}; +$ARGS{'ReverseTxns'} //= $DECODED_ARGS->{ReverseTxns}; if ( $ARGS{'ReverseTxns'} ) { $Transactions = $Object->SortedTransactions($ARGS{'ReverseTxns'}); } From d18adc562072957a9381d7fb914dd7c8c70266d4 Mon Sep 17 00:00:00 2001 From: Matt Zagrabelny Date: Tue, 17 Dec 2024 14:24:35 -0600 Subject: [PATCH 26/26] Add name attribute to Create New Ticket button Add name attribute so Callbacks can divine whether the button was clicked upon form submission. --- share/html/Elements/CreateTicket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/html/Elements/CreateTicket b/share/html/Elements/CreateTicket index 4e5c56f74ca..957ceac2a74 100644 --- a/share/html/Elements/CreateTicket +++ b/share/html/Elements/CreateTicket @@ -53,7 +53,7 @@

<&|/l&>Select a queue for your new ticket.

% } -% my $button_start = ''; % my $queue_selector = $m->scomp('/Elements/SelectNewTicketQueue', AutoSubmit => 1, SendTo => $SendTo, Placeholder => loc('Queue'), Hyperlink => $Hyperlink );