1%# BEGIN BPS TAGGED BLOCK {{{
2%#
3%# COPYRIGHT:
4%#
5%# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC
6%#                                          <sales@bestpractical.com>
7%#
8%# (Except where explicitly superseded by other copyright notices)
9%#
10%#
11%# LICENSE:
12%#
13%# This work is made available to you under the terms of Version 2 of
14%# the GNU General Public License. A copy of that license should have
15%# been provided with this software, but in any event can be snarfed
16%# from www.gnu.org.
17%#
18%# This work is distributed in the hope that it will be useful, but
19%# WITHOUT ANY WARRANTY; without even the implied warranty of
20%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21%# General Public License for more details.
22%#
23%# You should have received a copy of the GNU General Public License
24%# along with this program; if not, write to the Free Software
25%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26%# 02110-1301 or visit their web page on the internet at
27%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28%#
29%#
30%# CONTRIBUTION SUBMISSION POLICY:
31%#
32%# (The following paragraph is not intended to limit the rights granted
33%# to you to modify and distribute this software under the terms of
34%# the GNU General Public License and is only of importance to you if
35%# you choose to contribute your changes and enhancements to the
36%# community by submitting them to Best Practical Solutions, LLC.)
37%#
38%# By intentionally submitting any modifications, corrections or
39%# derivatives to this work, or any other work intended for use with
40%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
41%# you are the copyright holder for those contributions and you grant
42%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43%# royalty-free, perpetual, license to use, copy, create derivative
44%# works based on those contributions, and sublicense and distribute
45%# those contributions and any derivatives thereof.
46%#
47%# END BPS TAGGED BLOCK }}}
48<%ARGS>
49$Class => 'RT__Ticket'
50$Name
51$Attr  => undef
52</%ARGS>
53<%ONCE>
54
55use Scalar::Util;
56
57my $role_value = sub {
58    my $role   = shift;
59    my $object = shift;
60
61    # $[0] is the index number of current row
62    my $field  = $_[1] || '';
63
64    my ( $role_type, $attr, $cf_name );
65
66
67    if ( $role eq 'CustomRole' ) {
68        my $role_name;
69        if ( $field =~ /^\{(.+)\}\.CustomField\.\{(.+)\}/ ) {
70
71            # {test}.CustomField.{foo}
72            $role_name = $1;
73            $cf_name   = $2;
74        }
75        elsif ( $field =~ /^\{(.+)\}(?:\.(\w+))?$/ ) {
76
77            # {test}.Name or {test}
78            $role_name = $1;
79            $attr      = $2;
80        }
81
82        # Cache the role object on a per-request basis, to avoid
83        # having to load it for every row
84        my $key = "RT::CustomRole-" . $role_name;
85
86        $role_type = $m->notes($key);
87        if ( !$role_type ) {
88            my $role_obj = RT::CustomRole->new( $object->CurrentUser );
89            $role_obj->Load($role_name);
90
91            RT->Logger->notice("Unable to load custom role $role_name")
92                unless $role_obj->Id;
93
94            $role_type = $role_obj->GroupType;
95            $m->notes( $key, $role_type );
96        }
97    }
98    else {
99        if ( $field =~ /^CustomField\.\{(.+)\}/ ) {
100            $cf_name = $1;
101        }
102        elsif ( $field =~ /^(\w+)$/ ) {
103            $attr = $1;
104        }
105        $role_type = $role;
106    }
107
108    return if !$role_type;
109
110    my $role_group = $object->RoleGroup($role_type);
111    if ( $cf_name || $attr ) {
112        # TODO Show direct members only?
113        my $users = $role_group->UserMembersObj;
114        my @values;
115
116        while ( my $user = $users->Next ) {
117            if ($cf_name) {
118                my $key = join( "-", "CF", $user->CustomFieldLookupType, $cf_name );
119                my $cf = $m->notes($key);
120                if ( !$cf ) {
121                    $cf = $user->LoadCustomFieldByIdentifier($cf_name);
122                    RT->Logger->debug( "Unable to load $cf_name for " . $user->CustomFieldLookupType )
123                        unless $cf->Id;
124                    $m->notes( $key, $cf );
125                }
126
127                my $ocfvs = $cf->ValuesForObject($user)->ItemsArrayRef;
128                my $comp
129                    = $m->comp_exists( "/Elements/ShowCustomField" . $cf->Type )
130                    ? "/Elements/ShowCustomField" . $cf->Type
131                    : undef;
132
133                push @values, map { $comp ? \( $m->scomp( $comp, Object => $_ ) ) : $_->Content } @$ocfvs;
134
135            }
136            elsif ( $user->_Accessible( $attr, 'read' ) ) {
137                push @values, $user->$attr || ();
138            }
139        }
140        return @values if @values <= 1;
141
142        if ($cf_name) {
143            @values = map { \"<li>", $_, \"</li>" } @values;
144            @values = ( \"<ul class='cf-values'>", @values, \"</ul>" );
145        }
146        else {
147            return join ', ', @values;
148        }
149    }
150    else {
151        return \( $m->scomp( "/Elements/ShowPrincipal", Object => $role_group ) );
152    }
153};
154
155my ($COLUMN_MAP, $WCOLUMN_MAP);
156$WCOLUMN_MAP = $COLUMN_MAP = {
157    id => {
158        attribute => 'id',
159        title     => '#', # loc
160        align     => 'right',
161        value     => sub { return $_[0]->id }
162    },
163
164    Created => {
165        attribute => 'Created',
166        title     => 'Created', # loc
167        value     => sub { return $_[0]->CreatedObj->AsString }
168    },
169    CreatedRelative => {
170        attribute => 'Created',
171        title     => 'Created', # loc
172        value     => sub { return $_[0]->CreatedObj->AgeAsString }
173    },
174    CreatedBy => {
175        attribute => 'Creator',
176        title     => 'Created By', # loc
177        value     => sub { return $_[0]->CreatorObj->Name }
178    },
179    LastUpdated => {
180        attribute => 'LastUpdated',
181        title     => 'Last Updated', # loc
182        value     => sub { return $_[0]->LastUpdatedObj->AsString }
183    },
184    LastUpdatedRelative => {
185        attribute => 'LastUpdated',
186        title     => 'Last Updated', # loc
187        value     => sub { return $_[0]->LastUpdatedObj->AgeAsString }
188    },
189    LastUpdatedBy => {
190        attribute => 'LastUpdatedBy',
191        title     => 'Last Updated By', # loc
192        value     => sub { return $_[0]->LastUpdatedByObj->Name }
193    },
194
195    CustomField => {
196        attribute => sub { return shift @_ },
197        title     => sub { return pop @_ },
198        value     => sub {
199            my $self = $WCOLUMN_MAP->{CustomField};
200            my $cf   = $self->{load}->(@_);
201            return unless $cf->Id;
202            return $self->{render}->( $cf, $cf->ValuesForObject($_[0])->ItemsArrayRef );
203        },
204        load      => sub {
205            # Cache the CF object on a per-request basis, to avoid
206            # having to load it for every row
207            my $key = join("-","CF",
208                           $_[0]->CustomFieldLookupType,
209                           $_[0]->CustomFieldLookupId,
210                           $_[-1]);
211
212            my $cf = $m->notes($key);
213            unless ($cf) {
214                $cf = $_[0]->LoadCustomFieldByIdentifier($_[-1]);
215                RT->Logger->debug("Unable to load $_[-1] for ".$_[0]->CustomFieldLookupType." ".$_[0]->CustomFieldLookupId)
216                    unless $cf->Id;
217                $m->notes($key, $cf);
218            }
219            return $cf;
220        },
221        render    => sub {
222            my ($cf, $ocfvs) = @_;
223            my $comp = $m->comp_exists("/Elements/ShowCustomField".$cf->Type)
224                     ? "/Elements/ShowCustomField".$cf->Type
225                     : undef;
226
227            my @values = map {
228                $comp
229                    ? \($m->scomp( $comp, Object => $_ ))
230                    : $_->Content
231            } @$ocfvs;
232
233            if (@values > 1) {
234                for my $value (splice @values) {
235                    push @values, \"<li>", $value, \"</li> \n";
236                }
237                @values = (\"<ul class='cf-values'>", @values, \"</ul>");
238            }
239            return @values;
240        },
241    },
242    CustomRole => {
243        attribute => sub {
244            my $field = $_[0];
245            if ( $field =~ /^CustomRole\.\{.+\}\.\w+/ ) {
246                return $field;
247            }
248            else {
249                return "$field.Name";
250            }
251        },
252        title     => sub {
253            my $field = pop @_;
254            if (   $field =~ /^\{(.+)\}\.CustomField\.\{(.+)\}/
255                || $field =~ /^\{(.+)\}\.(.+)/ )
256            {
257                return "$1.$2";
258            }
259            elsif ( $field =~ /^\{(.+)\}$/ ) {
260                return $1;
261            }
262            else {
263                return $field;
264            }
265        },
266        value => sub { return $role_value->('CustomRole', @_) },
267    },
268
269    CheckBox => {
270        title => sub {
271            my $name = $_[1] || 'SelectedTickets';
272            my $checked = $DECODED_ARGS->{ $name .'All' }? 'checked="checked"': '';
273
274            return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked
275                              onclick="setCheckbox(this, },
276                              $m->interp->apply_escapes($name,'j'),
277                              \qq{)" />};
278        },
279        value => sub {
280            my $id = $_[0]->id;
281
282            my $name = $_[2] || 'SelectedTickets';
283            return \qq{<input type="checkbox" name="}, $name, \qq{" value="$id" checked="checked" />}
284                if $DECODED_ARGS->{ $name . 'All'};
285
286            my $arg = $DECODED_ARGS->{ $name };
287            my $checked = '';
288            if ( $arg && ref $arg ) {
289                $checked = 'checked="checked"' if grep $_ == $id, grep { defined and length } @$arg;
290            }
291            elsif ( $arg ) {
292                $checked = 'checked="checked"' if $arg == $id;
293            }
294            return \qq{<input type="checkbox" name="}, $name, \qq{" value="$id" $checked />}
295        },
296    },
297    RadioButton => {
298        title => \'&nbsp;',
299        value => sub {
300            my $id = $_[0]->id;
301
302            my $name = $_[2] || 'SelectedTicket';
303            my $arg = $DECODED_ARGS->{ $name };
304            my $checked = '';
305            $checked = 'checked="checked"' if $arg && $arg == $id;
306            return \qq{<input type="radio" name="}, $name, \qq{" value="$id" $checked />};
307        },
308    },
309    (map {
310        my $value = RT->Config->Get($_);
311        $_ => { value => sub { return \$value } };
312
313    } qw(WebPath WebBaseURL WebURL)),
314    WebRequestPath    => { value => sub { substr( $m->request_path, 1 ) } },
315    WebRequestPathDir => { value => sub { substr( $m->request_comp->dir_path, 1 ) } },
316    WebHomePath       => {
317        value => sub {
318            my $path = RT->Config->Get("WebPath");
319            if (not $session{CurrentUser}->Privileged) {
320                $path .= "/SelfService";
321            }
322            return \$path;
323        },
324    },
325    CurrentUser       => { value => sub { $session{CurrentUser}->id } },
326    CurrentUserName   => { value => sub { $session{CurrentUser}->Name } },
327};
328
329$COLUMN_MAP->{'CF'} = $COLUMN_MAP->{'CustomField'};
330
331Scalar::Util::weaken($WCOLUMN_MAP);
332
333my $ROLE_MAP = {};
334
335</%ONCE>
336<%INIT>
337$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'Once', CallbackOnce => 1 );
338
339my $generic_with_roles;
340
341# Add in roles
342my $RecordClass = $Class;
343$RecordClass =~ s/_/:/g;
344if ($RecordClass->DOES("RT::Record::Role::Roles")) {
345    unless ($ROLE_MAP->{$RecordClass}) {
346        # UserDefined => 1 is handled by the CustomRole mapping
347        for my $role ($RecordClass->Roles(UserDefined => 0)) {
348            my $attrs = $RecordClass->Role($role);
349            $ROLE_MAP->{$RecordClass}{$role} = {
350                attribute => sub {
351                    my $field = $_[0];
352                    if ( $field =~ /\.\w+/ ) {
353                        return $field;
354                    }
355                    else {
356                        return "$field.Name";
357                    }
358                },
359                title => sub {
360                    my $field = pop @_;
361                    if (   $field =~ /^CustomField\.\{(.+)\}/
362                        || $field =~ /^(?!$role)(.+)/ )
363                    {
364                        return "$role.$1";
365                    }
366                    else {
367                        return $role;
368                    }
369                },
370                value => sub { return $role_value->($role, @_, @_ == 2 ? '' : () ) },
371            };
372            $ROLE_MAP->{$RecordClass}{$role . "s"} = $ROLE_MAP->{$RecordClass}{$role}
373                unless $attrs->{Single};
374        }
375    }
376    $generic_with_roles = { %{$COLUMN_MAP}, %{$ROLE_MAP->{$RecordClass}} };
377} else {
378    $generic_with_roles = { %{$COLUMN_MAP} };
379}
380
381$m->callback( COLUMN_MAP => $generic_with_roles );
382
383# first deal with class specific things
384if (RT::Interface::Web->ComponentPathIsSafe($Class) and $m->comp_exists("/Elements/$Class/ColumnMap")) {
385    my $class_map = $m->comp("/Elements/$Class/ColumnMap", Attr => $Attr, Name => $Name, GenericMap => $generic_with_roles );
386    return $class_map if defined $class_map;
387}
388
389return GetColumnMapEntry( Map => $generic_with_roles, Name => $Name, Attribute => $Attr );
390
391</%INIT>
392