Mailing List Archive

rt branch, 4.6/core-group-management-extensions, created. rt-4.4.4-78-g130a70cf8
The branch, 4.6/core-group-management-extensions has been created
at 130a70cf8ad09ea4d76a3134d28d621e2af232fb (commit)

- Log -----------------------------------------------------------------
commit daf81d1bed7a4cbe132ae9127fc4b874bb1cfa4c
Author: Blaine Motsinger <blaine@bestpractical.com>
Date: Thu May 23 09:58:52 2019 -0500

Core RT-Extension-GroupLinks

diff --git a/lib/RT/Group.pm b/lib/RT/Group.pm
index 19a41ef7a..b2877d132 100644
--- a/lib/RT/Group.pm
+++ b/lib/RT/Group.pm
@@ -74,7 +74,8 @@ use warnings;
use base 'RT::Record';

use Role::Basic 'with';
-with "RT::Record::Role::Rights";
+with "RT::Record::Role::Rights",
+ "RT::Record::Role::Links";

sub Table {'Groups'}

diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm
index 5e9fbed9c..754cf8deb 100644
--- a/lib/RT/Interface/Web/MenuBuilder.pm
+++ b/lib/RT/Interface/Web/MenuBuilder.pm
@@ -2,7 +2,7 @@
#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -1077,9 +1077,19 @@ sub _BuildAdminMenu {
$page->child( basics => title => loc('Basics'), path => "/Admin/Groups/Modify.html?id=" . $obj->id );
$page->child( members => title => loc('Members'), path => "/Admin/Groups/Members.html?id=" . $obj->id );
$page->child( memberships => title => loc('Memberships'), path => "/Admin/Groups/Memberships.html?id=" . $obj->id );
+ $page->child( 'links' =>
+ title => loc("Links"),
+ path => "/Admin/Groups/ModifyLinks.html?id=" . $obj->id,
+ description => loc("Group links"),
+ );
$page->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/Groups/GroupRights.html?id=" . $obj->id );
$page->child( 'user-rights' => title => loc('User Rights'), path => "/Admin/Groups/UserRights.html?id=" . $obj->id );
$page->child( history => title => loc('History'), path => "/Admin/Groups/History.html?id=" . $obj->id );
+ $page->child( 'summary' =>
+ title => loc("Group Summary"),
+ path => "/Group/Summary.html?id=" . $obj->id,
+ description => loc("Group summary page"),
+ );
}
}
}
diff --git a/share/html/Elements/AddLinks b/share/html/Admin/Elements/AddLinks
similarity index 61%
copy from share/html/Elements/AddLinks
copy to share/html/Admin/Elements/AddLinks
index 30eb0e8bc..9948ffa5b 100644
--- a/share/html/Elements/AddLinks
+++ b/share/html/Admin/Elements/AddLinks
@@ -55,44 +55,23 @@ my $id = ($Object and $Object->id)
? $Object->id
: "new";

-my $exclude = qq| data-autocomplete="Tickets" data-autocomplete-multiple="1"|;
+my $exclude = qq| data-autocomplete="Groups" data-autocomplete-multiple="1" |;
$exclude .= qq| data-autocomplete-exclude="$id"| if $Object->id;
</%init>
-% if (ref($Object) eq 'RT::Ticket') {
-<i><&|/l&>Enter tickets or URIs to link tickets to. Separate multiple entries with spaces.</&>
-<br /><&|/l&>You may enter links to Articles as "a:###", where ### represents the number of the Article.</&>
-<br /><&|/l&>Enter links to assets as "asset:###", where ### represents the asset ID.</&>
+% if (ref($Object) eq 'RT::Group') {
+<i><&|/l&>Enter names or IDs of other groups to link. Start typing a group name to see matching groups. Separate multiple entries with a comma.</&>
% $m->callback( CallbackName => 'ExtraLinkInstructions' );
</i><br />
-% } elsif (ref($Object) eq 'RT::Queue') {
-<i><&|/l&>Enter queues or URIs to link queues to. Separate multiple entries with spaces.</&>
-</i><br />
% } else {
<i><&|/l&>Enter objects or URIs to link objects to. Separate multiple entries with spaces.</&></i><br />
% }
<table>
<tr>
- <td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Depends on').':', Relation => 'DependsOn' &></td>
- <td class="entry"><input name="<%$id%>-DependsOn" value="<% $ARGSRef->{"$id-DependsOn"} || '' %>" <% $exclude |n%>/></td>
- </tr>
- <tr>
- <td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Depended on by').':', Relation => 'DependedOnBy' &></td>
- <td class="entry"><input name="DependsOn-<%$id%>" value="<% $ARGSRef->{"DependsOn-$id"} || '' %>" <% $exclude |n%>/></td>
- </tr>
- <tr>
- <td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Parents').':', Relation => 'Parents' &></td>
- <td class="entry"><input name="<%$id%>-MemberOf" value="<% $ARGSRef->{"$id-MemberOf"} || '' %>" <% $exclude |n%>/></td>
- </tr>
- <tr>
- <td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Children').':', Relation => 'Children' &></td>
- <td class="entry"> <input name="MemberOf-<%$id%>" value="<% $ARGSRef->{"MemberOf-$id"} || '' %>" <% $exclude |n%>/></td>
- </tr>
- <tr>
- <td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Refers to').':', Relation => 'RefersTo' &></td>
+ <td class="label"><& /Elements/ShowRelationLabel, Object => $Object, Label => loc('Links to').':', Relation => 'RefersTo' &></td>
<td class="entry"><input name="<%$id%>-RefersTo" value="<% $ARGSRef->{"$id-RefersTo"} || '' %>" <% $exclude |n%>/></td>
</tr>
<tr>
- <td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Referred to by').':', Relation => 'ReferredToBy' &></td>
+ <td class="label"><& /Elements/ShowRelationLabel, Object => $Object, Label => loc('Linked to by').':', Relation => 'ReferredToBy' &></td>
<td class="entry"> <input name="RefersTo-<%$id%>" value="<% $ARGSRef->{"RefersTo-$id"} || '' %>" <% $exclude |n%>/></td>
</tr>
<& /Elements/EditCustomFields,
diff --git a/share/html/Elements/ShowPrincipal b/share/html/Admin/Elements/EditLinks
similarity index 66%
copy from share/html/Elements/ShowPrincipal
copy to share/html/Admin/Elements/EditLinks
index f8d1e0ed1..4671f3ebe 100644
--- a/share/html/Elements/ShowPrincipal
+++ b/share/html/Admin/Elements/EditLinks
@@ -45,28 +45,41 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-%# Released under the terms of version 2 of the GNU Public License
-<%args>
-$Object
-$PostUser => undef
-$Separator => ", "
-$Link => 1
-</%args>
-<%init>
-if ($Object->isa("RT::Group")) {
- # Link the users (non-recursively)
- my @ret = map {$m->scomp("ShowPrincipal", Object => $_->[1], PostUser => $PostUser, Link => $Link)}
- sort {$a->[0] cmp $b->[0]}
- map {+[($_->EmailAddress||''), $_]}
- @{ $Object->UserMembersObj( Recursively => 0 )->ItemsArrayRef };
+<table width="100%">
+ <tr>
+ <td valign="top" width="50%">
+ <h3><&|/l&>Current Links</&></h3>

- # But don't link the groups
- push @ret, sort map {$m->interp->apply_escapes( loc("Group: [_1]", $_->Name), 'h' )}
- @{ $Object->GroupMembersObj( Recursively => 0)->ItemsArrayRef };
+<table>
+ <tr>
+ <td class="labeltop"><& /Elements/ShowRelationLabel, Object => $Object, Label => loc('Links to').':', Relation => 'RefersTo' &></td>
+ <td class="value">
+% while (my $link = $Object->RefersTo->Next) {
+ <& /Elements/EditLink, Link => $link, Mode => 'Target' &>
+%}
+ </td>
+ </tr>
+ <tr>
+ <td class="labeltop group-link edit-referredtoby"><& /Elements/ShowRelationLabel, Object => $Object, Label => loc('Linked to by').':', Relation => 'ReferredToBy' &></td>
+ <td class="value group-link edit-referredtoby">
+% while (my $link = $Object->ReferredToBy->Next) {
+ <& /Elements/EditLink, Link => $link, Mode => 'Base' &>
+% }
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td><i><&|/l&>(Check box to remove link)</&></i></td>
+ </tr>
+</table>

- $m->out( join($Separator, @ret) );
-} else {
- $m->comp("/Elements/ShowUser", User => $Object, Link => $Link);
- $m->out( $PostUser->($Object) ) if $PostUser;
-}
-</%init>
+</td>
+<td valign="top">
+<h3><&|/l&>New Links</&></h3>
+<& AddLinks, %ARGS &>
+</td>
+</tr>
+</table>
+<%ARGS>
+$Object => undef
+</%ARGS>
diff --git a/share/html/Admin/Groups/ModifyLinks.html b/share/html/Admin/Groups/ModifyLinks.html
new file mode 100644
index 000000000..77a44d57d
--- /dev/null
+++ b/share/html/Admin/Groups/ModifyLinks.html
@@ -0,0 +1,116 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<& /Admin/Elements/Header, Title => $title &>
+
+<& /Elements/Tabs &>
+<& /Elements/ListActions, actions => \@results &>
+
+% $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, GroupObj => $Group);
+<& /Elements/ListActions, actions => \@results &>
+
+<form action="<% RT->Config->Get('WebPath') %>/Admin/Groups/ModifyLinks.html" name="ModifyLinks" method="post">
+<input type="hidden" class="hidden" name="id" value="<%$Group->Id%>" />
+
+% $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS );
+
+<&| /Widgets/TitleBox, title => loc('Manage Links for Group [_1]', $Group->Label) &>
+
+<& /Admin/Elements/EditLinks, Object => $Group &>
+</&>
+
+<& /Elements/Submit, Name => 'SubmitGroupLinks', Label => loc('Save Changes') &>
+</form>
+
+% $m->callback(CallbackName => 'AfterForm', ARGSRef => \%ARGS, GroupObj => $Group);
+
+<%INIT>
+my $Group = RT::Group->new($session{'CurrentUser'});
+$Group->Load($id) || Abort(loc('Could not load group'));
+my @results;
+
+$m->callback(CallbackName => 'Init', GroupObj => $Group, ARGSRef => \%ARGS, Results => \@results);
+
+my $title = loc("Modify Links for group [_1]", $Group->Label);
+
+if ( $ARGS{'SubmitGroupLinks'} ){
+
+ foreach my $link_type ( "RefersTo-$id", "$id-RefersTo" ){
+ next unless $ARGS{$link_type};
+
+ # List is comma delimited, which allows for group names with spaces
+ my @values = split ', ', $ARGS{$link_type};
+ foreach my $input ( @values ) {
+ if ( $input =~ /^\d+$/ ){
+ # Default scheme for link ids assumes a ticket. Since we're on the group
+ # links page, allow ids as input and prepend 'group:' here to
+ # create group links
+ $input = 'group:' . $input;
+ next;
+ }
+ else {
+ # Could be a group name. Try to look it up.
+ my $group = RT::Group->new($session{'CurrentUser'});
+ my ($ret, $msg) = $group->LoadUserDefinedGroup($input);
+ RT::Logger->info("Unable to load group from name $input: $msg") unless $ret;
+ $input = 'group:' . $group->Id if $ret and $group->Id;
+ }
+ }
+ $ARGS{$link_type} = join ' ', @values;
+ }
+
+ (@results) = ProcessRecordLinks(RecordObj => $Group, ARGSRef => \%ARGS);
+
+ MaybeRedirectForResults(
+ Actions => \@results,
+ Arguments => { id => $id },
+ );
+}
+</%INIT>
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/share/html/Elements/AddLinks b/share/html/Elements/AddLinks
index 30eb0e8bc..eb8787e9e 100644
--- a/share/html/Elements/AddLinks
+++ b/share/html/Elements/AddLinks
@@ -62,6 +62,8 @@ $exclude .= qq| data-autocomplete-exclude="$id"| if $Object->id;
<i><&|/l&>Enter tickets or URIs to link tickets to. Separate multiple entries with spaces.</&>
<br /><&|/l&>You may enter links to Articles as "a:###", where ### represents the number of the Article.</&>
<br /><&|/l&>Enter links to assets as "asset:###", where ### represents the asset ID.</&>
+<br /><&|/l&>Enter links to groups as "group:###", where ### represents the group ID.</&>
+<br /><&|/l&>Enter links to users as "user:###", where ### represents the user ID.</&>
% $m->callback( CallbackName => 'ExtraLinkInstructions' );
</i><br />
% } elsif (ref($Object) eq 'RT::Queue') {
diff --git a/share/html/Elements/ShowPrincipal b/share/html/Elements/ShowPrincipal
index f8d1e0ed1..329266512 100644
--- a/share/html/Elements/ShowPrincipal
+++ b/share/html/Elements/ShowPrincipal
@@ -60,8 +60,9 @@ if ($Object->isa("RT::Group")) {
map {+[($_->EmailAddress||''), $_]}
@{ $Object->UserMembersObj( Recursively => 0 )->ItemsArrayRef };

- # But don't link the groups
- push @ret, sort map {$m->interp->apply_escapes( loc("Group: [_1]", $_->Name), 'h' )}
+ # Link to the group summary page
+ my $href = RT->Config->Get("WebPath") . "/Group/Summary.html?id=";
+ push @ret, sort map { "<a href=\"" . $href . $_->id . "\">" . loc("Group:") . " " . $_->Name . "</a>" }
@{ $Object->GroupMembersObj( Recursively => 0)->ItemsArrayRef };

$m->out( join($Separator, @ret) );

commit 0ff9b75cb02416ac27d22e1bd2390dd918766454
Author: Blaine Motsinger <blaine@bestpractical.com>
Date: Wed May 29 13:16:08 2019 -0500

Core RT-Extension-GroupSummary

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 2559c662b..0d3bfb373 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1310,7 +1310,7 @@ user's customized homepage ("RT at a glance").
Set(
$HomepageComponents,
[.
- qw(QuickCreate QueueList MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards SavedSearches FindUser MyAssets FindAsset) # loc_qw
+ qw(QuickCreate QueueList MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards SavedSearches FindUser MyAssets FindAsset FindGroup) # loc_qw
]
);

@@ -1768,6 +1768,97 @@ Set($HideUnsetFieldsOnDisplay, 0);

=back

+=head2 Group Summary Configuration
+
+Below are configuration options for the Group Summary page.
+
+=over
+
+=item C<$GroupSearchResultFormat>
+
+This controls the display of lists of groups returned from the Group
+Summary Search. The display of groups in the Admin interface is
+controlled by C<%AdminSearchResultFormat>.
+
+=cut
+
+Set($GroupSearchResultFormat,
+ q{ '<a href="__WebPath__/Group/Summary.html?id=__id__">__id__</a>/TITLE:#'}
+ .q{,'<a href="__WebPath__/Group/Summary.html?id=__id__">__Name__</a>/TITLE:Name'}
+);
+
+=item C<@GroupSummaryPortlets>
+
+A list of portlets to be displayed on the Group Summary page.
+By default, we show all of the available portlets.
+Extensions may provide their own portlets for this page.
+
+=cut
+
+Set(@GroupSummaryPortlets, (qw/ExtraInfo CreateTicket ActiveTickets InactiveTickets GroupAssets /));
+
+=item C<$GroupSummaryExtraInfo>
+
+This controls what information is displayed on the Group Summary
+portal. By default the group Name and Description are displayed.
+
+=cut
+
+Set($GroupSummaryExtraInfo, "id, Name, Description");
+
+=item C<$GroupSummaryTicketListFormat>
+
+Control the appearance of the Active and Inactive ticket lists in the
+Group Summary.
+
+=cut
+
+Set($GroupSummaryTicketListFormat, q{
+ '<B><A HREF="__WebPath__/Ticket/Display.html?id=__id__">__id__</a></B>/TITLE:#',
+ '<B><A HREF="__WebPath__/Ticket/Display.html?id=__id__">__Subject__</a></B>/TITLE:Subject',
+ Status,
+ QueueName,
+ Owner,
+ Priority,
+ '__NEWLINE__',
+ '',
+ '<small>__Requestors__</small>',
+ '<small>__CreatedRelative__</small>',
+ '<small>__ToldRelative__</small>',
+ '<small>__LastUpdatedRelative__</small>',
+ '<small>__TimeLeft__</small>'
+});
+
+=item C<$GroupSearchFields>
+
+Specifies which fields of L<RT::Group> to match against and how to match
+each field when performing a quick search on groups. Valid match
+methods are LIKE, STARTSWITH, ENDSWITH, =, and !=. Valid search fields
+are id, Name, Description, or custom fields, which are specified as
+"CF.1234" or "CF.Name"
+
+=cut
+
+Set($GroupSearchFields, {
+ id => '=',
+ Name => 'LIKE',
+ Description => 'LIKE',
+});
+
+=item C<$AllowGroupAutocompleteForUnprivileged>
+
+Defines whether unprivileged users (users of SelfService) are allowed
+to autocomplete groups when searching. Setting this option to 1 means
+unprivileged users will be able to search all your user created
+group names. Users will also need the SeeGroup privilege to use
+this feature.
+
+=cut
+
+Set($AllowGroupAutocompleteForUnprivileged, 0);
+
+=back
+
=head2 Self Service Interface

The Self Service Interface is a view automatically presented to Unprivileged
@@ -1893,7 +1984,6 @@ Users also need the ModifySelf right to have access to this page.

Set( $SelfServiceDownloadUserData, 0 );

-
=back

=head2 Articles
diff --git a/lib/RT/Groups.pm b/lib/RT/Groups.pm
index 0d9784bc6..ae80e0e53 100644
--- a/lib/RT/Groups.pm
+++ b/lib/RT/Groups.pm
@@ -433,6 +433,110 @@ sub _DoSearch {

}

+=head2 SimpleSearch
+
+Does a 'simple' search of Groups against a specified Term.
+
+This Term is compared to a number of fields using various types of SQL
+comparison operators.
+
+Ensures that the returned collection of Groups will have a value for Return.
+
+This method is passed the following. You must specify a Term and a Return.
+
+ Fields - Hashref of data - defaults to C<$GroupSearchFields> emulate that if you want to override
+ Term - String that is in the fields specified by Fields
+ Return - What field on the User you want to be sure isn't empty
+ Exclude - Array reference of ids to exclude
+ Max - Size to limit this collection to
+
+=cut
+
+sub SimpleSearch {
+ my $self = shift;
+ my %args = (
+ Fields => RT->Config->Get('GroupSearchFields'),
+ Term => undef,
+ Exclude => [],
+ Return => 'Name',
+ Max => 10,
+ @_
+ );
+
+ return $self unless defined $args{Return}
+ and defined $args{Term}
+ and length $args{Term};
+
+ $self->RowsPerPage( $args{Max} );
+
+ $self->LimitToUserDefinedGroups();
+
+ while (my ($name, $op) = each %{$args{Fields}}) {
+ $op = 'STARTSWITH'
+ unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i;
+
+ if ($name =~ /^CF\.(?:\{(.*)}|(.*))$/) {
+ my $cfname = $1 || $2;
+ my $cf = RT::CustomField->new(RT->SystemUser);
+ my ($ok, $msg) = $cf->LoadByName( Name => $cfname, LookupType => 'RT::Group');
+ if ( $ok ) {
+ $self->LimitCustomField(
+ CUSTOMFIELD => $cf->Id,
+ OPERATOR => $op,
+ VALUE => $args{Term},
+ ENTRYAGGREGATOR => 'OR',
+ SUBCLAUSE => 'autocomplete',
+ CASESENSITIVE => 0,
+ );
+ } else {
+ RT->Logger->warning("Asked to search custom field $name but unable to load a Group CF with the name $cfname: $msg");
+ }
+ } elsif ($name eq 'id' and $op =~ /(?:LIKE|(?:START|END)SWITH)$/i) {
+ $self->Limit(
+ FUNCTION => "CAST( main.$name AS TEXT )",
+ OPERATOR => $op,
+ VALUE => $args{Term},
+ ENTRYAGGREGATOR => 'OR',
+ SUBCLAUSE => 'autocomplete',
+ CASESENSITIVE => 0,
+ ) if $args{Term} =~ /^\d+$/;
+ } else {
+ $self->Limit(
+ FIELD => $name,
+ OPERATOR => $op,
+ VALUE => $args{Term},
+ ENTRYAGGREGATOR => 'OR',
+ SUBCLAUSE => 'autocomplete',
+ CASESENSITIVE => 0,
+ ) unless $args{Term} =~ /\D/ and $name eq 'id';
+ }
+ }
+
+ # Exclude groups we don't want
+ $self->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => $args{Exclude} )
+ if @{$args{Exclude}};
+
+ if ( RT->Config->Get('DatabaseType') eq 'Oracle' ) {
+ $self->Limit(
+ FIELD => $args{Return},
+ OPERATOR => 'IS NOT',
+ VALUE => 'NULL',
+ );
+ }
+ else {
+ $self->Limit( FIELD => $args{Return}, OPERATOR => '!=', VALUE => '', CASESENSITIVE => 0, );
+ $self->Limit(
+ FIELD => $args{Return},
+ OPERATOR => 'IS NOT',
+ VALUE => 'NULL',
+ ENTRYAGGREGATOR => 'AND',
+ CASESENSITIVE => 0,
+ );
+ }
+
+ return $self;
+}
+
RT::Base->_ImportOverlays();

1;
diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm
index 754cf8deb..a9734ed9f 100644
--- a/lib/RT/Interface/Web/MenuBuilder.pm
+++ b/lib/RT/Interface/Web/MenuBuilder.pm
@@ -167,6 +167,12 @@ sub BuildMainNav {

$search->child( users => title => loc('Users'), path => "/User/Search.html" );

+ $search->child( groups =>
+ title => loc('Groups'),
+ path => "/Group/Search.html",
+ description => 'Group search'
+ );
+
$search->child( assets => title => loc("Assets"), path => "/Asset/Search/" )
if $current_user->HasRight( Right => 'ShowAssetsMenu', Object => RT->System );

@@ -1067,7 +1073,7 @@ sub _BuildAdminMenu {

}

- if ( $request_path =~ m{^/Admin/Groups} ) {
+ if ( $request_path =~ m{^(/Admin/Groups|/Group/(Summary|History)\.html)} ) {
if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) {
my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'};
my $obj = RT::Group->new( $current_user );
diff --git a/share/html/Elements/FindGroup b/share/html/Elements/FindGroup
new file mode 100644
index 000000000..d5c0eeaac
--- /dev/null
+++ b/share/html/Elements/FindGroup
@@ -0,0 +1,50 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<&|/Widgets/TitleBox, title => loc('Find a group')&>
+<& /Elements/GotoGroup &>
+</&>
diff --git a/share/html/Elements/GotoGroup b/share/html/Elements/GotoGroup
new file mode 100644
index 000000000..6c84066de
--- /dev/null
+++ b/share/html/Elements/GotoGroup
@@ -0,0 +1,62 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<form name="GroupSearch" method="post" action="<% RT->Config->Get('WebPath') %>/Group/Search.html">
+<input type="text" name="GroupString" value="<% $Default %>" data-autocomplete="Groups" data-autocomplete-return="Name" id="autocomplete-GroupString" />
+<script type="text/javascript">
+jQuery(function(){
+ // Jump directly to the page if a group is chosen
+ jQuery("#autocomplete-GroupString").on("autocompleteselect", function( event, ui ) {
+ document.location = RT.Config.WebPath + "/Group/Summary.html?id=" + ui.item.id;
+ });
+});
+</script>
+<input type="submit" name="GroupSearch" value="<&|/l&>Search</&>" class="button" />
+</form>
+<%ARGS>
+$Default => ''
+</%ARGS>
diff --git a/share/html/Group/Elements/AssetList b/share/html/Group/Elements/AssetList
new file mode 100644
index 000000000..15a4f826c
--- /dev/null
+++ b/share/html/Group/Elements/AssetList
@@ -0,0 +1,80 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%init>
+my $assets = RT::Assets->new($session{CurrentUser});
+$m->callback( CallbackName => 'ModifyAssetSearch', %ARGS, Assets => $assets, Roles => \@Roles );
+for my $role (@Roles) {
+ $assets->RoleLimit(
+ TYPE => $role,
+ VALUE => $Group->PrincipalId,
+ SUBCLAUSE => "Role$role",
+ );
+}
+my $Format = q[
+ '<b><a href="__WebHomePath__/Asset/Display.html?id=__id__">__id__</a></b>/TITLE:#',
+ '<b><a href="__WebHomePath__/Asset/Display.html?id=__id__">__Name__</a></b>/TITLE:Name',
+ Description,
+];
+$m->callback( CallbackName => 'ModifyFormat', %ARGS, Format => \$Format );
+</%init>
+<&| /Widgets/TitleBox, title => $Title, class => "group asset-list" &>
+ <& /Elements/CollectionList,
+ Collection => $assets,
+ OrderBy => 'id',
+ Order => 'ASC',
+ Format => $Format,
+ AllowSorting => 0,
+ HasResults => $HasResults,
+ &>
+</&>
+<%args>
+$Group
+$Title
+@Roles
+$HasResults => undef
+</%args>
diff --git a/share/html/Group/Elements/GroupInfo b/share/html/Group/Elements/GroupInfo
new file mode 100644
index 000000000..3ec06e4f5
--- /dev/null
+++ b/share/html/Group/Elements/GroupInfo
@@ -0,0 +1,64 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<& /Elements/ShowRecord,
+ Object => $Group,
+ Format => $format,
+ TrustFormat => 1, # Only modifiable by the RT server admin, so no need to scrub.
+ Class => "$ClassPrefix-extra",
+ &>
+<%INIT>
+return unless blessed($Group) and $Group->id;
+return unless $FormatConfig;
+my $format = RT->Config->Get($FormatConfig);
+return unless $format;
+</%INIT>
+<%ARGS>
+$Group => undef
+$FormatConfig => undef
+$ClassPrefix => undef
+</%ARGS>
diff --git a/share/html/Group/Elements/Portlets/ActiveTickets b/share/html/Group/Elements/Portlets/ActiveTickets
new file mode 100644
index 000000000..0cf5317ec
--- /dev/null
+++ b/share/html/Group/Elements/Portlets/ActiveTickets
@@ -0,0 +1,68 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<& /Group/Elements/TicketList ,
+ Group => $Group,
+ conditions => $conditions,
+ Rows => $Rows,
+ WatcherTypes => [qw(Watcher)],
+ Class => "group active-tickets",
+ Title => loc('Active Tickets'),
+ TitleBox => 1,
+ ShowHeader => 1,
+ Format => RT->Config->Get('GroupSummaryTicketListFormat'),
+&>
+<%INIT>
+unless ( @$conditions ) {
+ push @$conditions, { cond => "Status = '__Active__'" };
+}
+</%INIT>
+<%ARGS>
+$Group => undef
+$conditions => []
+$Rows => 10
+</%ARGS>
diff --git a/share/html/Group/Elements/Portlets/CreateTicket b/share/html/Group/Elements/Portlets/CreateTicket
new file mode 100644
index 000000000..cecc09674
--- /dev/null
+++ b/share/html/Group/Elements/Portlets/CreateTicket
@@ -0,0 +1,58 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<&| /Widgets/TitleBox, title => loc('Quick ticket creation'), class => "group create-ticket" &>
+<form action="<%RT->Config->Get('WebPath')%>/Ticket/Create.html">
+<&|/l&>Create a ticket with this group as Cc in Queue</&>
+<input type="hidden" name="AddGroupCc" value="<%$Group->Id%>">
+<& /Elements/SelectNewTicketQueue &>
+<input type="submit" value="<&|/l&>Create</&>">
+</form>
+</&>
+<%ARGS>
+$Group
+</%ARGS>
diff --git a/share/html/Group/Elements/Portlets/ExtraInfo b/share/html/Group/Elements/Portlets/ExtraInfo
new file mode 100644
index 000000000..c065ccaea
--- /dev/null
+++ b/share/html/Group/Elements/Portlets/ExtraInfo
@@ -0,0 +1,56 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<&| /Widgets/TitleBox, title => loc('Group Information'), class => "group extra-info" &>
+
+% $m->callback( Group => $Group, CallbackName => 'BeforeExtraInfo' );
+<& /Group/Elements/GroupInfo, Group => $Group, FormatConfig => 'GroupSummaryExtraInfo', ClassPrefix => 'group-summary' &>
+
+</&>
+<%ARGS>
+$Group
+</%ARGS>
diff --git a/share/html/Group/Elements/Portlets/GroupAssets b/share/html/Group/Elements/Portlets/GroupAssets
new file mode 100644
index 000000000..88aa7a2ee
--- /dev/null
+++ b/share/html/Group/Elements/Portlets/GroupAssets
@@ -0,0 +1,52 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+%# Roles => [''] triggers the magical RoleLimit behavior that matches any role
+<& /Group/Elements/AssetList, Group => $Group, Roles => [''], Title => loc('Assigned Assets') &>
+<%ARGS>
+$Group
+</%ARGS>
diff --git a/share/html/Group/Elements/Portlets/InactiveTickets b/share/html/Group/Elements/Portlets/InactiveTickets
new file mode 100644
index 000000000..902b82614
--- /dev/null
+++ b/share/html/Group/Elements/Portlets/InactiveTickets
@@ -0,0 +1,68 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<& /Group/Elements/TicketList ,
+ Group => $Group,
+ conditions => $conditions,
+ Rows => $Rows,
+ WatcherTypes => [qw(Watcher)],
+ Class => "group inactive-tickets",
+ Title => loc('Inactive Tickets'),
+ TitleBox => 1,
+ ShowHeader => 1,
+ Format => RT->Config->Get('GroupSummaryTicketListFormat'),
+&>
+<%INIT>
+unless ( @$conditions ) {
+ push @$conditions, { cond => "Status = '__Inactive__'" };
+}
+</%INIT>
+<%ARGS>
+$Group => undef
+$conditions => []
+$Rows => 10
+</%ARGS>
diff --git a/share/html/Group/Elements/TicketList b/share/html/Group/Elements/TicketList
new file mode 100644
index 000000000..faaec0d8e
--- /dev/null
+++ b/share/html/Group/Elements/TicketList
@@ -0,0 +1,114 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+% if ( $TitleBox ) {
+ <& /Widgets/TitleBoxStart, title => $Title, title_href => $url, class => $Class &>
+% } else {
+ <span class="label"><a href="<% $url %>"><% $Title %>:</a></span>
+% }
+
+<& /Elements/CollectionList,
+ %QueryProperties,
+ Class => 'RT::Tickets',
+ Page => 1,
+ AllowSorting => 0,
+ ShowNavigation => 0,
+&>
+
+% if ( $TitleBox ) {
+ <& /Widgets/TitleBoxEnd &>
+% }
+<%INIT>
+
+my $sql = '';
+
+$sql = join(' OR ', map { "$_.id = ".$Group->Id } @WatcherTypes );
+$sql = "( $sql )";
+
+$m->callback( CallbackName => 'ModifyWatcherSQL',
+ %ARGS,
+ sql => \$sql,
+);
+
+if (@$conditions) {
+ $sql .= " AND (".join( " OR ", map $_->{cond}, @$conditions).")";
+}
+
+my %QueryProperties = (
+ Query => $sql,
+ OrderBy => 'Priority|id',
+ Order => 'DESC|DESC',
+ Rows => $Rows || 10,
+ ShowHeader => $ShowHeader,
+ Format => $Format,
+);
+
+$m->callback( CallbackName => 'ModifyQueryProperties',
+ %ARGS,
+ QueryProperties => \%QueryProperties,
+);
+
+my $url = RT->Config->Get('WebPath') . '/Search/Results.html?';
+ $url .= $m->comp('/Elements/QueryString',
+ Query => $QueryProperties{'Query'},
+ OrderBy => $QueryProperties{'OrderBy'},
+ Order => $QueryProperties{'Order'},
+ );
+
+</%INIT>
+<%ARGS>
+$Title => ''
+$Class => ''
+@WatcherTypes => (qw(Watcher))
+$Group => undef
+$conditions
+$Rows => 10
+$Description => ''
+$TitleBox => 0
+$Format => ''
+$ShowHeader => 0
+</%ARGS>
diff --git a/share/html/Group/Search.html b/share/html/Group/Search.html
new file mode 100644
index 000000000..0834b6de8
--- /dev/null
+++ b/share/html/Group/Search.html
@@ -0,0 +1,100 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<& /Elements/Header, Title => loc('Group Search'), Focus => '#autocomplete-GroupString' &>
+<& /Elements/Tabs &>
+
+<& /Elements/GotoGroup, Default => $GroupString||'' &>
+
+<p> <&|/l&>This will search for groups by looking in the following fields:</&> <% $search_fields %></p>
+
+% if ($GroupString) {
+
+% unless ( $groups->Count ) {
+<p><&|/l&>No groups matching search criteria found.</&></p>
+% } else {
+<p><&|/l&>Select a group</&>:</p>
+
+<& /Elements/CollectionList,
+ OrderBy => 'Name',
+ Order => 'ASC',
+ Rows => 100,
+ %ARGS,
+ Format => $Format,
+ Collection => $groups,
+ AllowSorting => 1,
+ PassArguments => [qw(Format Rows Page Order OrderBy GroupString)],
+&>
+
+% }
+% }
+
+<%INIT>
+my $groups;
+my $Format;
+if ( $GroupString ) {
+ $groups = RT::Groups->new($session{'CurrentUser'});
+ $groups->LimitToUserDefinedGroups();
+
+ $groups->SimpleSearch( Return => 'Name',
+ Term => $GroupString,
+ Max => 100 );
+ my $first = $groups->First;
+ RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."Group/Summary.html?id=".$first->Id)
+ if $groups->Count == 1;
+ $groups->GotoFirstItem;
+ $Format = RT->Config->Get('GroupSearchResultFormat');
+}
+
+my $search_fields = join ", ",
+ sort map {s/^CF\.(?:\{(.*)}|(.*))/$1 || $2/e; loc($_)}
+ keys %{RT->Config->Get('GroupSearchFields')};
+
+</%INIT>
+<%ARGS>
+$GroupString => undef
+</%ARGS>
diff --git a/share/html/Group/Summary.html b/share/html/Group/Summary.html
new file mode 100644
index 000000000..6048fc954
--- /dev/null
+++ b/share/html/Group/Summary.html
@@ -0,0 +1,103 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<& /Elements/Header, Title => loc('Group: [_1]', $Group->Name) &>
+<& /Elements/Tabs &>
+
+<& /Elements/GotoGroup &>
+<& /Elements/ListActions, actions => \@results &>
+
+<%perl>
+$m->callback( CallbackName => 'BeforePortlets', ARGSRef => \%ARGS, Group => $Group, Portlets => $portlets );
+for my $portlet (@$portlets) {
+ $show_portlet->($portlet);
+}
+$m->callback( CallbackName => 'AfterPortlets', ARGSRef => \%ARGS, Group => $Group, Portlets => $portlets );
+</%perl>
+
+<%INIT>
+my $Group = RT::Group->new( $session{'CurrentUser'} );
+my ($status, $msg) = $Group->Load($id);
+unless ($status) {
+ RT->Logger->error("Unable to load group $id: $msg");
+ Abort("Unable to load Group $id");
+}
+
+unless ( $Group->CurrentUserHasRight('SeeGroup') ){
+ Abort("No permission to view group");
+}
+
+my @results;
+if ( $Group->Disabled ){
+ if ( $session{'CurrentUser'}->HasRight(
+ Object => RT->System, Right => 'AdminGroup' ) ){
+ push @results, loc('Group [_1] is currently disabled. Edit the group and check "Enabled" to enable.', $Group->Name);
+ }
+ else{
+ push @results, loc('Group [_1] is currently disabled.', $Group->Name);
+ }
+}
+
+my $portlets = RT->Config->Get('GroupSummaryPortlets');
+
+my $show_portlet = sub {
+ my $portlet = shift;
+ my $full_path = "/Group/Elements/Portlets/$portlet";
+ unless ( RT::Interface::Web->ComponentPathIsSafe($full_path) ) {
+ RT->Logger->error("unsafe portlet $portlet specified in GroupSummaryPortlets");
+ return;
+ }
+ unless ( $m->comp_exists($full_path) ) {
+ RT->Logger->error("Unable to find $portlet in /Group/Elements/Portlets - specified in GroupSummaryPortlets");
+ return;
+ }
+ $m->comp( $full_path, Group => $Group );
+};
+</%INIT>
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 025032046..47be144aa 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -56,7 +56,11 @@
<input type="submit" name="SubmitTicket" value="Create" style="display:none">
<input type="hidden" class="hidden" name="id" value="new" />
<input type="hidden" class="hidden" name="Token" value="<% $ARGS{'Token'} %>" />
-
+
+% if ( $ARGSRef->{'AddGroupCc'} ){
+<input type="hidden" class="hidden" name="AddGroupCc" value="<% $ARGSRef->{'AddGroupCc'} %>" />
+% }
+
% $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS );

% if ($gnupg_widget) {
@@ -525,4 +529,5 @@ $DependedOnBy => undef
$MemberOf => undef
$QuoteTransaction => undef
$CloneTicket => undef
+$ARGSRef => undef
</%ARGS>
diff --git a/share/html/Ticket/Display.html b/share/html/Ticket/Display.html
index 2681a3831..92fcac0d7 100644
--- a/share/html/Ticket/Display.html
+++ b/share/html/Ticket/Display.html
@@ -116,6 +116,7 @@ $TicketObj => undef
$ShowHeaders => 0
$HideUnsetFields => RT->Config->Get('HideUnsetFieldsOnDisplay', $session{CurrentUser})
$ForceShowHistory => 0
+$ARGSRef => undef
</%ARGS>

<%INIT>
@@ -216,6 +217,26 @@ if ($ARGS{'id'} eq 'new') {

$title = loc("#[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject || '');

+if ( $ARGS{'id'} and $ARGS{'id'} eq 'new' ) {
+ if ( $ARGSRef->{'AddGroupCc'} ){
+ my $group = RT::Group->new($session{'CurrentUser'});
+ my ($ret, $msg) = $group->LoadUserDefinedGroup($ARGSRef->{'AddGroupCc'});
+
+ unless ( $ret ){
+ RT::Logger->warn("Unable to load group " . $ARGSRef->{'AddGroupCc'} . ", $msg. Not adding as Cc.");
+ return;
+ }
+
+ ( $ret, $msg ) = $$TicketObj->AddWatcher(
+ Type => 'Cc',
+ PrincipalId => $group->Id
+ );
+
+ RT::Logger->warn("Unable to add group " . $group->Name . ": " . $group->Id . " as a Cc: $msg")
+ unless $ret;
+ }
+}
+
$m->callback(
CallbackName => 'BeforeDisplay',
TicketObj => \$TicketObj,

commit 97de34d41e2ca40d893a4ad09962187dc585c3ea
Author: Blaine Motsinger <blaine@bestpractical.com>
Date: Wed May 29 18:32:18 2019 -0500

Core RT-Extension-GroupSelfService

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 0d3bfb373..30929c092 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1984,6 +1984,26 @@ Users also need the ModifySelf right to have access to this page.

Set( $SelfServiceDownloadUserData, 0 );

+=item C<$SelfServiceShowGroupTickets>
+
+Set this option to true to show a section with group tickets
+on self service pages.
+
+=cut
+
+Set($SelfServiceShowGroupTickets, 1);
+
+=item C<$GroupSelfServiceOwnerFilterName>
+
+To populate "My" tickets in Self Service based on Owner rather
+than Requestor, create a group CF called 'Group Type' and set
+it to the value you configure here. See the main documentation
+for more information.
+
+=cut
+
+Set($GroupSelfServiceOwnerFilterName, 'Partner');
+
=back

=head2 Articles
diff --git a/etc/initialdata b/etc/initialdata
index 74951a5b8..68604d6e9 100644
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -940,4 +940,15 @@ Hour: { $SubscriptionObj->SubValue('Hour') }
Type => 'Text',
MaxValues => 1,
},
+ {
+ Name => 'Group Type',
+ Description => 'Define a group type to sort by Owner in Self Service',
+ Type => 'SelectSingle',
+ LookupType => 'RT::Group',
+ RenderType => 'Dropdown',
+ Values => [.
+ { Name => 'Partner', },
+ { Name => 'Customer', },
+ ],
+ },
);
diff --git a/lib/RT/System.pm b/lib/RT/System.pm
index 05ef84738..d19be82c1 100644
--- a/lib/RT/System.pm
+++ b/lib/RT/System.pm
@@ -93,6 +93,7 @@ __PACKAGE__->AddRight( Staff => ShowGlobalTemplates => 'Show global templates'
__PACKAGE__->AddRight( General => LoadSavedSearch => 'Allow loading of saved searches'); # loc
__PACKAGE__->AddRight( General => CreateSavedSearch => 'Allow creation of saved searches'); # loc
__PACKAGE__->AddRight( Admin => ExecuteCode => 'Allow writing Perl code in templates, scrips, etc'); # loc
+__PACKAGE__->AddRight( Staff => SeeSelfServiceGroupTicket => 'See tickets for other group members in SelfService' ); # loc

=head2 AvailableRights

diff --git a/share/html/SelfService/Elements/MyRequests b/share/html/SelfService/Elements/MyGroupRequests
similarity index 66%
copy from share/html/SelfService/Elements/MyRequests
copy to share/html/SelfService/Elements/MyGroupRequests
index 0cd4e3781..8285cece2 100644
--- a/share/html/SelfService/Elements/MyRequests
+++ b/share/html/SelfService/Elements/MyGroupRequests
@@ -59,9 +59,47 @@
</&>

<%INIT>
+
+unless ( RT->Config->Get('SelfServiceShowGroupTickets')
+ and $session{'CurrentUser'}->HasRight(Right => 'SeeSelfServiceGroupTicket', Object => $RT::System) ){
+ return;
+}
+
$title ||= loc("My [_1] tickets", $friendly_status);
-my $id = $session{'CurrentUser'}->id;
-my $Query = "( Watcher.id = $id )";
+
+# Load a system user to see all groups without a rights check on whether
+# the current user has ShowGroup.
+my $user = RT::User->new(RT->SystemUser);
+my ($ret, $msg) = $user->Load($session{'CurrentUser'}->Id);
+unless ( $ret ){
+ RT::Logger->error("Unable to load user record for user: " . $session{'CurrentUser'}->Name . " :$msg");
+ return;
+}
+my $groups_obj = $user->OwnGroups;
+
+my $Query = '';
+
+if ( $groups_obj->Count ){
+ my $group = $groups_obj->Next;
+
+ # Confirm we got a group. Count can report available groups, but
+ # if the current user doesn't have SeeGroup, it won't be loaded.
+ if ( $group ){
+ $Query = "(( WatcherGroup = " . $group->Id . " )";
+ }
+
+ # Handle multiple groups
+ while ( $group = $groups_obj->Next ){
+ $Query .= " OR ( WatcherGroup = " . $group->Id . " )";
+ }
+
+ $Query .= ")" if $Query;
+}
+
+# Exclude tickets where current user is requestor or cc since they will
+# appear in the My open tickets list
+$Query .= " AND" if $Query;
+$Query .= " $SortByRole.id != " . $session{'CurrentUser'}->Id;

if ($status) {
$status =~ s/(['\\])/\\$1/g;
@@ -72,11 +110,12 @@ my $Format = RT->Config->Get('DefaultSelfServiceSearchResultFormat');
</%INIT>
<%ARGS>
$title => undef
-$friendly_status => loc('open')
+$friendly_status => loc("group's")
$status => undef
$BaseURL => undef
$Page => 1
@Order => ('ASC')
@OrderBy => ('Created')
$Rows => 50
+$SortByRole => 'Requestor' # Role to use when determining "My" tickets
</%ARGS>
diff --git a/share/html/SelfService/Elements/MyRequests b/share/html/SelfService/Elements/MyRequests
index 0cd4e3781..4c05189eb 100644
--- a/share/html/SelfService/Elements/MyRequests
+++ b/share/html/SelfService/Elements/MyRequests
@@ -61,7 +61,7 @@
<%INIT>
$title ||= loc("My [_1] tickets", $friendly_status);
my $id = $session{'CurrentUser'}->id;
-my $Query = "( Watcher.id = $id )";
+my $Query = "( $SortByRole.id = $id OR Watcher.id = $id )";

if ($status) {
$status =~ s/(['\\])/\\$1/g;
@@ -79,4 +79,5 @@ $Page => 1
@Order => ('ASC')
@OrderBy => ('Created')
$Rows => 50
+$SortByRole => 'Requestor' # Role to use when determining "My" tickets
</%ARGS>
diff --git a/share/html/SelfService/index.html b/share/html/SelfService/index.html
index 40f389538..46bc68192 100644
--- a/share/html/SelfService/index.html
+++ b/share/html/SelfService/index.html
@@ -59,6 +59,40 @@

% $m->callback(CallbackName => 'AfterMyRequests', ARGSRef => \%ARGS, Page => $Page);

+<& /SelfService/Elements/MyGroupRequests,
+ %ARGS,
+ status => '__Active__',
+ title => loc('My group\'s tickets'),
+ BaseURL => RT->Config->Get('WebPath') ."/SelfService/?",
+ Page => $Page,
+&>
+
+% $m->callback(CallbackName => 'AfterMyGroupRequests', ARGSRef => \%ARGS, Page => $Page);
+
+<%init>
+# Find current user's groups and determine if they are a partner
+# Load a system user to see all groups without a rights check on whether
+# the current user has ShowGroup.
+my $user = RT::User->new(RT->SystemUser);
+my ($ret, $msg) = $user->Load($session{'CurrentUser'}->Id);
+unless ( $ret ){
+ RT::Logger->error("Unable to load user record for user: " . $session{'CurrentUser'}->Name . " :$msg");
+ return;
+}
+my $groups_obj = $user->OwnGroups;
+my $is_partner;
+
+while ( my $group = $groups_obj->Next ){
+ if ( $group
+ and $group->FirstCustomFieldValue("Group Type")
+ and $group->FirstCustomFieldValue("Group Type") eq RT->Config->Get('GroupSelfServiceOwnerFilterName') ){
+ $is_partner = 1;
+ last;
+ }
+}
+
+$ARGS{'SortByRole'} = 'Owner' if $is_partner;
+</%init>
<%ARGS>
$Page => 1
</%ARGS>

commit 05543b6a0ce688fe44afcd4060919d23f4fa1071
Author: Blaine Motsinger <blaine@bestpractical.com>
Date: Thu Jun 6 12:56:58 2019 -0500

Add note to UPGRADING-4.6 for callback changes

diff --git a/devel/docs/UPGRADING-4.6 b/devel/docs/UPGRADING-4.6
index 747e45e3d..77a8d8248 100644
--- a/devel/docs/UPGRADING-4.6
+++ b/devel/docs/UPGRADING-4.6
@@ -12,6 +12,14 @@ The default callback in C<Articles/Elements/IncludeArticle> provides a ticket
object. However, the template itself does not need this ticket object, so it
is no longer guaranteed to be loaded when it is passed.

+=item *
+
+New group options were added to the ticket listings pages in SelfService. With
+the additions, the C<AfterMyRequests> callback is no longer at the bottom of the
+page. If you previously used this callback to add to the bottom of the SelfService
+page, a new callback C<AfterMyGroupRequests> is now available below the new group
+ticket listing.
+
=back

=cut

commit 130a70cf8ad09ea4d76a3134d28d621e2af232fb
Author: Blaine Motsinger <blaine@bestpractical.com>
Date: Thu May 30 19:30:12 2019 -0500

Fix tests for initialdata change

Use the custom field id created through the tests instead of the
previously hardcoded values.

diff --git a/t/api/customfield.t b/t/api/customfield.t
index dedeaa236..4e925a558 100644
--- a/t/api/customfield.t
+++ b/t/api/customfield.t
@@ -26,6 +26,9 @@ is($cf->Type, 'Select', "Is a select CF");
ok($cf->SingleValue, "Also a single-value CF");
is($cf->MaxValues, 1, "...meaning only one value, max");

+# set $cf_id for loading later instead of using magic id 2
+my $cf_id = $cf->id;
+
($ok, $msg) = $cf->SetMaxValues('0');
ok($ok, "Set to infinite values: $msg");
is($cf->Type, 'Select', "Still a select CF");
@@ -47,7 +50,7 @@ $cf = RT::CustomField->new(RT->SystemUser);
ok( ! $ok, "Correctly could not create with bogus type: $msg");

# Test adding and removing CFVs
-$cf->Load(2);
+$cf->Load($cf_id);
($ok, $msg) = $cf->AddValue(Name => 'foo' , Description => 'TestCFValue', SortOrder => '6');
ok($ok, "Added a new value to the select options");
($ok, $msg) = $cf->DeleteValue($ok);
@@ -131,7 +134,7 @@ warning_like {


# Make it only apply to one queue
-$cf->Load(2);
+$cf->Load($cf_id);
my $ocf = RT::ObjectCustomField->new( RT->SystemUser );
( $ok, $msg ) = $ocf->LoadByCols( CustomField => $cf->id, ObjectId => 0 );
ok( $ok, "Found global application of CF" );
@@ -273,7 +276,7 @@ ok( ! $cf->id, "Also doesn't find with Queue => 0 and IncludeGlobal" );


# Change the lookup type to be a _user_ CF
-$cf->Load(2);
+$cf->Load($cf_id);
($ok, $msg) = $cf->SetLookupType( RT::User->CustomFieldLookupType );
ok($ok, "Changed CF type to be a CF on users" );
$ocf = RT::ObjectCustomField->new( RT->SystemUser );
@@ -308,7 +311,7 @@ ok($cf->id, "Also with user CF and explicit global" );


# Add a second, queue-specific CF to test load order
-$cf->Load(2);
+$cf->Load($cf_id);
($ok, $msg) = $cf->SetLookupType( RT::Ticket->CustomFieldLookupType );
ok($ok, "Changed CF type back to be a CF on tickets" );
$ocf = RT::ObjectCustomField->new( RT->SystemUser );
diff --git a/t/web/cf_image.t b/t/web/cf_image.t
index 7c294b847..6880cd416 100644
--- a/t/web/cf_image.t
+++ b/t/web/cf_image.t
@@ -20,8 +20,8 @@ my $cfid = $m->form_name('ModifyCustomField')->value('id');
ok $cfid, "Created CF correctly";

$m->follow_link_ok( {id => "page-applies-to"} );
-$m->form_with_fields( "AddCustomField-2" );
-$m->tick( "AddCustomField-2", 0 );
+$m->form_with_fields( "AddCustomField-$cfid" );
+$m->tick( "AddCustomField-$cfid", 0 );
$m->click_ok( "UpdateObjs" );
$m->content_contains("Globally added custom field Images");

@@ -46,7 +46,7 @@ $m->content_contains("Upload one image");
$m->submit_form_ok({
form_name => "TicketModify",
fields => {
- "Object-RT::Ticket-1-CustomField-2-Upload" =>
+ "Object-RT::Ticket-1-CustomField-$cfid-Upload" =>
RT::Test::get_relocatable_file('bpslogo.png', '..', 'data'),
},
});
@@ -54,7 +54,7 @@ $m->content_contains("bpslogo.png added");
$m->content_contains("/Download/CustomFieldValue/1/bpslogo.png");

$m->form_name("TicketModify");
-$m->tick("Object-RT::Ticket-1-CustomField-2-DeleteValueIds", 1);
+$m->tick("Object-RT::Ticket-1-CustomField-$cfid-DeleteValueIds", 1);
$m->click_ok("SubmitTicket");
$m->content_lacks("/Download/CustomFieldValue/1/bpslogo.png");

diff --git a/t/web/cf_onqueue.t b/t/web/cf_onqueue.t
index 74559330f..0c260dcc6 100644
--- a/t/web/cf_onqueue.t
+++ b/t/web/cf_onqueue.t
@@ -6,6 +6,8 @@ my ($baseurl, $m) = RT::Test->started_ok;

ok $m->login, 'logged in';

+my $cfid;
+
diag "Create a queue CF";
{
$m->follow_link( id => 'admin-custom-fields-create');
@@ -19,6 +21,9 @@ diag "Create a queue CF";
},
);
$m->content_contains('Object created', 'CF QueueCFTest created' );
+
+ # set the custom field id for the rest of the tests to use
+ $cfid = $m->form_name('ModifyCustomField')->value('id');
}

diag "Apply the new CF globally";
@@ -32,7 +37,7 @@ diag "Apply the new CF globally";
$m->content_contains('QueueCFTest', 'CF QueueCFTest displayed on page' );

$m->form_name('EditCustomFields');
- $m->tick( AddCustomField => 2 );
+ $m->tick( AddCustomField => $cfid );
$m->click('UpdateCFs');

$m->content_contains("Globally added custom field QueueCFTest", 'CF QueueCFTest enabled globally' );
@@ -50,7 +55,7 @@ diag "Edit the CF value for default queue";
# The following doesn't want to works :(
#with_fields => { 'Object-RT::Queue-1-CustomField-2-Value' },
fields => {
- 'Object-RT::Queue-1-CustomField-2-Value' => 'QueueCFTest content',
+ "Object-RT::Queue-1-CustomField-$cfid-Value" => 'QueueCFTest content',
},
);
$m->content_contains('QueueCFTest QueueCFTest content added', 'Content filed in CF QueueCFTest for default queue' );

-----------------------------------------------------------------------
_______________________________________________
rt-commit mailing list
rt-commit@lists.bestpractical.com
http://lists.bestpractical.com/cgi-bin/mailman/listinfo/rt-commit