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 => \' ', 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