diff options
author | Ivan Kohler <ivan@freeside.biz> | 2015-07-09 22:32:26 -0700 |
---|---|---|
committer | Ivan Kohler <ivan@freeside.biz> | 2015-07-09 22:32:26 -0700 |
commit | 026dc7ad72ba972f230b6709e31fa64397d75ad4 (patch) | |
tree | c5af1a7ac9154744afc3660e9a9405892f2bb50b /rt/share/html/Elements | |
parent | 07b4bc84d1078f7390221d766cdb3142513db4b0 (diff) | |
parent | 1c538bfabc2cd31f27067505f0c3d1a46cba6ef0 (diff) |
merge RT 4.2.11 and Header changes to disable RT javascript, RT#34237
Diffstat (limited to 'rt/share/html/Elements')
105 files changed, 3495 insertions, 1136 deletions
diff --git a/rt/share/html/Elements/AddLinks b/rt/share/html/Elements/AddLinks new file mode 100644 index 000000000..3e34237da --- /dev/null +++ b/rt/share/html/Elements/AddLinks @@ -0,0 +1,106 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 }}} +<%args> +$Object => undef +$CustomFields => undef +$ARGSRef => $DECODED_ARGS +</%args> +<%init> +my $id = ($Object and $Object->id) + ? $Object->id + : "new"; + +my $exclude = qq| data-autocomplete="Tickets" 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.</&> +% $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="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="entry"> <input name="RefersTo-<%$id%>" value="<% $ARGSRef->{"RefersTo-$id"} || '' %>" <% $exclude |n%>/></td> + </tr> + <& /Elements/EditCustomFields, + Object => $Object, + Grouping => 'Links', + InTable => 1, + ($CustomFields + ? (CustomFields => $CustomFields) + : ()), + &> +% $m->callback( CallbackName => 'NewLink' ); +</table> diff --git a/rt/share/html/Elements/BulkCustomFields b/rt/share/html/Elements/BulkCustomFields new file mode 100644 index 000000000..871e30667 --- /dev/null +++ b/rt/share/html/Elements/BulkCustomFields @@ -0,0 +1,105 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 }}} +<table class="bulk-edit-custom-fields"> + +<tr> +<th><&|/l&>Name</&></th> +<th><&|/l&>Add values</&></th> +<th><&|/l&>Delete values</&></th> +</tr> +% my $i = 0; +% while (my $cf = $CustomFields->Next) { +<tr class="<% ++$i%2 ? 'oddline': 'evenline' %>"> +<td class="label"><% $cf->Name %><br /> +<em>(<% $cf->FriendlyType %>)</em></td> +% my $rows = 5; +% my $cf_id = $cf->id; +% my @add = (NamePrefix => 'Bulk-Add-CustomField-', CustomField => $cf, Rows => $rows, +% Multiple => ($cf->MaxValues ==1 ? 0 : 1) , Cols => 25, +% Default => $ARGS{"Bulk-Add-CustomField-$cf_id-Values"} || $ARGS{"Bulk-Add-CustomField-$cf_id-Value"}, ); +% my @del = (NamePrefix => 'Bulk-Delete-CustomField-', CustomField => $cf, +% Rows => $rows, Multiple => 1, Cols => 25, +% Default => $ARGS{"Bulk-Delete-CustomField-$cf_id-Values"} || $ARGS{"Bulk-Delete-CustomField-$cf_id-Value"}, ); +% if ($cf->Type eq 'Select') { +<td><& /Elements/EditCustomFieldSelect, @add &></td> +<td><& /Elements/EditCustomFieldSelect, @del &><br /> +% } elsif ($cf->Type eq 'Combobox') { +<td><& /Elements/EditCustomFieldCombobox, @add &></td> +<td><& /Elements/EditCustomFieldCombobox, @del &><br /> +% } elsif ($cf->Type eq 'Freeform') { +<td><& /Elements/EditCustomFieldFreeform, @add &></td> +<td><& /Elements/EditCustomFieldFreeform, @del &><br /> +% } elsif ($cf->Type eq 'Text') { +<td><& /Elements/EditCustomFieldText, @add &></td> +<td> +% } elsif ($cf->Type eq 'Wikitext') { +<td><& /Elements/EditCustomFieldWikitext, @add &></td> +<td> +% } elsif ($cf->Type eq 'Date') { +<td><& /Elements/EditCustomFieldDate, @add, Default => undef &></td> +<td><& /Elements/EditCustomFieldDate, @del, Default => undef &><br /> +% } elsif ($cf->Type eq 'DateTime') { +% # Pass datemanip format to prevent another tz date conversion +<td><& /Elements/EditCustomFieldDateTime, @add, Default => undef, Format => 'datemanip' &></td> +<td><& /Elements/EditCustomFieldDateTime, @del, Default => undef, Format => 'datemanip' &><br /> +% } else { +% $RT::Logger->crit("Unknown CustomField type: " . $cf->Type); +% next +% } + <label><input type="checkbox" name="Bulk-Delete-CustomField-<% $cf_id %>-AllValues" value="1"> + <em><&|/l&>(Check to delete all values)</&></em></label> +</td> +</tr> +% } +</table> +<%ARGS> +$CustomFields +</%ARGS> +<%INIT> +return unless $CustomFields->Count; +</%INIT> diff --git a/rt/share/html/Elements/BulkLinks b/rt/share/html/Elements/BulkLinks new file mode 100644 index 000000000..473e84347 --- /dev/null +++ b/rt/share/html/Elements/BulkLinks @@ -0,0 +1,197 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 }}} +<table width="100%"> + <tr> + <td valign="top" width="50%"> + <h3><&|/l&>Current Links</&></h3> +<table> + <tr> + <td class="labeltop"><&|/l&>Depends on</&>:</td> + <td class="value"> +% if ( $hash{DependsOn} ) { +% for my $link ( values %{$hash{DependsOn}} ) { + <input type="checkbox" class="checkbox" id="DeleteLink--<%$link->Type%>-<%$link->Target%>" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" /> + <label for="DeleteLink--<%$link->Type%>-<%$link->Target%>"><& /Elements/ShowLink, URI => $link->TargetURI &></label><br /> +% } } + </td> + </tr> + <tr> + <td class="labeltop"><&|/l&>Depended on by</&>:</td> + <td class="value"> +% if ( $hash{DependedOnBy} ) { +% for my $link ( values %{$hash{DependedOnBy}} ) { + <input type="checkbox" class="checkbox" id="DeleteLink-<%$link->Base%>-<%$link->Type%>-" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" /> + <label for="DeleteLink-<%$link->Base%>-<%$link->Type%>-"><& /Elements/ShowLink, URI => $link->BaseURI &></label><br /> +% } } + </td> + </tr> + <tr> + <td class="labeltop"><&|/l&>Parents</&>:</td> + <td class="value"> +% if ( $hash{MemberOf} ) { +% for my $link ( values %{$hash{MemberOf}} ) { + <input type="checkbox" class="checkbox" id="DeleteLink--<%$link->Type%>-<%$link->Target%>" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" /> + <label for="DeleteLink--<%$link->Type%>-<%$link->Target%>"><& /Elements/ShowLink, URI => $link->TargetURI &></label><br /> +% } } + </td> + </tr> + <tr> + <td class="labeltop"><&|/l&>Children</&>:</td> + <td class="value"> +% if ( $hash{Members} ) { +% for my $link ( values %{$hash{Members}} ) { + <input type="checkbox" class="checkbox" id="DeleteLink-<%$link->Base%>-<%$link->Type%>-" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" /> + <label for="DeleteLink-<%$link->Base%>-<%$link->Type%>-"><& /Elements/ShowLink, URI => $link->BaseURI &></label><br /> +% } } + </td> + </tr> + <tr> + <td class="labeltop"><&|/l&>Refers to</&>:</td> + <td class="value"> +% if ( $hash{RefersTo} ) { +% for my $link ( values %{$hash{RefersTo}} ) { + <input type="checkbox" class="checkbox" id="DeleteLink--<%$link->Type%>-<%$link->Target%>" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" /> + <label for="DeleteLink--<%$link->Type%>-<%$link->Target%>"><& /Elements/ShowLink, URI => $link->TargetURI &></label><br /> +% } } + </td> + </tr> + <tr> + <td class="labeltop"><&|/l&>Referred to by</&>:</td> + <td class="value"> +% if ( $hash{ReferredToBy} ) { +% for my $link ( values %{$hash{ReferredToBy}} ) { +% # Skip reminders +% next if (UNIVERSAL::isa($link->BaseObj, 'RT::Ticket') && $link->BaseObj->Type eq 'reminder'); + <input type="checkbox" class="checkbox" id="DeleteLink-<%$link->Base%>-<%$link->Type%>-" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" /> + <label for="DeleteLink-<%$link->Base%>-<%$link->Type%>-"><& /Elements/ShowLink, URI => $link->BaseURI &></label><br /> +% } } + </td> + </tr> + <tr> + <td></td> + <td><i><&|/l&>(Check box to delete)</&></i></td> + </tr> +</table> +</td> +<td valign="top"> +<h3><&|/l&>New Links</&></h3> +<em><&|/l&>Enter tickets or URIs to link to. Separate multiple entries with spaces.</&> +</em><br /> +<table> + <tr> + <td class="label"><&|/l&>Depends on</&>:</td> + <td class="entry"><input name="<% $record_type %>-DependsOn" data-autocomplete="Tickets" data-autocomplete-multiple="1" value="<% $ARGS{ $record_type .'-DependsOn' } || '' %>" /></td> + </tr> + <tr> + <td class="label"><&|/l&>Depended on by</&>:</td> + <td class="entry"><input name="DependsOn-<% $record_type %>" data-autocomplete="Tickets" data-autocomplete-multiple="1" value="<% $ARGS{ 'DependsOn-'. $record_type } || '' %>" /></td> + </tr> + <tr> + <td class="label"><&|/l&>Parents</&>:</td> + <td class="entry"><input name="<% $record_type %>-MemberOf" data-autocomplete="Tickets" data-autocomplete-multiple="1" value="<% $ARGS{ $record_type .'-MemberOf'} || '' %>" /></td> + </tr> + <tr> + <td class="label"><&|/l&>Children</&>:</td> + <td class="entry"> <input name="MemberOf-<% $record_type %>" data-autocomplete="Tickets" data-autocomplete-multiple="1" value="<% $ARGS{ 'MemberOf-'. $record_type } || '' %>" /></td> + </tr> + <tr> + <td class="label"><&|/l&>Refers to</&>:</td> + <td class="entry"><input name="<% $record_type %>-RefersTo" data-autocomplete="Tickets" data-autocomplete-multiple="1" value="<% $ARGS{ $record_type .'-RefersTo'} || '' %>" /></td> + </tr> + <tr> + <td class="label"><&|/l&>Referred to by</&>:</td> + <td class="entry"> <input name="RefersTo-<% $record_type %>" data-autocomplete="Tickets" data-autocomplete-multiple="1" value="<% $ARGS{ 'RefersTo-'. $record_type } || '' %>" /></td> + </tr> +</table> +</td> +</tr> +</table> + +<%ARGS> +$Collection +</%ARGS> + +<%INIT> + +my @types = qw/DependsOn DependedOnBy Members MemberOf RefersTo ReferredToBy/; + +my $record_type = $Collection->RecordClass; +$record_type =~ s/^RT:://; $record_type =~ s/::/-/g; + +my %hash; +if ( $Collection->Count ) { + my $first_record = $Collection->Next; + # we only show current links that exist on all the records + for my $type ( @types ) { + my $target_or_base = + $type =~ /DependsOn|MemberOf|RefersTo/ ? 'Target' : 'Base'; + my $links = $first_record->$type; + while ( my $link = $links->Next ) { + $hash{$type}{$link->$target_or_base} = $link; + } + } + + while ( my $record = $Collection->Next ) { + for my $type ( @types ) { + my $target_or_base = + $type =~ /DependsOn|MemberOf|RefersTo/ ? 'Target' : 'Base'; + # if $hash{$type} is empty, no need to check any more + next unless $hash{$type} && keys %{$hash{$type}}; + + my %exists; + while ( my $link = $record->$type->Next ) { + $exists{$link->$target_or_base}++; + } + + for ( keys %{$hash{$type}} ) { + delete $hash{$type}{$_} unless $exists{$_}; + } + } + } +} +</%INIT> diff --git a/rt/share/html/Elements/Callback b/rt/share/html/Elements/Callback index 119853a57..d472e14a1 100755 --- a/rt/share/html/Elements/Callback +++ b/rt/share/html/Elements/Callback @@ -48,6 +48,9 @@ <%INIT> $ARGS{'CallbackPage'} = delete $ARGS{'Page'} || $m->callers(1)->path; $ARGS{'CallbackName'} = delete $ARGS{'_CallbackName'}; -$RT::Logger->debug("$ARGS{'CallbackPage'} calls old style callback, use \$m->callback"); +RT->Deprecated( + Instead => '$m->callback', + Remove => "4.4", +); return $m->callback( %ARGS ); </%INIT> diff --git a/rt/share/html/Elements/CollectionAsTable/Header b/rt/share/html/Elements/CollectionAsTable/Header index ea3fafd51..a1c5c482e 100644 --- a/rt/share/html/Elements/CollectionAsTable/Header +++ b/rt/share/html/Elements/CollectionAsTable/Header @@ -71,6 +71,8 @@ $generic_query_args->{'Format'} = $FormatString if grep $_ eq 'Format', @PassArg my $item = 0; foreach my $col ( @Format ) { + my $attr = $col->{'attribute'} || $col->{'last_attribute'}; + my $title = $col->{'title'} || ''; if ( $title eq 'NEWLINE' ) { while ( $item < $maxitems ) { @@ -93,12 +95,22 @@ foreach my $col ( @Format ) { $m->out('<th class="collection-as-table"'); $m->out(' colspan="' . $m->interp->apply_escapes($span => 'h') . '"') if $span; + + my $align = $col->{'align'} || do { + my $tmp_columnmap = $m->comp( '/Elements/ColumnMap', + Class => $Class, + Name => $attr, + Attr => 'align', + ); + ProcessColumnMapValue( $tmp_columnmap, Arguments => [ $attr ] ); + }; + $m->out(qq{ style="text-align: $align"}) if $align; $m->out('>'); + my $loc_title; # if title is not defined then use defined attribute or last # one we saw in the format unless ( defined $col->{'title'} ) { - my $attr = $col->{'attribute'} || $col->{'last_attribute'}; my $tmp = $m->comp( '/Elements/ColumnMap', Class => $Class, Name => $attr, @@ -109,8 +121,9 @@ foreach my $col ( @Format ) { # in case title is not defined in ColumnMap # the following regex changes $attr like from "ReferredToBy" to "Referred To By" $title = join ' ', split /(?<=[a-z])(?=[A-Z])/, $attr unless defined $title; + $loc_title = $attr =~ /^(?:CustomField|CF)\./ ? $title : loc($title); } else { - $title = $m->comp('/Elements/ScrubHTML', Content => $title); + $loc_title = loc($m->comp('/Elements/ScrubHTML', Content => $title)); } if ( $AllowSorting and $col->{'attribute'} @@ -134,11 +147,11 @@ foreach my $col ( @Format ) { %$generic_query_args, OrderBy => $attr, Order => $new_order ), 'h') - . '">'. loc($title) .'</a>' + . '">'. $loc_title .'</a>' ); } else { - $m->out( loc($title) ); + $m->out( $loc_title ); } $m->out('</th>'); } diff --git a/rt/share/html/Elements/CollectionAsTable/Row b/rt/share/html/Elements/CollectionAsTable/Row index b83943aa5..deaa312ba 100644 --- a/rt/share/html/Elements/CollectionAsTable/Row +++ b/rt/share/html/Elements/CollectionAsTable/Row @@ -130,7 +130,6 @@ foreach my $column (@Format) { Arguments => [$record, $i], ); } - s/\n/<br \/>/gs for grep defined $_, @out; $m->out( $_ .'="'. $m->interp->apply_escapes( $attrs{$_} => 'h' ) .'"' ) foreach grep $attrs{$_}, qw(align style colspan); diff --git a/rt/share/html/Elements/CollectionList b/rt/share/html/Elements/CollectionList index d76cf014b..fc678f47c 100644 --- a/rt/share/html/Elements/CollectionList +++ b/rt/share/html/Elements/CollectionList @@ -113,23 +113,24 @@ if ($Class =~ /::/) { # older passed in value } $m->out('<table cellspacing="0" class="' . - ($Collection->isa('RT::Tickets') ? 'ticket-list' : 'collection') . ' collection-as-table">'); + ($Collection->isa('RT::Tickets') ? 'ticket-list' : 'collection') . ' collection-as-table">'); if ( $ShowHeader ) { - $m->comp('/Elements/CollectionAsTable/Header', - %ARGS, - Class => $Class, - Format => \@Format, - FormatString => $Format, - Order => \@Order, - OrderBy => \@OrderBy, - Rows => $Rows, - Page => $Page, - AllowSorting => $AllowSorting, - BaseURL => $BaseURL, - GenericQueryArgs => $GenericQueryArgs, - maxitems => $maxitems, - ); + $m->comp('/Elements/CollectionAsTable/Header', + %ARGS, + Class => $Class, + Format => \@Format, + FormatString => $Format, + Order => \@Order, + OrderBy => \@OrderBy, + Rows => $Rows, + Page => $Page, + AllowSorting => $AllowSorting, + BaseURL => $BaseURL, + GenericQueryArgs => $GenericQueryArgs, + maxitems => $maxitems, + PassArguments => \@PassArguments, + ); } my ($i, $column_map) = (0, {}); diff --git a/rt/share/html/Elements/CollectionListPaging b/rt/share/html/Elements/CollectionListPaging index 8bfa4f60b..a7f2aee09 100644 --- a/rt/share/html/Elements/CollectionListPaging +++ b/rt/share/html/Elements/CollectionListPaging @@ -64,15 +64,15 @@ if ($Pages == 1) { else{ $m->out(loc('Page') . ' '); my $prev = $m->interp->apply_escapes($m->comp( - '/Elements/QueryString', - %$URLParams, - Page => ( $CurrentPage - 1 ) - ), 'h'); + '/Elements/QueryString', + %$URLParams, + Page => ( $CurrentPage - 1 ) + ), 'h'); my $next = $m->interp->apply_escapes($m->comp( - '/Elements/QueryString', - %$URLParams, - Page => ( $CurrentPage + 1 ) - ), 'h'); + '/Elements/QueryString', + %$URLParams, + Page => ( $CurrentPage + 1 ) + ), 'h'); my %show; $show{1} = 1; $show{$_} = 1 for (($CurrentPage - 2)..($CurrentPage + 2)); @@ -84,7 +84,7 @@ for my $number ( 1 .. $Pages ) { $dots = undef; my $qs = $m->interp->apply_escapes($m->comp( '/Elements/QueryString', %$URLParams, Page => $number ), 'h'); - $m->out(qq{<span class="pagenum">}); + $m->out(qq{<span class="pagenum">}); if ( $number == $CurrentPage ) { $m->out(qq{<span class="currentpage">$number</span> }); } @@ -96,7 +96,7 @@ for my $number ( 1 .. $Pages ) { $dots = 1; $m->out(qq{<span class="dots">...</span>}); } - $m->out(qq{</span>}); + $m->out(qq{</span>}); } if ($CurrentPage > 1) { diff --git a/rt/share/html/Elements/ColumnMap b/rt/share/html/Elements/ColumnMap index 3ae10ea3e..9c6582507 100644 --- a/rt/share/html/Elements/ColumnMap +++ b/rt/share/html/Elements/ColumnMap @@ -52,11 +52,14 @@ $Attr => undef </%ARGS> <%ONCE> +use Scalar::Util; + # This is scary and should totally be refactored -- jesse -my $COLUMN_MAP = { +my ($COLUMN_MAP, $WCOLUMN_MAP); +$WCOLUMN_MAP = $COLUMN_MAP = { id => { attribute => 'id', - title => 'id', # loc + title => '#', # loc align => 'right', value => sub { return $_[0]->id } }, @@ -96,6 +99,12 @@ my $COLUMN_MAP = { attribute => sub { return shift @_ }, title => sub { return pop @_ }, value => sub { + my $self = $WCOLUMN_MAP->{CustomField}; + my $cf = $self->{load}->(@_); + return unless $cf->Id; + return $self->{render}->( $cf, $cf->ValuesForObject($_[0])->ItemsArrayRef ); + }, + load => sub { # Cache the CF object on a per-request basis, to avoid # having to load it for every row my $key = join("-","CF", @@ -106,36 +115,31 @@ my $COLUMN_MAP = { my $cf = $m->notes($key); unless ($cf) { $cf = $_[0]->LoadCustomFieldByIdentifier($_[-1]); + RT->Logger->notice("Unable to load $_[-1] for ".$_[0]->CustomFieldLookupType." ".$_[0]->CustomFieldLookupId) + unless $cf->Id; $m->notes($key, $cf); } - # Display custom field contents, separated by newlines. - # For Image custom fields we also show a thumbnail here. - my $values = $cf->ValuesForObject( $_[0] ); - return if $values->Count == 0; - - my @values; - # it is guaranteed to be the same type for all fields, right? - my $v = $values->First; - my $cftype = $v->CustomFieldObj->Type; - - do { - if ($cftype eq 'Image') { - push @values, - \($m->scomp( '/Elements/ShowCustomFieldImage', - Object => $v )); - } elsif ( $cftype eq 'Date' or $cftype eq 'DateTime' ) { - # then actually return the date object; - # ProcessColumnMapValue will stringify it - my $DateObj = RT::Date->new( $session{'CurrentUser'} ); - $DateObj->Set(Format => 'unknown', Value => $v->Content); - push @values, $DateObj; - } else { - push @values, $v->Content; + return $cf; + }, + render => sub { + my ($cf, $ocfvs) = @_; + my $comp = $m->comp_exists("/Elements/ShowCustomField".$cf->Type) + ? "/Elements/ShowCustomField".$cf->Type + : undef; + + my @values = map { + $comp + ? \($m->scomp( $comp, Object => $_ )) + : $_->Content + } @$ocfvs; + + if (@values > 1) { + for my $value (splice @values) { + push @values, \"<li>", $value, \"</li> \n"; } - push @values, \'<br />'; # this is deeply silly - } while ($v = $values->Next); - pop @values; # Remove that last <br /> + @values = (\"<ul class='cf-values'>", @values, \"</ul>"); + } return @values; }, }, @@ -146,9 +150,9 @@ my $COLUMN_MAP = { my $checked = $DECODED_ARGS->{ $name .'All' }? 'checked="checked"': ''; return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked - onclick="setCheckbox(this.form, }, + onclick="setCheckbox(this, }, $m->interp->apply_escapes($name,'j'), - \qq{, this.checked)" />}; + \qq{)" />}; }, value => sub { my $id = $_[0]->id; @@ -187,20 +191,60 @@ my $COLUMN_MAP = { } qw(WebPath WebBaseURL WebURL)), WebRequestPath => { value => sub { substr( $m->request_path, 1 ) } }, WebRequestPathDir => { value => sub { substr( $m->request_comp->dir_path, 1 ) } }, + WebHomePath => { + value => sub { + my $path = RT->Config->Get("WebPath"); + if (not $session{CurrentUser}->Privileged) { + $path .= "/SelfService"; + } + return \$path; + }, + }, + CurrentUser => { value => sub { $session{CurrentUser}->id } }, + CurrentUserName => { value => sub { $session{CurrentUser}->Name } }, }; $COLUMN_MAP->{'CF'} = $COLUMN_MAP->{'CustomField'}; +Scalar::Util::weaken($WCOLUMN_MAP); + +my $ROLE_MAP = {}; + </%ONCE> <%INIT> $m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'Once', CallbackOnce => 1 ); -$m->callback( COLUMN_MAP => $COLUMN_MAP ); + +my $generic_with_roles; + +# Add in roles +my $RecordClass = $Class; +$RecordClass =~ s/_/:/g; +if ($RecordClass->DOES("RT::Record::Role::Roles")) { + unless ($ROLE_MAP->{$RecordClass}) { + for my $role ($RecordClass->Roles) { + my $attrs = $RecordClass->Role($role); + $ROLE_MAP->{$RecordClass}{$role} = { + title => $role, + attribute => $attrs->{Column} || "$role.EmailAddress", + value => sub { return \($m->scomp("/Elements/ShowPrincipal", Object => $_[0]->RoleGroup($role) ) ) }, + }; + $ROLE_MAP->{$RecordClass}{$role . "s"} = $ROLE_MAP->{$RecordClass}{$role} + unless $attrs->{Single}; + } + } + $generic_with_roles = { %{$COLUMN_MAP}, %{$ROLE_MAP->{$RecordClass}} }; +} else { + $generic_with_roles = { %{$COLUMN_MAP} }; +} + +$m->callback( COLUMN_MAP => $generic_with_roles ); # first deal with class specific things if (RT::Interface::Web->ComponentPathIsSafe($Class) and $m->comp_exists("/Elements/$Class/ColumnMap")) { - my $class_map = $m->comp("/Elements/$Class/ColumnMap", Attr => $Attr, Name => $Name ); + my $class_map = $m->comp("/Elements/$Class/ColumnMap", Attr => $Attr, Name => $Name, GenericMap => $generic_with_roles ); return $class_map if defined $class_map; } -return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); + +return GetColumnMapEntry( Map => $generic_with_roles, Name => $Name, Attribute => $Attr ); </%INIT> diff --git a/rt/share/html/Elements/GnuPG/KeyIssues b/rt/share/html/Elements/Crypt/KeyIssues index a7ef7c4fd..35c12641e 100644 --- a/rt/share/html/Elements/GnuPG/KeyIssues +++ b/rt/share/html/Elements/Crypt/KeyIssues @@ -46,7 +46,8 @@ %# %# END BPS TAGGED BLOCK }}} % if ( @$Issues || @$SignAddresses ) { -<&| /Widgets/TitleBox, title => loc('GnuPG issues') &> +<div class="results"> +<&| /Widgets/TitleBox, title => loc('[_1] issues', RT->Config->Get('Crypt')->{'Outgoing'}) &> % if ( @$SignAddresses ) { <% loc("The system is unable to sign outgoing email messages. This usually indicates that the passphrase was mis-set, or that GPG Agent is down. Please alert your system administrator immediately. The problem addresses are:") %> @@ -58,9 +59,9 @@ % } % if (@$Issues == 1) { -<% loc("You are going to encrypt outgoing email messages, but there is a problem with a recipient's public key. You have to fix the problem with the key, disable sending a message to that recipient, or disable encryption.") %> +<% loc("You are going to encrypt outgoing email messages, but there is a problem with a recipient's public key/certificate. You have to fix the problem with the key/certificate, disable sending a message to that recipient, or disable encryption.") %> % } elsif (@$Issues > 1) { -<% loc("You are going to encrypt outgoing email messages, but there are problems with recipients' public keys. You have to fix the problems with the keys, disable sending a message to the recipients with key problems, or disable encryption.") %> +<% loc("You are going to encrypt outgoing email messages, but there are problems with recipients' public keys/certificates. You have to fix the problems with the keys/certificates, disable sending a message to the recipients with problems, or disable encryption.") %> % } <ul> @@ -69,12 +70,12 @@ % if ( $issue->{'User'} ) { User <a href="<% RT->Config->Get('WebPath') %>/Admin/Users/Modify.html?id=<% $issue->{'User'}->id %>"><&/Elements/ShowUser, User => $issue->{'User'} &></a> has a problem. % } else { -There is a problem with key(s) for address <% $issue->{'EmailAddress'} %>, but there is no user in the DB for this address. +There is a problem with key/certificate(s) for address <% $issue->{'EmailAddress'} %>, but there is no user in the DB for this address. % } <% $issue->{'Message'} %> <br /> -Select a key you want to use for encryption: -<& /Elements/GnuPG/SelectKeyForEncryption, +Select a key/certificate you want to use for encryption: +<& /Elements/Crypt/SelectKeyForEncryption, Name => 'UseKey-'. $issue->{'EmailAddress'}, EmailAddress => $issue->{'EmailAddress'}, Default => ( $issue->{'User'}? $issue->{'User'}->PreferredKey : undef ), @@ -83,6 +84,7 @@ Select a key you want to use for encryption: % } </ul> </&> +</div> % } <%ARGS> diff --git a/rt/share/html/Elements/GnuPG/SelectKeyForEncryption b/rt/share/html/Elements/Crypt/SelectKeyForEncryption index 54aa3c6ec..2bf79bc4d 100644 --- a/rt/share/html/Elements/GnuPG/SelectKeyForEncryption +++ b/rt/share/html/Elements/Crypt/SelectKeyForEncryption @@ -50,16 +50,15 @@ % } else { <select name="<% $Name %>"> % foreach my $key (@keys) { -<option value="<% $key->{'Fingerprint'} %>"><% $key->{'Fingerprint'} %> <% loc("(trust: [_1])", $key->{'TrustTerse'}) %></option> +<option value="<% $key->{'Fingerprint'} %>"><% $key->{'Formatted'} %> <% loc("(trust: [_1])", $key->{'TrustTerse'}) %></option> % } </select> % } <%INIT> -require RT::Crypt::GnuPG; my $d; -my %res = RT::Crypt::GnuPG::GetKeysForEncryption($EmailAddress); +my %res = RT::Crypt->GetKeysForEncryption($EmailAddress); # move the preferred key to the top of the list my @keys = map { $_->{'Fingerprint'} eq ( $Default || '' ) diff --git a/rt/share/html/Elements/GnuPG/SelectKeyForSigning b/rt/share/html/Elements/Crypt/SelectKeyForSigning index 047d55810..bbd9bda84 100644 --- a/rt/share/html/Elements/GnuPG/SelectKeyForSigning +++ b/rt/share/html/Elements/Crypt/SelectKeyForSigning @@ -58,7 +58,10 @@ $Name => 'SignUsing', $User => undef, </%ARGS> <%INIT> -return unless RT->Config->Get('GnuPG')->{'Enable'}; +return unless RT->Config->Get('Crypt')->{'Enable'}; -my $user_key = $User->PrivateKey; +# XXX: Only GnuPG at this moment supports user's private keys +my $user_key; +$user_key = $User->PrivateKey + if RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG'; </%INIT> diff --git a/rt/share/html/Elements/GnuPG/SignEncryptWidget b/rt/share/html/Elements/Crypt/SignEncryptWidget index c1b5b2560..33136f968 100644 --- a/rt/share/html/Elements/GnuPG/SignEncryptWidget +++ b/rt/share/html/Elements/Crypt/SignEncryptWidget @@ -47,6 +47,7 @@ %# END BPS TAGGED BLOCK }}} <table><tr> % my $columnsplit = "</td><td>"; +% if ( RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG' ) { <td><% loc( 'Sign[_1][_2] using [_3]', $columnsplit, $m->scomp('/Widgets/Form/Boolean:InputOnly', @@ -54,6 +55,14 @@ ), $m->scomp('SelectKeyForSigning', User => $session{'CurrentUser'}->UserObj ), ) |n %></td> +% } else { +<td><% loc( 'Sign[_1][_2]', + $columnsplit, + $m->scomp('/Widgets/Form/Boolean:InputOnly', + Name => 'Sign', CurrentValue => $self->{'Sign'} + ), +) |n %></td> +% } <td><% loc('Encrypt')%></td> <td><& /Widgets/Form/Boolean:InputOnly, Name => 'Encrypt', CurrentValue => $self->{'Encrypt'} &></td> @@ -70,9 +79,7 @@ return unless $self; $Arguments => {} </%ARGS> <%INIT> -return undef unless RT->Config->Get('GnuPG')->{'Enable'}; - -require RT::Crypt::GnuPG; +return undef unless RT->Config->Get('Crypt')->{'Enable'}; return { %$Arguments }; </%INIT> </%METHOD> @@ -84,7 +91,7 @@ $self => undef, <%INIT> return unless $self; -return $m->comp( '/Elements/GnuPG/KeyIssues', +return $m->comp( '/Elements/Crypt/KeyIssues', Issues => $self->{'GnuPGRecipientsKeyIssues'} || [], SignAddresses => $self->{'GnuPGCanNotSignAs'} || [], ); @@ -138,11 +145,11 @@ if ( $self->{'Sign'} ) { if ($address ne $private and $address ne $queue) { push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address; $checks_failure = 1; - } elsif ( not RT::Crypt::GnuPG::DrySign( $address ) ) { + } elsif ( not RT::Crypt->DrySign( Signer => $address ) ) { push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address; $checks_failure = 1; } else { - RT::Crypt::GnuPG::UseKeyForSigning( $self->{'SignUsing'} ) + RT::Crypt->UseKeyForSigning( $self->{'SignUsing'} ) if $self->{'SignUsing'}; } } @@ -165,13 +172,13 @@ if ( $self->{'Encrypt'} ) { my %seen; @recipients = grep !$seen{ lc $_ }++, @recipients; - RT::Crypt::GnuPG::UseKeyForEncryption( + RT::Crypt->UseKeyForEncryption( map { (/^UseKey-(.*)$/)[0] => $self->{ $_ } } grep $self->{ $_ } && /^UseKey-/, keys %$self ); - my ($status, @issues) = RT::Crypt::GnuPG::CheckRecipients( @recipients ); + my ($status, @issues) = RT::Crypt->CheckRecipients( @recipients ); push @{ $self->{'GnuPGRecipientsKeyIssues'} ||= [] }, @issues; $checks_failure = 1 unless $status; } diff --git a/rt/share/html/Elements/CryptStatus b/rt/share/html/Elements/CryptStatus new file mode 100644 index 000000000..b022b1029 --- /dev/null +++ b/rt/share/html/Elements/CryptStatus @@ -0,0 +1,195 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 }}} +<%ARGS> +$Message +$WarnUnsigned => undef +$Reverify => 1 +</%ARGS> +<%INIT> +my @runs; +my $needs_unsigned_warning = $WarnUnsigned; + +my @protocols = RT::Crypt->EnabledProtocols; +my $re_protocols = join '|', map "\Q$_\E", @protocols; + +foreach ( $Message->SplitHeaders ) { + if ( s/^X-RT-($re_protocols)-Status:\s*//io ) { + push @runs, [ $1, RT::Crypt->ParseStatus( Protocol => "$1", Status => $_ ) ]; + } + + $needs_unsigned_warning = 0 if /^X-RT-Incoming-Signature:/; + + # if this is not set, then the email is generated by RT, and so we don't + # need "email is unsigned" warnings + $needs_unsigned_warning = 0 if not /^Received:/; +} + +return unless @runs or $needs_unsigned_warning; + +my $reverify_cb = sub { + my $top = shift; + + my $txn = $top->TransactionObj; + unless ( $txn && $txn->id ) { + return (0, "Couldn't get transaction of attachment #". $top->id); + } + + my $attachments = $txn->Attachments->Clone; + $attachments->Limit( FIELD => 'ContentType', VALUE => 'application/x-rt-original-message' ); + my $original = $attachments->First; + unless ( $original ) { + return (0, "Couldn't find attachment with original email of transaction #". $txn->id); + } + + my $parser = RT::EmailParser->new(); + $parser->SmartParseMIMEEntityFromScalar( + Message => $original->Content, + Decode => 0, + Exact => 1, + ); + my $entity = $parser->Entity; + unless ( $entity ) { + return (0, "Couldn't parse content of attachment #". $original->id); + } + + my @res = RT::Crypt->VerifyDecrypt( Entity => $entity ); + return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted") + unless @res; + + $top->DelHeader("X-RT-$_-Status") for RT::Crypt->Protocols; + $top->AddHeader(map { ("X-RT-". $_->{Protocol} ."-Status" => $_->{'status'} ) } @res); + $top->DelHeader("X-RT-Privacy"); + my %protocols; $protocols{$_->{Protocol}}++ for @res; + $top->AddHeader('X-RT-Privacy' => $_ ) for sort keys %protocols; + + $top->DelHeader('X-RT-Incoming-Signature'); + my @status = RT::Crypt->ParseStatus( + Protocol => $res[0]{'Protocol'}, + Status => $res[0]{'status'}, + ); + for ( @status ) { + if ( $_->{'Operation'} eq 'Verify' && $_->{'Status'} eq 'DONE' ) { + $top->AddHeader( 'X-RT-Incoming-Signature' => $_->{'UserString'} ); + $needs_unsigned_warning = 0; + } + } + return (1, "Reverified original message"); +}; + +my @messages; +foreach my $run ( @runs ) { + my $protocol = shift @$run; + $protocol = $RT::Crypt::PROTOCOLS{lc $protocol}; + foreach my $line ( @$run ) { + if ( $line->{'Operation'} eq 'KeyCheck' ) { + next unless $Reverify; + # if a public key was missing during verification then we want try again + next unless $line->{'KeyType'} eq 'public' && $line->{'Status'} eq 'MISSING'; + + # but only if we have key + my %key = RT::Crypt->GetPublicKeyInfo( + Protocol => $protocol, Key => $line->{'Key'} + ); + if ( $key{'info'} ) { + my ($status, $msg) = $reverify_cb->($Message); + unless ($status) { + $RT::Logger->error($msg); + } else { + return $m->comp('SELF', %ARGS, Reverify => 0); + } + } + else { + push @messages, { + Tag => $protocol, + Classes => [qw/keycheck bad/], + Value => loc( "Public key '0x[_1]' is required to verify signature", $line->{'Key'} ), + }; + } + } + elsif ( $line->{'Operation'} eq 'PassphraseCheck' ) { + next if $line->{'Status'} eq 'DONE'; + push @messages, { + Tag => $protocol, + Classes => ['passphrasecheck', lc $line->{Status}], + Value => loc( $line->{'Message'} ), + }; + } + elsif ( $line->{'Operation'} eq 'Decrypt' ) { + push @messages, { + Tag => $protocol, + Classes => ['decrypt', lc $line->{Status}], + Value => loc( $line->{'Message'} ), + }; + } + elsif ( $line->{'Operation'} eq 'Verify' ) { + push @messages, { + Tag => $protocol, + Classes => ['verify', lc $line->{Status}, 'trust-'.($line->{Trust} || 'UNKNOWN')], + Value => loc( $line->{'Message'} ), + }; + } + else { + next if $line->{'Status'} eq 'DONE'; + push @messages, { + Tag => $protocol, + Classes => [lc $line->{Operation}, lc $line->{Status}], + Value => loc( $line->{'Message'} ), + } + } + } +} + +push @messages, { Tag => "Signing", Classes => ['verify', 'bad'], Value => loc('Warning! This is NOT signed!') } + if $needs_unsigned_warning; +return unless @messages; + +my %seen; +@messages = grep !$seen{$_->{Value}}++, @messages; + +return @messages; +</%INIT> diff --git a/rt/share/html/Elements/EditCustomField b/rt/share/html/Elements/EditCustomField index d0928dc9a..db6d18180 100644 --- a/rt/share/html/Elements/EditCustomField +++ b/rt/share/html/Elements/EditCustomField @@ -55,25 +55,39 @@ unless ( $Type ) { } my $Values; -if ( $Object && $Object->id ) { - $NamePrefix ||= join '-', - 'Object', ref($Object), $Object->Id, 'CustomField', ''; +if ( $Object ) { + $Grouping =~ s/\W//g if $Grouping; - $Values = $Object->CustomFieldValues( $CustomField->id ); - $Values->Columns( - qw( id CustomField ObjectType ObjectId Disabled Content - ContentType ContentEncoding SortOrder Creator Created - LastUpdatedBy LastUpdated ) - ); - # Don't take care of $Values if there isn't values inside - undef ( $Values ) unless ( $Values->Count ); + if ( $Object->Id ) { + $Values = $Object->CustomFieldValues( $CustomField->id ); + $Values->Columns( + qw( id CustomField ObjectType ObjectId Disabled Content + ContentType ContentEncoding SortOrder Creator Created + LastUpdatedBy LastUpdated ) + ); + # Don't take care of $Values if there isn't values inside + undef ( $Values ) unless ( $Values->Count ); + } +} + +my $Name; +if ( !$NamePrefix ) { + $Name = GetCustomFieldInputName(Object => $Object, CustomField => $CustomField, Grouping => $Grouping ); } # Always fill $Default with submited values if it's empty if ( ( !defined $Default || !length $Default ) && $DefaultsFromTopArguments ) { my %TOP = %$DECODED_ARGS; - $Default = $TOP{ $NamePrefix .$CustomField->Id . '-Values' } - || $TOP{ $NamePrefix .$CustomField->Id . '-Value' }; + $Default = $TOP{ $Name } if $Name; + # check both -Values and -Value for back compatibility + if ( $NamePrefix ) { + $Default //= $TOP{ $NamePrefix . $CustomField->Id . '-Values' } + // $TOP{ $NamePrefix . $CustomField->Id . '-Value' }; + } + else { + my $prefix = GetCustomFieldInputNamePrefix(Object => $Object, CustomField => $CustomField, Grouping => $Grouping ); + $Default //= $TOP{ $prefix . 'Values' } // $TOP{ $prefix . 'Value' }; + } } my $MaxValues = $CustomField->MaxValues; @@ -85,10 +99,13 @@ if ($MaxValues == 1 && $Values) { } # The "Magic" hidden input causes RT to know that we were trying to edit the field, even if # we don't see a value later, since browsers aren't compelled to submit empty form fields -$m->out("\n".'<input type="hidden" class="hidden" name="'.$m->interp->apply_escapes($NamePrefix, 'h').$CustomField->Id.'-Values-Magic" value="1" />'."\n"); +$m->out("\n".'<input type="hidden" class="hidden" name="' + . ($Name ? $m->interp->apply_escapes($Name, 'h') : $m->interp->apply_escapes($NamePrefix, 'h').$CustomField->Id.'-Values') + . '-Magic" value="1" />'."\n"); + my $EditComponent = "EditCustomField$Type"; -$m->callback( %ARGS, CallbackName => 'EditComponentName', Name => \$EditComponent, CustomField => $CustomField, Object => $Object ); +$m->callback( %ARGS, CallbackName => 'EditComponentName', Name => \$EditComponent, CustomField => $CustomField, Object => $Object, Rows => \$Rows, Cols => \$Cols); $EditComponent = "EditCustomField$Type" unless $m->comp_exists($EditComponent); return $m->comp( @@ -103,9 +120,12 @@ return $m->comp( Multiple => ($MaxValues != 1), NamePrefix => $NamePrefix, CustomField => $CustomField, + Name => $Name, + $CustomField->BasedOn && $Name ? ( BasedOnName => GetCustomFieldInputName(Object => $Object, CustomField => $CustomField->BasedOnObj, Grouping => $Grouping) ) : (), ); </%INIT> <%ARGS> +$Grouping => undef $Object => undef $CustomField => undef $NamePrefix => undef diff --git a/rt/share/html/Elements/EditCustomFieldAutocomplete b/rt/share/html/Elements/EditCustomFieldAutocomplete index 1dd4b621f..092f83569 100644 --- a/rt/share/html/Elements/EditCustomFieldAutocomplete +++ b/rt/share/html/Elements/EditCustomFieldAutocomplete @@ -53,13 +53,13 @@ cols="<% $Cols %>" \ % if ( defined $Rows ) { rows="<% $Rows %>" \ % } -name="<% $name %>-Values" id="<% $name %>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default || '' %></textarea> +name="<% $name %>" id="<% $name %>" class="CF-<%$CustomField->id%>-Edit"><% $Default || '' %></textarea> <script type="text/javascript"> -var id = <% "$name-Values" |n,j%>; +var id = <% "$name" |n,j%>; id = id.replace(/:/g,'\\:'); jQuery('#'+id).autocomplete( { - source: <%RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/CustomFieldValues?"+<% $Context |n,j %>+<% "$name-Values" |n,u,j%>, + source: RT.Config.WebHomePath + "/Helpers/Autocomplete/CustomFieldValues?"+<% $Context |n,j %>+<% $name |n,u,j%>, focus: function () { // prevent value inserted on focus return false; @@ -78,18 +78,18 @@ jQuery('#'+id).autocomplete( { } ); % } else { -<input type="text" id="<% $name %>-Value" name="<% $name %>-Value" class="CF-<%$CustomField->id%>-Edit" value="<% $Default || '' %>"/> +<input type="text" id="<% $name %>" name="<% $name %>" class="CF-<%$CustomField->id%>-Edit" value="<% $Default || '' %>"/> <script type="text/javascript"> -var id = <% "$name-Value" |n,j%>; +var id = <% $name |n,j%>; id = id.replace(/:/g,'\\:'); jQuery('#'+id).autocomplete( { - source: <%RT->Config->Get('WebPath')|n,j%>+"/Helpers/Autocomplete/CustomFieldValues?"+<% $Context |n,j %>+<% "$name-Value" |n,u,j%> + source: RT.Config.WebHomePath + "/Helpers/Autocomplete/CustomFieldValues?"+<% $Context |n,j %>+<% $name |n,u,j%> } ); % } </script> <%INIT> -my $name = $NamePrefix . $CustomField->Id; +my $name = $Name || $NamePrefix . $CustomField->Id . ( $Multiple ? '-Values' : '-Value' ); if ( $Default && !$Multiple ) { $Default =~ s/\s*\r*\n\s*/ /g; } @@ -108,6 +108,7 @@ if ($CustomField->ContextObject) { <%ARGS> $CustomField => undef $NamePrefix => undef +$Name => undef $Default => undef $Values => undef $Multiple => undef diff --git a/rt/share/html/Elements/EditCustomFieldBinary b/rt/share/html/Elements/EditCustomFieldBinary index 7a2de4995..fc6ee3f26 100644 --- a/rt/share/html/Elements/EditCustomFieldBinary +++ b/rt/share/html/Elements/EditCustomFieldBinary @@ -47,15 +47,26 @@ %# END BPS TAGGED BLOCK }}} % while ( $Values and my $value = $Values->Next ) { %# XXX - let user download the file(s) here? -<input type="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" class="checkbox CF-<%$CustomField->id%>-Edit" value="<% $value->Id %>" /><a href="<%RT->Config->Get('WebPath')%>/Download/CustomFieldValue/<% $value->Id %>/<% $value->Content |un %>"><% $value->Content %></a><br /> +<input type="checkbox" name="<%$delete_name%>" class="checkbox CF-<%$CustomField->id%>-Edit" value="<% $value->Id %>" /><a href="<%RT->Config->Get('WebPath')%>/Download/CustomFieldValue/<% $value->Id %>/<% $value->Content |un %>"><% $value->Content %></a><br /> % } -% if (!$MaxValues || !$Values || $Values->Count < $MaxValues) { -<input type="file" name="<% $NamePrefix %><% $CustomField->Id %>-Upload" class="CF-<%$CustomField->id%>-Edit" /> +% if ($MaxValues && $Values && $Values->Count >= $MaxValues ) { +<div class="hints"> +<&|/l&>Reached maximum number, so new values will override old ones.</&> +</div> % } +<input type="file" name="<% $name %>" class="CF-<%$CustomField->id%>-Edit" /> + +<%INIT> +my $name = $Name || $NamePrefix . $CustomField->Id . '-Upload'; +my $delete_name = $name; +$delete_name =~ s!-Upload$!-DeleteValueIds!; +</%INIT> + <%ARGS> $Object => undef $CustomField => undef $NamePrefix => undef +$Name => undef $Default => undef $Values => undef $MaxValues => undef diff --git a/rt/share/html/Elements/EditCustomFieldCombobox b/rt/share/html/Elements/EditCustomFieldCombobox index 718ff3035..28fc2a1f4 100644 --- a/rt/share/html/Elements/EditCustomFieldCombobox +++ b/rt/share/html/Elements/EditCustomFieldCombobox @@ -46,17 +46,25 @@ %# %# END BPS TAGGED BLOCK }}} % while ($Values and my $value = $Values->Next and $Multiple) { -<input type="checkbox" class="checkbox CF-<%$CustomField->id%>-Edit" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" class="CF-<%$CustomField->id%>-Edit" value="<% $value->Id %>" /><% $value->Content %> +<input type="checkbox" id="<%$delete_name%>" class="checkbox CF-<%$CustomField->id%>-Edit" name="<%$delete_name%>" class="CF-<%$CustomField->id%>-Edit" value="<% $value->Id %>" /> +<label for="<%$delete_name%>"><% $value->Content %></label> <br /> % } % (!$Multiple or !$MaxValues or !$Values or $Values->Count < $MaxValues) or return; <& /Widgets/ComboBox, - Name => $NamePrefix . $CustomField->Id . "-Value", + Name => $name, Default => $Default, Rows => $Rows, Class => "CF-".$CustomField->id."-Edit", Values => [map {$_->Name} @{$CustomField->Values->ItemsArrayRef}], &> + +<%INIT> +my $name = $Name || $NamePrefix . $CustomField->Id . '-Value'; +my $delete_name = $name; +$delete_name =~ s!-Value$!-DeleteValueIds!; +</%INIT> + <%ARGS> $Object => undef $CustomField => undef @@ -66,4 +74,5 @@ $Values => undef $Multiple => 0 $Rows => undef $MaxValues => undef +$Name => undef </%ARGS> diff --git a/rt/share/html/Elements/SelectTicketSortBy b/rt/share/html/Elements/EditCustomFieldCustomGroupings index d06692a80..0287b189f 100755..100644 --- a/rt/share/html/Elements/SelectTicketSortBy +++ b/rt/share/html/Elements/EditCustomFieldCustomGroupings @@ -45,18 +45,29 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<select name="<%$Name%>"> -% foreach my $field (@sortfields) { -<option value="<%$field%>"<% $field eq $Default && qq[ selected="selected"] |n%>><% loc($field) %></option> +% foreach my $group ( @Groupings ) { +<&| /Widgets/TitleBox, + title => $group? loc($group) : loc('Custom Fields'), + class => $css_class .' '. ($group? CSSClass("$css_class-$group") : ''), + id => ($group ? CSSClass("$css_class-$group") : $css_class), + hide_empty => 1, + %$TitleBoxARGS, +&> +% $ARGS{CustomFields} = $CustomFieldGenerator->() if $CustomFieldGenerator; +<& EditCustomFields, %ARGS, Object => $Object, Grouping => $group &> +</&> % } -</select> - +<%ARGS> +$Object +$CustomFieldGenerator => undef, +@Groupings => (RT::CustomField->CustomGroupings( $Object ), '') +</%ARGS> <%INIT> -my $tickets = RT::Tickets->new($session{'CurrentUser'}); -my @sortfields = $tickets->SortFields(); +my $css_class = lc(ref($Object)||$Object); +$css_class =~ s/^rt:://; +$css_class =~ s/::/-/g; +$css_class = CSSClass($css_class); +$css_class .= '-info-cfs'; +my $TitleBoxARGS = delete $ARGS{TitleBoxARGS} || {}; </%INIT> -<%ARGS> -$Name => 'SortTicketsBy' -$Default => 'id' -</%ARGS> diff --git a/rt/share/html/Elements/EditCustomFieldDate b/rt/share/html/Elements/EditCustomFieldDate index 25e01f4ab..6568d34b1 100644 --- a/rt/share/html/Elements/EditCustomFieldDate +++ b/rt/share/html/Elements/EditCustomFieldDate @@ -45,7 +45,7 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -% my $name = $NamePrefix.$CustomField->Id.'-Values'; +% my $name = $Name || $NamePrefix.$CustomField->Id.'-Values'; <& /Elements/SelectDate, Name => "$name", current => 0, ShowTime => 0, $KeepValue && $Default ? (Default => $Default) : () &> (<%$DateObj->AsString(Time => 0, Timezone => 'utc')%>) <%INIT> @@ -59,5 +59,6 @@ $NamePrefix => undef $Default => undef $Values => undef $MaxValues => 1 +$Name => undef $KeepValue => undef </%ARGS> diff --git a/rt/share/html/Elements/EditCustomFieldDateTime b/rt/share/html/Elements/EditCustomFieldDateTime index 28fecd31d..0ca7bb2c4 100644 --- a/rt/share/html/Elements/EditCustomFieldDateTime +++ b/rt/share/html/Elements/EditCustomFieldDateTime @@ -45,7 +45,7 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -% my $name = $NamePrefix.$CustomField->Id.'-Values'; +% my $name = $Name || $NamePrefix.$CustomField->Id.'-Values'; <& /Elements/SelectDate, Name => "$name", current => 0, $KeepValue && $Default ? (Default => $Default) : () &> (<%$DateObj->AsString($KeepValue ? ( Timezone => 'utc' ) : () )%>) <%INIT> @@ -59,6 +59,7 @@ $NamePrefix => undef $Default => undef $Values => undef $MaxValues => 1 +$Name => undef $Format => 'ISO' $KeepValue => undef </%ARGS> diff --git a/rt/share/html/Elements/EditCustomFieldFreeform b/rt/share/html/Elements/EditCustomFieldFreeform index 3514b1dc6..3c2847c04 100644 --- a/rt/share/html/Elements/EditCustomFieldFreeform +++ b/rt/share/html/Elements/EditCustomFieldFreeform @@ -45,7 +45,7 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -% my $name = $NamePrefix . $CustomField->Id . '-Value'; +% my $name = $Name || $NamePrefix . $CustomField->Id . ( $Multiple ? '-Values' : '-Value' ); % if ($Multiple) { <textarea \ % if ( defined $Cols ) { @@ -54,7 +54,7 @@ cols="<% $Cols %>" \ % if ( defined $Rows ) { rows="<% $Rows %>" \ % } -name="<%$name%>s" id="<%$name%>s" wrap="off" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea> +name="<%$name%>" id="<%$name%>" wrap="off" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea> % } else { <input type="text" name="<%$name%>" id="<%$name%>" \ % if ( defined $Cols ) { @@ -74,6 +74,7 @@ unless ( $Multiple ) { $Object => undef $CustomField => undef $NamePrefix => undef +$Name => undef $Default => undef $Values => undef $Multiple => undef diff --git a/rt/share/html/Elements/EditCustomFieldImage b/rt/share/html/Elements/EditCustomFieldImage index 96e0ac648..0b2340a7e 100644 --- a/rt/share/html/Elements/EditCustomFieldImage +++ b/rt/share/html/Elements/EditCustomFieldImage @@ -46,16 +46,27 @@ %# %# END BPS TAGGED BLOCK }}} % while ($Values and my $value = $Values->Next ) { -<input type="checkbox" class="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" class="CF-<%$CustomField->id%>-Edit" value="<% $value->Id %>" /><& ShowCustomFieldImage, Object => $value &> +<input type="checkbox" class="checkbox" name="<%$delete_name%>" class="CF-<%$CustomField->id%>-Edit" value="<% $value->Id %>" /><& ShowCustomFieldImage, Object => $value &> <br /> % } -% if (!$MaxValues or !$Values or $Values->Count < $MaxValues) { -<input type="file" name="<%$NamePrefix%><%$CustomField->Id%>-Upload" class="CF-<%$CustomField->id%>-Edit" /> +% if ($MaxValues && $Values && $Values->Count >= $MaxValues ) { +<div class="hints"> +<&|/l&>Reached maximum number, so new values will override old ones.</&> +</div> % } +<input type="file" name="<%$name%>" class="CF-<%$CustomField->id%>-Edit" /> + +<%INIT> +my $name = $Name || $NamePrefix . $CustomField->Id . '-Upload'; +my $delete_name = $name; +$delete_name =~ s!-Upload$!-DeleteValueIds!; +</%INIT> + <%ARGS> $Object => undef $CustomField => undef $NamePrefix => undef +$Name => undef $Default => undef $Values => undef $MaxValues => undef diff --git a/rt/share/html/Elements/EditCustomFieldSelect b/rt/share/html/Elements/EditCustomFieldSelect index 662c1889c..9fcebfb15 100644 --- a/rt/share/html/Elements/EditCustomFieldSelect +++ b/rt/share/html/Elements/EditCustomFieldSelect @@ -50,12 +50,10 @@ %# (perhaps by tweaking the .display style?) % my $selected = 0; % my @category; -% my $id = $NamePrefix . $CustomField->Id; % my $out = $m->scomp('SELF:options', %ARGS, SelectedRef => \$selected, CategoryRef => \@category); % if (!$HideCategory and @category and not $CustomField->BasedOnObj->id) { - <script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/js/cascaded.js"></script> %# XXX - Hide this select from w3m? - <select onchange="filter_cascade(<% "$id-Values" |n,j%>, this.value)" name="<% $id %>-Category" class="CF-<%$CustomField->id%>-Edit"> + <select onchange="filter_cascade_by_id(<% $name |n,j %>, this.value)" name="<% $name %>-Category" class="CF-<%$CustomField->id%>-Edit"> <option value=""<% !$selected && qq[ selected="selected"] |n %>><&|/l&>-</&></option> % foreach my $cat (@category) { % my ($depth, $name) = @$cat; @@ -63,10 +61,10 @@ % } </select><br /> % } elsif ($CustomField->BasedOnObj->id) { -<script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/js/cascaded.js"></script> + <script type="text/javascript"><!-- jQuery( function () { - var basedon = jQuery('[name^=<% $NamePrefix . $CustomField->BasedOnObj->id %>-Value][type!=hidden]:input:not(.hidden)'); + var basedon = jQuery('[name^="'+<% $BasedOnName || $NamePrefix . $CustomField->BasedOnObj->id . '-Value' |n,j %>+'"][type!="hidden"]:input:not(.hidden)'); basedon.each( function() { var oldchange = jQuery(this).onchange; jQuery(this).change( function () { @@ -82,10 +80,10 @@ jQuery( function () { } }); } - filter_cascade( - <% "$id-Values" |n,j%>, + filter_cascade_by_id( + <% $name |n,j%>, vals, - 1 + true ); if (oldchange != null) oldchange(); @@ -104,7 +102,7 @@ jQuery( function () { % if ( $RenderType eq 'List' ) { <fieldset class="cfedit"> -<div data-name="<%$id%>-Values" id="<%$id%>-Values"> +<div data-name="<%$name%>" id="<%$name%>"> % if ( $checktype eq 'radio' ) { <div class="none"> <input class="none" type="<% $checktype %>" name="<% $name %>" id="<% $name %>-none" value="" <% keys %default ? '' : ' checked="checked"' |n%> /> @@ -125,14 +123,14 @@ jQuery( function () { % } else { % if (@category) { %# this hidden select is to supply a full list of values, -%# see filter_cascade() in js/cascaded.js - <select name="<%$id%>-Values-Complete" id="<%$id%>-Values-Complete" class="hidden" disabled="disabled"> +%# see filter_cascade_select() in js/cascaded.js + <select name="<%$name%>-Complete" id="<%$name%>-Complete" class="hidden" disabled="disabled"> <option value=""<% !$selected && qq[ selected="selected"] |n %>><&|/l&>(no value)</&></option> % $m->out($out); </select> % } <select - name="<%$id%>-Values" id="<%$id%>-Values" class="CF-<%$CustomField->id%>-Edit" + name="<%$name%>" id="<%$name%>" class="CF-<%$CustomField->id%>-Edit" % if ( $Rows && ( $Multiple || !@category || $RenderType eq 'Select box') ) { size="<% $Rows %>" % } @@ -149,12 +147,11 @@ if ( $RenderType eq 'Dropdown' ) { $Rows = 0; } -# The following is for rendering checkboxes / radio buttons only my ($checktype, $name); -if ( $MaxValues == 1 ) { - ($checktype, $name) = ('radio', $NamePrefix . $CustomField->Id . '-Value'); +if ( $MaxValues == 1 and $RenderType eq 'List' ) { + ($checktype, $name) = ('radio', $Name || $NamePrefix . $CustomField->Id . '-Value'); } else { - ($checktype, $name) = ('checkbox', $NamePrefix . $CustomField->Id . '-Values'); + ($checktype, $name) = ('checkbox', $Name || $NamePrefix . $CustomField->Id . '-Values'); } @Default = grep defined && length, @Default; @@ -167,6 +164,8 @@ my %default = map {lc $_ => 1} @Default; $Object => undef $CustomField => undef $NamePrefix => undef +$Name => undef +$BasedOnName => undef @Default => () $Values => undef $Multiple => 0 diff --git a/rt/share/html/Elements/EditCustomFieldText b/rt/share/html/Elements/EditCustomFieldText index a5c043b61..35be9706e 100644 --- a/rt/share/html/Elements/EditCustomFieldText +++ b/rt/share/html/Elements/EditCustomFieldText @@ -53,7 +53,7 @@ cols="<% $Cols %>" \ % if ( defined $Rows ) { rows="<% $Rows %>" \ % } -name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br /> +name="<%$name%>" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br /> % } % if (!$MaxValues or !$Values or $Values->Count < $MaxValues) { <textarea \ @@ -63,16 +63,18 @@ cols="<% $Cols %>" \ % if ( defined $Rows ) { rows="<% $Rows %>" \ % } -name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea> +name="<%$name%>" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea> % } <%INIT> # XXX - MultiValue textarea is for now outlawed. $MaxValues = 1; +my $name = $Name || $NamePrefix . $CustomField->Id . '-Values'; </%INIT> <%ARGS> $Object => undef $CustomField => undef $NamePrefix => '' +$Name => undef $Default => undef $Values => undef $MaxValues => undef diff --git a/rt/share/html/Elements/EditCustomFieldWikitext b/rt/share/html/Elements/EditCustomFieldWikitext index b75fc99d3..7acad3778 100644 --- a/rt/share/html/Elements/EditCustomFieldWikitext +++ b/rt/share/html/Elements/EditCustomFieldWikitext @@ -53,7 +53,7 @@ cols="<% $Cols %>" \ % if ( defined $Rows ) { rows="<% $Rows %>" \ % } -name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br /> +name="<%$name%>" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br /> % } % if (!$MaxValues or !$Values or $Values->Count < $MaxValues) { <textarea \ @@ -63,16 +63,18 @@ cols="<% $Cols %>" \ % if ( defined $Rows ) { rows="<% $Rows %>" \ % } -name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default %></textarea> +name="<%$name%>" class="CF-<%$CustomField->id%>-Edit"><% $Default %></textarea> % } <%INIT> # XXX - MultiValue textarea is for now outlawed. $MaxValues = 1; +my $name = $Name || $NamePrefix . $CustomField->Id . '-Values'; </%INIT> <%ARGS> $Object => undef $CustomField => undef $NamePrefix => undef +$Name => undef $Default => undef $Values => undef $MaxValues => undef diff --git a/rt/share/html/Elements/EditCustomFields b/rt/share/html/Elements/EditCustomFields new file mode 100644 index 000000000..3a3829943 --- /dev/null +++ b/rt/share/html/Elements/EditCustomFields @@ -0,0 +1,119 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 }}} +% $m->callback( CallbackName => 'BeforeCustomFields', Object => $Object, +% Grouping => $Grouping, ARGSRef => \%ARGS, CustomFields => $CustomFields); +% if ( $WRAP ) { +<<% $WRAP %> class="edit-custom-fields"> +% } +% while ( my $CustomField = $CustomFields->Next ) { +% next unless $CustomField->CurrentUserHasRight('ModifyCustomField'); +% my $Type = $CustomField->Type || 'Unknown'; + + <<% $FIELD %> class="edit-custom-field cftype-<% $Type %>"> + <<% $CELL %> class="cflabel"> + <span class="name"><% $CustomField->Name %>:</span><br /> + <span class="type"><% $CustomField->FriendlyType %></span> + </<% $CELL %>> + <<% $CELL %> class="entry"> +% my $default = $m->notes('Field-' . $CustomField->Id); +% $default ||= $ARGS{"CustomField-". $CustomField->Id }; + <& /Elements/EditCustomField, + %ARGS, + CustomField => $CustomField, + Default => $default, + Object => $Object, + &> +% if (my $msg = $m->notes('InvalidField-' . $CustomField->Id)) { + <br /> + <span class="cfinvalidfield"><% $msg %></span> +% } elsif ($ShowHints and $CustomField->FriendlyPattern) { + <br> + <span class="cfhints"> + <&|/l, $CustomField->FriendlyPattern &>Input must match [_1]</&> + </span> +% } + </<% $CELL %>> +% $m->callback( CallbackName => 'AfterCustomFieldValue', CustomField => $CustomField, Object => $Object, Grouping => $Grouping ); + </<% $FIELD %>> +% } + +% if ( $WRAP ) { +</<% $WRAP %>> +% } +% $m->callback( CallbackName => 'AfterCustomFields', Object => $Object, +% Grouping => $Grouping, ARGSRef => \%ARGS ); +<%INIT> +$CustomFields ||= $Object->CustomFields; + +$CustomFields->LimitToGrouping( $Object => $Grouping ) if defined $Grouping; + +$m->callback( %ARGS, CallbackName => 'MassageCustomFields', CustomFields => $CustomFields ); + +# don't print anything if there is no custom fields +return unless $CustomFields->First; +$CustomFields->GotoFirstItem; + +$AsTable ||= $InTable; +my $FIELD = $AsTable ? 'tr' : 'div'; +my $CELL = $AsTable ? 'td' : 'div'; +my $WRAP = ''; +if ( $AsTable ) { + $WRAP = 'table' unless $InTable; +} else { + $WRAP = 'div'; +} + +</%INIT> +<%ARGS> +$Object +$CustomFields => undef +$Grouping => undef +$AsTable => 1 +$InTable => 0 +$ShowHints => 1 +</%ARGS> diff --git a/rt/share/html/Elements/EditLinks b/rt/share/html/Elements/EditLinks index e167d1144..2e759367e 100755 --- a/rt/share/html/Elements/EditLinks +++ b/rt/share/html/Elements/EditLinks @@ -52,59 +52,59 @@ <table> <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Depends on'), Relation => 'DependsOn' &>:</td> + <td class="labeltop"><& ShowRelationLabel, Object => $Object, Label => loc('Depends on'), Relation => 'DependsOn' &>:</td> <td class="value"> % while (my $link = $Object->DependsOn->Next) { - <input type="checkbox" class="checkbox" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" /> - <& ShowLink, URI => $link->TargetURI &><br /> + <input type="checkbox" class="checkbox" id="DeleteLink--<%$link->Type%>-<%$link->Target%>" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" /> + <label for="DeleteLink--<%$link->Type%>-<%$link->Target%>"><& ShowLink, URI => $link->TargetURI &></label><br /> % } </td> </tr> <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Depended on by'), Relation => 'DependedOnBy' &>:</td> + <td class="labeltop"><& ShowRelationLabel, Object => $Object, Label => loc('Depended on by'), Relation => 'DependedOnBy' &>:</td> <td class="value"> % while (my $link = $Object->DependedOnBy->Next) { - <input type="checkbox" class="checkbox" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" /> - <& ShowLink, URI => $link->BaseURI &><br /> + <input type="checkbox" class="checkbox" id="DeleteLink-<%$link->Base%>-<%$link->Type%>-" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" /> + <label for="DeleteLink-<%$link->Base%>-<%$link->Type%>-"><& ShowLink, URI => $link->BaseURI &></label><br /> % } </td> </tr> <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Parents'), Relation => 'Parents' &>:</td> + <td class="labeltop"><& ShowRelationLabel, Object => $Object, Label => loc('Parents'), Relation => 'Parents' &>:</td> <td class="value"> % while (my $link = $Object->MemberOf->Next) { % next if $link->Target and $link->Target =~ m(^freeside://); - <input type="checkbox" class="checkbox" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" /> - <& ShowLink, URI => $link->TargetURI &><br /> + <input type="checkbox" class="checkbox" id="DeleteLink--<%$link->Type%>-<%$link->Target%>" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" /> + <label for="DeleteLink--<%$link->Type%>-<%$link->Target%>"><& ShowLink, URI => $link->TargetURI &></label><br /> % } </td> </tr> <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Children'), Relation => 'Children' &>:</td> + <td class="labeltop"><& ShowRelationLabel, Object => $Object, Label => loc('Children'), Relation => 'Children' &>:</td> <td class="value"> % while (my $link = $Object->Members->Next) { - <input type="checkbox" class="checkbox" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" /> - <& ShowLink, URI => $link->BaseURI &><br /> + <input type="checkbox" class="checkbox" id="DeleteLink-<%$link->Base%>-<%$link->Type%>-" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" /> + <label for="DeleteLink-<%$link->Base%>-<%$link->Type%>-"><& ShowLink, URI => $link->BaseURI &></label><br /> % } </td> </tr> <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Refers to'), Relation => 'RefersTo' &>:</td> + <td class="labeltop"><& ShowRelationLabel, Object => $Object, Label => loc('Refers to'), Relation => 'RefersTo' &>:</td> <td class="value"> % while (my $link = $Object->RefersTo->Next) { - <input type="checkbox" class="checkbox" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" /> - <& ShowLink, URI => $link->TargetURI &><br /> + <input type="checkbox" class="checkbox" id="DeleteLink--<%$link->Type%>-<%$link->Target%>" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" /> + <label for="DeleteLink--<%$link->Type%>-<%$link->Target%>"><& ShowLink, URI => $link->TargetURI &></label><br /> %} </td> </tr> <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Referred to by'), Relation => 'ReferredToBy' &>:</td> + <td class="labeltop"><& ShowRelationLabel, Object => $Object, Label => loc('Referred to by'), Relation => 'ReferredToBy' &>:</td> <td class="value"> % while (my $link = $Object->ReferredToBy->Next) { % # Skip reminders % next if (UNIVERSAL::isa($link->BaseObj, 'RT::Ticket') && $link->BaseObj->Type eq 'reminder'); - <input type="checkbox" class="checkbox" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" /> - <& ShowLink, URI => $link->BaseURI &><br /> + <input type="checkbox" class="checkbox" id="DeleteLink-<%$link->Base%>-<%$link->Type%>-" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" /> + <label for="DeleteLink-<%$link->Base%>-<%$link->Type%>-"><& ShowLink, URI => $link->BaseURI &></label><br /> % } </td> </tr> @@ -113,68 +113,14 @@ <td><i><&|/l&>(Check box to delete)</&></i></td> </tr> </table> - + </td> <td valign="top"> <h3><&|/l&>New Links</&></h3> -% 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.</&> -% $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> -% if ($Merge) { - <tr> - <td class="label"><&|/l&>Merge into</&>:</td> - <td class="entry"><input name="<%$id%>-MergeInto" /> <i><&|/l&>(only one ticket)</&></i></td> - </tr> -% } - <tr> - <td class="label"><& ShowRelationLabel, id => $id, Label => loc('Depends on'), Relation => 'DependsOn' &>:</td> - <td class="entry"><input name="<%$id%>-DependsOn" /></td> - </tr> - <tr> - <td class="label"><& ShowRelationLabel, id => $id, Label => loc('Depended on by'), Relation => 'DependedOnBy' &>:</td> - <td class="entry"><input name="DependsOn-<%$id%>" /></td> - </tr> - <tr> - <td class="label"><& ShowRelationLabel, id => $id, Label => loc('Parents'), Relation => 'Parents' &>:</td> - <td class="entry"><input name="<%$id%>-MemberOf" /></td> - </tr> - <tr> - <td class="label"><& ShowRelationLabel, id => $id, Label => loc('Children'), Relation => 'Children' &>:</td> - <td class="entry"> <input name="MemberOf-<%$id%>" /></td> - </tr> - <tr> - <td class="label"><& ShowRelationLabel, id => $id, Label => loc('Refers to'), Relation => 'RefersTo' &>:</td> - <td class="entry"><input name="<%$id%>-RefersTo" /></td> - </tr> - <tr> - <td class="label"><& ShowRelationLabel, id => $id, Label => loc('Referred to by'), Relation => 'ReferredToBy' &>:</td> - <td class="entry"> <input name="RefersTo-<%$id%>" /></td> - </tr> -% $m->callback( CallbackName => 'NewLink' ); -</table> +<& AddLinks, %ARGS &> </td> </tr> </table> - -<%INIT> -my $id; -if ($Object && $Object->Id) { - $id = $Object->Id; -} else { - $id = 'new'; -} -</%INIT> - <%ARGS> $Object => undef -$Merge => 0 </%ARGS> diff --git a/rt/share/html/Elements/EditTimeValue b/rt/share/html/Elements/EditTimeValue index ac27665fb..e422c0bf1 100644 --- a/rt/share/html/Elements/EditTimeValue +++ b/rt/share/html/Elements/EditTimeValue @@ -46,20 +46,22 @@ %# %# END BPS TAGGED BLOCK }}} <input name="<% $ValueName %>" value="<% $Default || '' %>" size="5" /> -<& /Elements/SelectTimeUnits, Name => $UnitName &> +<& /Elements/SelectTimeUnits, Name => $UnitName, Default => $InUnits &> <%ARGS> $Default => '' $Name => '' $ValueName => '' $UnitName => '' -$InputUnits => 'minutes' +$InUnits => '' </%ARGS> <%INIT> $ValueName ||= $Name; $UnitName ||= ($Name||$ValueName) . '-TimeUnits'; +$InUnits ||= $m->request_args->{ $UnitName }; +$InUnits ||= RT->Config->Get('DefaultTimeUnitsToHours', $session{'CurrentUser'}) ? 'hours' : 'minutes'; -if ($InputUnits eq 'minutes' && RT->Config->Get('DefaultTimeUnitsToHours', $session{'CurrentUser'})) { - $Default = sprintf '%.3f', $Default / 60 - unless $Default eq ''; +if ($Default && $InUnits eq 'hours') { + # 0+ here is to remove the ending 0s + $Default = 0 + sprintf '%.3f', $Default / 60; } </%INIT> diff --git a/rt/share/html/Elements/EmailInput b/rt/share/html/Elements/EmailInput index 2ae0ddb9e..e894a144a 100644 --- a/rt/share/html/Elements/EmailInput +++ b/rt/share/html/Elements/EmailInput @@ -45,9 +45,11 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<input type="text" id="<% $Name %>" name="<% $Name %>" <% defined $Size ? qq{size="$Size"} : '' |n %> value="<% $Default || '' %>" /> +<input type="text" id="<% $Name %>" name="<% $Name %>" <% defined $Size ? qq{size="$Size"} : '' |n %> value="<% $Default || '' %>" <% $Autocomplete ? q{data-autocomplete="Users"} : '' |n%> <% $AutocompleteMultiple ? q{data-autocomplete-multiple} : '' |n%> /> <%ARGS> $Name $Size => 40 $Default => '' +$Autocomplete => 1 +$AutocompleteMultiple => 0 </%ARGS> diff --git a/rt/share/html/Elements/Error b/rt/share/html/Elements/Error index 5ca18386e..72bea6991 100755 --- a/rt/share/html/Elements/Error +++ b/rt/share/html/Elements/Error @@ -48,7 +48,7 @@ % $m->callback( %ARGS, error => $error ); % unless ($SuppressHeader) { -<& /Elements/Header, Code => $Code, Why => $Why, Title => $Title &> +<& /Elements/Header, Title => $Title &> <& /Elements/Tabs &> % } @@ -60,14 +60,11 @@ <%$Details%> </div> -<%cleanup> -$m->comp('/Elements/Footer'); -$m->abort(); -</%cleanup> +<& /Elements/Footer &> +% $m->abort; <%args> $Actions => [] -$Code => undef $Details => '' $Title => loc("RT Error") $Why => loc("the calling component did not specify why"), @@ -75,13 +72,13 @@ $SuppressHeader => 0, </%args> <%INIT> -my $error = "WebRT: $Why"; +my $error = $Why; $error .= " ($Details)" if defined $Details && length $Details; $RT::Logger->error( $error ); if ( $session{'REST'} ) { - $r->content_type('text/plain'); + $r->content_type('text/plain; charset=utf-8'); $m->out( "Error: " . $Why . "\n" ); $m->out( $Details . "\n" ) if defined $Details && length $Details; $m->abort(); diff --git a/rt/share/html/Elements/BevelBoxRaisedEnd b/rt/share/html/Elements/FindUser index e51efe2fc..6a28582dd 100755..100644 --- a/rt/share/html/Elements/BevelBoxRaisedEnd +++ b/rt/share/html/Elements/FindUser @@ -45,6 +45,6 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} - </td> -</tr> -</table> +<&|/Widgets/TitleBox, title => loc('Find a user')&> +<& /Elements/GotoUser &> +</&> diff --git a/rt/share/html/Elements/BevelBoxRaisedStart b/rt/share/html/Elements/FoldStanzaJS index 9c9b41082..b2cf0a5f5 100755..100644 --- a/rt/share/html/Elements/BevelBoxRaisedStart +++ b/rt/share/html/Elements/FoldStanzaJS @@ -45,6 +45,6 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<table cellspacing="0" cellpadding="0" width="100%" height="100%"> - <tr> - <td width="100%" height="100%"> +<div + class="message-stanza-folder closed" + onclick="fold_message_stanza(this, <%loc('Show quoted text') |n,j%>, <%loc('Hide quoted text') |n,j%>);"><%loc('Show quoted text')%></div> diff --git a/rt/share/html/Elements/SelectLinkType b/rt/share/html/Elements/GotoUser index 5d70f04e4..214d23263 100755..100644 --- a/rt/share/html/Elements/SelectLinkType +++ b/rt/share/html/Elements/GotoUser @@ -45,13 +45,18 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<select name="<%$Name%>"> -<option <% ($Default eq 'MemberOf') ? 'selected="selected"' : '' %> value="MemberOf"><&|/l&>Member of</&></option> -<option <% ($Default eq 'DependsOn') ? 'selected="selected"' : '' %> value="DependsOn"><&|/l&>Depends on</&></option> -<option <% ($Default eq 'RefersTo') ? 'selected="selected"' : '' %> value="RefersTo"><&|/l&>Refers to</&></option> -</select> - +<form name="UserSearch" method="post" action="<% RT->Config->Get('WebPath') %>/User/Search.html"> +<input type="text" name="UserString" value="<% $Default %>" data-autocomplete="Users" data-autocomplete-return="Name" id="autocomplete-UserString" /> +<script type="text/javascript"> +jQuery(function(){ + // Jump directly to the page if a user is chosen + jQuery("#autocomplete-UserString").on("autocompleteselect", function( event, ui ) { + document.location = RT.Config.WebPath + "/User/Summary.html?id=" + ui.item.id; + }); +}); +</script> +<input type="submit" name="UserSearch" value="<&|/l&>Search</&>" class="button" /> +</form> <%ARGS> -$Name => "LinkType" $Default => '' </%ARGS> diff --git a/rt/share/html/Elements/Header b/rt/share/html/Elements/Header index 664da06b0..1e7eb3556 100755 --- a/rt/share/html/Elements/Header +++ b/rt/share/html/Elements/Header @@ -56,8 +56,6 @@ &> <%INIT> #for "Site CSS from theme editor" below -#use Scalar::Util qw(blessed); - $r->headers_out->{'Pragma'} = 'no-cache'; $r->headers_out->{'Cache-control'} = 'no-cache'; @@ -75,17 +73,23 @@ my $style = $session{'CurrentUser'} my @css_files; if ( RT->Config->Get('DevelMode') ) { - @css_files = ( "$style/main.css", RT->Config->Get('CSSFiles' ) ); + @css_files = map { "/static/css/$_" } "$style/main.css", RT->Config->Get('CSSFiles'); } else { my $key = RT::Interface::Web::SquishedCSS( $style )->Key; - @css_files = "$style-squished-$key.css"; + @css_files = "/NoAuth/css/$style/squished-$key.css"; } -my $head = ''; +# We use BodyClass in its $ARGS form so that callbacks have a chance to +# massage it +push @{$ARGS{'BodyClass'}}, lc $style; -#XXX $head .= <& /Elements/Framekiller &>; +if (RT->Config->Get("UseSideBySideLayout", $session{'CurrentUser'})) { + push @{$ARGS{'BodyClass'}}, 'sidebyside'; +} + +my $head = ''; if ($Refresh && $Refresh =~ /^(\d+)/ && $1 > 0) { my $URL = $m->notes->{RefreshURL}; $URL = $URL ? ";URL=$URL" : ""; @@ -96,15 +100,17 @@ my $WebPath = RT->Config->Get('WebPath'); my $WebImagesURL = RT->Config->Get('WebImagesURL'); my $squished = RT->Config->Get('DevelMode') ? '' : '-squished'; -$head .= qq(<link rel="shortcut icon" href="${WebImagesURL}favicon.png" type="image/png" />\n); -for my $cssfile ( @css_files ) { - $head .= qq(<link rel="stylesheet" href="$WebPath/NoAuth/css/$cssfile" type="text/css" media="all" />\n); +if ( $JavaScript ) { + $head .= $m->scomp('JavascriptConfig'); } -$head .= qq(<link rel="stylesheet" href="$WebPath/NoAuth/css/print.css" type="text/css" media="print" />\n); + +for my $cssfile ( @css_files ) { + $head .= qq(<link rel="stylesheet" href="$WebPath$cssfile" type="text/css" media="all" />\n); for (keys %{$LinkRel || {}}) { $head .= qq(<link rel="$_" href="$WebPath) . $LinkRel->{$_} . '" />'; } +$head .= qq(<link rel="shortcut icon" href="${WebImagesURL}favicon.png" type="image/png" />\n); if ( $RSSAutoDiscovery ) { $head .= qq(<link rel="alternate" href="$RSSAutoDiscovery" type="application/rss+xml" title="RSS RT Search" />); @@ -112,29 +118,23 @@ if ( $RSSAutoDiscovery ) { if ($JavaScript) { $head .= $m->scomp('HeaderJavascript', focus => $Focus, onload => $onload, RichText => $RichText ); -} -if ($JavaScript) { my $stylesheet_plugin = "/NoAuth/css/$style/InHeader"; if ($m->comp_exists($stylesheet_plugin) ) { $head .= $m->scomp($stylesheet_plugin); } } -#<!-- Site CSS from theme editor --> -#<style type="text/css" media="all" id="sitecss"> -#%# Header is used when there isn't a database (such as in the Installer) which means there is no -#%# RT::System object, nor are there attributes. -#% if (blessed(RT->System) and my $attr = RT->System->FirstAttribute('UserCSS')) { -#<% $attr->Content |n %> -#% } -#</style> - # $m->callback( %ARGS, CallbackName => 'Head' ); $head .= $m->scomp( '/Elements/Callback', _CallbackName => 'Head', %ARGS ); -my $sbs = RT->Config->Get("UseSideBySideLayout", $session{'CurrentUser'}) ? ' sidebyside' : ''; -my $etc = qq[ class="\L$style$sbs" ]; +if ($JavaScript) { + $head .= $m->scomp('HeaderJavascript', focus => $Focus, onload => $onload, RichText => $RichText ); +} + +#XXX $head .= <& /Elements/Framekiller &>; + +my $etc = ' class="'. join( '',@{$ARGS{'BodyClass'}}). '" '; $etc .= qq[ id="comp-$id"] if $id; </%INIT> @@ -143,15 +143,13 @@ $etc .= qq[ id="comp-$id"] if $id; #$Focus => 'focus' $Focus => '' $Title => 'RT' -$Code => undef $Refresh => 0 -$Why => undef $ShowBar => 1 $URL => undef $RSSAutoDiscovery => undef $onload => undef $LinkRel => undef -$JavaScript => 1 $SkipDoctype => 0 $RichText => 1 +$BodyClass => undef </%ARGS> diff --git a/rt/share/html/Elements/HeaderJavascript b/rt/share/html/Elements/HeaderJavascript index 4bba5592c..79ee74e21 100644 --- a/rt/share/html/Elements/HeaderJavascript +++ b/rt/share/html/Elements/HeaderJavascript @@ -51,37 +51,27 @@ $onload => undef </%args> % for my $jsfile ( @js_files ) { -<script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/js/<% $jsfile %>"></script> +<script type="text/javascript" src="<%RT->Config->Get('WebPath')%><% $jsfile %>"></script> % } -% if ( $RichText and RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'}) ) { -<script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/RichText/ckeditor.js"></script> -% } <script type="text/javascript"><!-- - jQuery( loadTitleBoxStates ); +jQuery( loadTitleBoxStates ); % if ( $focus ) { - jQuery(function () { focusElementById(<% $focus |n,j%>) }); + jQuery(function () { jQuery(<% $focus |n,j%>).focus() }); % } % if ( $onload ) { jQuery( <% $onload |n %> ); % } - -% if ( $RichText and RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'})) { - jQuery().ready(function () { ReplaceAllTextareas() }); -% } --></script> -<%ARGS> -$RichText => 1 -</%ARGS> <%INIT> my @js_files; if ( RT->Config->Get('DevelMode') ) { - @js_files = RT->Config->Get('JSFiles' ); + @js_files = map { $_ =~ m{^/} ? $_ : "/static/js/$_" } RT::Interface::Web->JSFiles(); } else { my $key = RT::Interface::Web::SquishedJS()->Key; - @js_files = "squished-$key.js"; + @js_files = "/NoAuth/js/squished-$key.js"; } </%INIT> diff --git a/rt/share/html/Elements/ShowUserConcise b/rt/share/html/Elements/JavascriptConfig index 1fa970e8d..9437567f0 100644 --- a/rt/share/html/Elements/ShowUserConcise +++ b/rt/share/html/Elements/JavascriptConfig @@ -45,23 +45,40 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -%# Released under the terms of version 2 of the GNU Public License -<% $display |n %>\ -<%ARGS> -$User => undef -$Address => undef -</%ARGS> -<%INIT> -if ( !$User && $Address ) { - $User = RT::User->new( $session{'CurrentUser'} ); - $User->LoadByEmail( $Address->address ); - if ( $User->Id ) { - $Address = ''; - } else { - $Address = $Address->address; - } +<%init> +my $Config = {}; +$Config->{$_} = RT->Config->Get( $_, $session{CurrentUser} ) + for qw(rtname WebPath MessageBoxRichTextHeight); + +my $CurrentUser = {}; +if ($session{CurrentUser} and $session{CurrentUser}->id) { + $CurrentUser->{$_} = $session{CurrentUser}->$_ + for qw(id Name EmailAddress RealName); + + $CurrentUser->{Privileged} = $session{CurrentUser}->Privileged + ? JSON::true : JSON::false; + + $Config->{WebHomePath} = RT->Config->Get("WebPath") + . (!$session{CurrentUser}->Privileged ? "/SelfService" : ""); } -my $display = $Address || $User->RealName || $User->Name; - $display = $m->interp->apply_escapes( $display, 'h' ) - unless $ARGS{'NoEscape'}; -</%INIT> + +my $Catalog = { + quote_in_filename => "Filenames with double quotes can not be uploaded.", #loc +}; +$_ = loc($_) for values %$Catalog; + +$m->callback( + CallbackName => "Data", + CurrentUser => $CurrentUser, + Config => $Config, + Catalog => $Catalog, +); +</%init> +<script> +window.RT = {}; +RT.CurrentUser = <% JSON( $CurrentUser ) |n%>; +RT.Config = <% JSON( $Config ) |n%>; + +RT.I18N = {}; +RT.I18N.Catalog = <% JSON( $Catalog ) |n %>; +</script> diff --git a/rt/share/html/Elements/ListActions b/rt/share/html/Elements/ListActions index 2b74c3878..29bcf6b67 100755 --- a/rt/share/html/Elements/ListActions +++ b/rt/share/html/Elements/ListActions @@ -45,15 +45,18 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} +% $m->out($$_) for grep {ref $_} @actions; +% if (grep {not ref $_} @actions) { <div class="results"> <&| /Widgets/TitleBox, title => loc('Results'), %{$titlebox || {}} &> <ul class="action-results"> -% foreach my $action (@actions) { +% foreach my $action (grep {not ref $_} @actions) { <li><%$action%></li> % } </ul> </&> </div> +% } <%init> # backward compatibility, don't use array in new code, but use keyed hash diff --git a/rt/share/html/Elements/ListMenu b/rt/share/html/Elements/ListMenu index 45949c6c7..5c2a3d583 100644 --- a/rt/share/html/Elements/ListMenu +++ b/rt/share/html/Elements/ListMenu @@ -47,6 +47,7 @@ %# END BPS TAGGED BLOCK }}} <%args> $menu +$show_children => undef </%args> <ul class="list-menu"> % for my $child ($menu->children) { @@ -55,6 +56,9 @@ $menu <span class="description"><% $description %></span>\ % } </li> +% if ($show_children && $child->children) { +<& /Elements/ListMenu, menu => $child &> +% } % } </ul> diff --git a/rt/share/html/Elements/Login b/rt/share/html/Elements/Login index 2c48294a2..cd38b29b1 100755 --- a/rt/share/html/Elements/Login +++ b/rt/share/html/Elements/Login @@ -46,7 +46,7 @@ %# %# END BPS TAGGED BLOCK }}} % $m->callback( %ARGS, CallbackName => 'Header' ); -<& /Elements/Header, Title => loc('Login'), Focus => 'user', RichText => 0 &> +<& /Elements/Header, Title => loc('Login'), Focus => '#user', RichText => 0 &> <div id="body" class="login-body"> @@ -63,7 +63,7 @@ <& LoginRedirectWarning, %ARGS &> -% unless (RT->Config->Get('WebExternalAuth') and !RT->Config->Get('WebFallbackToInternalAuth')) { +% unless (RT->Config->Get('WebRemoteUserAuth') and !RT->Config->Get('WebFallbackToRTLogin')) { <form id="login" name="login" method="post" action="<% RT->Config->Get('WebPath') %>/NoAuth/Login.html"> <div class="input-row"> @@ -73,7 +73,7 @@ <div class="input-row"> <span class="label"><&|/l&>Password</&>:</span> - <span class="input"><input type="password" name="pass" autocomplete="off" /></span> + <span class="input"><input type="password" name="pass" <% RT->Config->Get('AllowLoginPasswordAutoComplete') ? '' : 'autocomplete="off"' | n %> /></span> </div> <input type="hidden" name="next" value="<% $next %>" /> @@ -97,6 +97,7 @@ jQuery(function(){ </form> % } </&> +<& /Elements/LoginHelp &> </div><!-- #login-box --> % $m->callback( %ARGS, CallbackName => 'AfterForm' ); </div><!-- #login-body --> diff --git a/rt/share/html/Elements/MyTickets b/rt/share/html/Elements/LoginHelp index 583d17d19..fbb4c0cc0 100755..100644 --- a/rt/share/html/Elements/MyTickets +++ b/rt/share/html/Elements/LoginHelp @@ -45,5 +45,10 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -%# DEPRECATED -<& /Elements/ShowSearch, Name => 'My Tickets' &> +<div class="login-help"> +<&|/l, RT->Config->Get('OwnerEmail')&>For local help, please contact [_1]</&> +</div> +<%init> +my $source = RT->Config->Meta('OwnerEmail')->{Source}; +return unless $source->{SiteConfig} or $source->{Extension}; +</%init> diff --git a/rt/share/html/Elements/Logo b/rt/share/html/Elements/Logo index 23c73e845..80e86975d 100644 --- a/rt/share/html/Elements/Logo +++ b/rt/share/html/Elements/Logo @@ -53,9 +53,7 @@ % } else { <a href="<%$ARGS{'LogoLinkURL'}||RT->Config->Get('LogoLinkURL')%>"><img src="<%$ARGS{'LogoURL'}||RT->Config->Get('LogoURL')%>" - alt="<%loc($ARGS{'LogoAltText'}||RT->Config->Get('LogoAltText'))%>" - width="<%$ARGS{'LogoImageWidth'}||RT->Config->Get('LogoImageWidth')%>" - height="<%$ARGS{'LogoImageHeight'}||RT->Config->Get('LogoImageHeight')%>" /></a> + alt="<%loc($ARGS{'LogoAltText'}||RT->Config->Get('LogoAltText'))%>" /></a> % } % if ( $ShowName ) { <span class="rtname"><% $Name || loc("RT for [_1]", RT->Config->Get('rtname')) %></span> @@ -67,7 +65,6 @@ if ( exists $ARGS{'show_name'} ) { $ShowName = delete $ARGS{'show_name'}; } -use Scalar::Util qw(blessed); my $user_logo = blessed $RT::System ? $RT::System->FirstAttribute('UserLogo') : undef; # If we have the attribute, but no content, we don't really have a user logo diff --git a/rt/share/html/Elements/MakeClicky b/rt/share/html/Elements/MakeClicky index 4607ba092..4d28771e2 100644 --- a/rt/share/html/Elements/MakeClicky +++ b/rt/share/html/Elements/MakeClicky @@ -50,7 +50,7 @@ use Regexp::Common qw(URI); my $escaper = sub { my $content = shift; - RT::Interface::Web::EscapeUTF8( \$content ); + RT::Interface::Web::EscapeHTML( \$content ); return $content; }; @@ -61,15 +61,19 @@ my %actions = ( }, url => sub { my %args = @_; + my $post = ""; + $post = ")" if $args{value} !~ /\(/ and $args{value} =~ s/\)$//; $args{value} = $escaper->($args{value}); - my $result = qq{[<a target="new" href="$args{value}">}. loc('Open URL') .qq{</a>]}; - return $args{value} . qq{ <span class="clickylink">$result</span>}; + my $result = qq{[<a target="_blank" href="$args{value}">}. loc('Open URL') .qq{</a>]}; + return $args{value} . qq{ <span class="clickylink">$result</span>$post}; }, url_overwrite => sub { my %args = @_; + my $post = ""; + $post = ")" if $args{value} !~ /\(/ and $args{value} =~ s/\)$//; $args{value} = $escaper->($args{value}); - my $result = qq{<a target="new" href="$args{value}">$args{value}</a>}; - return qq{<span class="clickylink">$result</span>}; + my $result = qq{<a target="_blank" href="$args{value}">$args{value}</a>}; + return qq{<span class="clickylink">$result</span>$post}; }, ); @@ -144,14 +148,14 @@ if ( defined $cache ) { } unless ( $regexp ) { - RT::Interface::Web::EscapeUTF8( $content ) unless $html; + RT::Interface::Web::EscapeHTML( $content ) unless $html; return; } my $pos = 0; while ( $$content =~ /($regexp)/gsio ) { my $match = $1; - next if $` =~ /href=(?:"|")$/; + next if $` =~ /\w+=(?:"|")$/; my $skipped_len = pos($$content) - $pos - length($match); if ( $skipped_len > 0 ) { my $plain; diff --git a/rt/share/html/Elements/Menu b/rt/share/html/Elements/Menu index 16535c825..fb109a91f 100755 --- a/rt/share/html/Elements/Menu +++ b/rt/share/html/Elements/Menu @@ -45,36 +45,6 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -% return unless ($menu); -<%" " x $depth%><ul<%$id ? ' id="'.$id.'"' : '' |n%><% $toplevel? ' class="toplevel"' : '' |n %>> -% for my $child ($menu->children) { -% my $item_id = lc(($parent_id? $parent_id."-" : "") .$child->key); -% $item_id =~ s/\s/-/g; -% my @classes; -% push @classes, 'has-children' if $child->has_children; -% push @classes, 'active' if $child->active; -<%" " x ($depth+1)%><li id="li-<%$item_id%>"\ -% if (@classes) { - class="<% join ' ', @classes %>"\ -% } ->\ -% if ($child->raw_html) { -<% $child->raw_html |n %> -% } else { -% my $url = $m->interp->apply_escapes((not $child->path or $child->path =~ m{^\w+:/}) ? $child->path : RT->Config->Get('WebPath').$child->path, 'h'); -<a id="<%$item_id%>" class="menu-item <% $child->class || '' %>"<% $child->path ? ' href="'.$url.'"' : '' |n%><% $child->target ? ' target="'.$m->interp->apply_escapes($child->target, 'h').'"' : '' |n %>>\ -<% $child->escape_title ? $m->interp->apply_escapes($child->title, 'h') : $child->title |n %>\ -</a>\ -% } -% if ($child->has_children) { - -<& Menu, menu => $child, toplevel => 0, parent_id => ($parent_id? $parent_id."-": '').$child->key, depth=> ($depth+1) &> -<%" " x ($depth+1)%></li> -% } else { -</li> -% } -% } -<%" " x $depth%></ul>\ <%ARGS> $menu $id => undef @@ -82,6 +52,6 @@ $toplevel => 1 $parent_id => '' $depth => 0 </%ARGS> -<%init> -$id = $m->interp->apply_escapes($id, 'h'); -</%init> +<%INIT> +RenderMenu( %ARGS ); +</%INIT> diff --git a/rt/share/html/Elements/MessageBox b/rt/share/html/Elements/MessageBox index c4a2de98c..bcc64d403 100755 --- a/rt/share/html/Elements/MessageBox +++ b/rt/share/html/Elements/MessageBox @@ -45,13 +45,13 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<textarea autocomplete="off" class="messagebox" <% $width_attr %>="<% $Width %>" rows="<% $Height %>" <% $wrap_type |n %> name="<% $Name %>" id="<% $Name %>">\ +<textarea autocomplete="off" class="messagebox <% $Type eq 'text/html' ? 'richtext' : '' %>" <% $width_attr %>="<% $Width %>" rows="<% $Height %>" <% $wrap_type |n %> name="<% $Name %>" id="<% $Name %>">\ % $m->comp('/Articles/Elements/IncludeArticle', %ARGS) if $IncludeArticle; % $m->callback( %ARGS, SignatureRef => \$signature ); <% $Default || '' %><% $message %><% $signature %></textarea> % $m->callback( %ARGS, CallbackName => 'AfterTextArea' ); -% if (RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'})) { -<input type="text" style="display:none" name="<% $Name %>Type" id="<% $Name %>Type" value="<% $m->request_args->{$Name."Type"}||'' %>" /> +% if ($Type eq 'text/html') { +<input type="text" style="display:none" name="<% $Name %>Type" id="<% $Name %>Type" value="<% $m->request_args->{$Name."Type"}||$Type %>" /> % } <%INIT> @@ -60,19 +60,25 @@ my $message = ''; if ( $QuoteTransaction ) { my $transaction = RT::Transaction->new( $session{'CurrentUser'} ); $transaction->Load( $QuoteTransaction ); - $message = $transaction->Content( Quote => 1 ); + $message = $transaction->Content( Quote => 1, Type => $Type ); } my $signature = ''; if ( $IncludeSignature and my $text = $session{'CurrentUser'}->UserObj->Signature ) { - $signature = "-- \n". $text; + $signature = "-- \n". $text; + if ($Type eq 'text/html') { + $signature =~ s/&/&/g; + $signature =~ s/</</g; + $signature =~ s/>/>/g; + $signature =~ s/"/"/g; # "//; + $signature =~ s/'/'/g; # '//; + $signature =~ s{\n}{<br />}g; + $signature = "<p>$signature</p>"; + } } # wrap="something" seems to really break IE + richtext -my $wrap_type = ''; -if ( not RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'}) ) { - $wrap_type = 'wrap="' . $m->interp->apply_escapes($Wrap, 'h') . '"'; -} +my $wrap_type = $Type eq 'text/html' ? '' : 'wrap="soft"'; # If there's no cols specified, we want to set the width to 100% in CSS my $width_attr; @@ -90,7 +96,7 @@ $Name => 'Content' $Default => '' $Width => RT->Config->Get('MessageBoxWidth', $session{'CurrentUser'} ) $Height => RT->Config->Get('MessageBoxHeight', $session{'CurrentUser'} ) || 15 -$Wrap => RT->Config->Get('MessageBoxWrap', $session{'CurrentUser'} ) || 'SOFT' $IncludeSignature => RT->Config->Get('MessageBoxIncludeSignature'); $IncludeArticle => 1; +$Type => RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'}) ? 'text/html' : 'text/plain'; </%ARGS> diff --git a/rt/share/html/Elements/MyRT b/rt/share/html/Elements/MyRT index e8b084660..0d07eaa54 100644 --- a/rt/share/html/Elements/MyRT +++ b/rt/share/html/Elements/MyRT @@ -53,9 +53,9 @@ % $show_cb->($_) foreach @$body; </td> -% if ( $summary ) { +% if ( $sidebar ) { <td class="boxcontainer"> -% $show_cb->($_) foreach @$summary; +% $show_cb->($_) foreach @$sidebar; </td> % } @@ -63,24 +63,24 @@ % $m->callback( ARGSRef => \%ARGS, CallbackName => 'AfterTable' ); <%INIT> -# XXX: we don't use this, but should. my %allowed_components = map {$_ => 1} @{RT->Config->Get('HomepageComponents')}; my $user = $session{'CurrentUser'}->UserObj; -$Portlets ||= $user->Preferences('HomepageSettings'); unless ( $Portlets ) { my ($defaults) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings'); - $Portlets = $defaults ? $defaults->Content : {}; + $Portlets = $user->Preferences( + HomepageSettings => $defaults ? $defaults->Content : {} + ); } $m->callback( CallbackName => 'MassagePortlets', Portlets => $Portlets ); -my ($body, $summary) = @{$Portlets}{qw(body summary)}; +my ($body, $sidebar) = @{$Portlets}{qw(body sidebar)}; unless( $body && @$body ) { - $body = $summary || []; - $summary = undef; + $body = $sidebar || []; + $sidebar = undef; } -$summary = undef unless $summary && @$summary; +$sidebar = undef unless $sidebar && @$sidebar; my $Rows = $user->Preferences( 'SummaryRows', ( RT->Config->Get('DefaultSummaryRows') || 10 ) ); @@ -89,12 +89,16 @@ my $show_cb = sub { my $type = $entry->{type}; my $name = $entry->{'name'}; if ( $type eq 'component' ) { - # XXX: security check etc. - $m->comp( $name, %{ $entry->{arguments} || {} } ); + if (!$allowed_components{$name}) { + $m->out( $m->interp->apply_escapes( loc("Invalid portlet [_1]", $name), "h" ) ); + } + else { + $m->comp( $name, %{ $entry->{arguments} || {} } ); + } } elsif ( $type eq 'system' ) { $m->comp( '/Elements/ShowSearch', Name => $name, Override => { Rows => $Rows } ); } elsif ( $type eq 'saved' ) { - $m->comp( '/Elements/ShowSearch', SavedSearch => $name, Override => { Rows => $Rows }, IgnoreMissing => 1 ); + $m->comp( '/Elements/ShowSearch', SavedSearch => $name, Override => { Rows => $Rows } ); } else { $RT::Logger->error("unknown portlet type '$type'"); } diff --git a/rt/share/html/Elements/MyReminders b/rt/share/html/Elements/MyReminders index 7619808f8..f4fbf5de6 100755 --- a/rt/share/html/Elements/MyReminders +++ b/rt/share/html/Elements/MyReminders @@ -45,7 +45,6 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -%# DEPRECATED <&|/Widgets/TitleBox, class => 'reminders', title => loc("My reminders"), diff --git a/rt/share/html/Elements/MyRequests b/rt/share/html/Elements/MyRequests deleted file mode 100755 index 357476cfd..000000000 --- a/rt/share/html/Elements/MyRequests +++ /dev/null @@ -1,49 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2015 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 }}} -%# DEPRECATED -<& /Elements/ShowSearch, Name => 'My Requests' &> diff --git a/rt/share/html/Elements/PageLayout b/rt/share/html/Elements/PageLayout index 9e8aecb20..edd71d11e 100755 --- a/rt/share/html/Elements/PageLayout +++ b/rt/share/html/Elements/PageLayout @@ -55,6 +55,11 @@ % if (0) { ## new ticket via customer, and we already have a ticket search box <div id="topactions"><& /Elements/WidgetBar, menu => PageWidgets() &></div> % } + +% if ($m->comp_exists($stylesheet_plugin) ) { +<& $stylesheet_plugin &> +% } + <div id="body"> % $m->callback( %ARGS, CallbackName => 'BeforeBody' ); % $m->flush_buffer(); # we've got the page laid out, let's flush the buffer; @@ -62,3 +67,9 @@ $title => $m->callers(-1)->path $show_menu => 1 </%ARGS> +<%init> +my $style = $session{'CurrentUser'} + ? $session{'CurrentUser'}->Stylesheet + : RT->Config->Get('WebDefaultStylesheet'); +my $stylesheet_plugin = "/NoAuth/css/".$style."/AfterMenus"; +</%init> diff --git a/rt/share/html/Elements/QueryString b/rt/share/html/Elements/QueryString index 8bff988c6..447cc5dbe 100644 --- a/rt/share/html/Elements/QueryString +++ b/rt/share/html/Elements/QueryString @@ -54,11 +54,12 @@ for my $key (sort keys %ARGS) { if( UNIVERSAL::isa( $value, 'ARRAY' ) ) { push @params, map $key ."=". $m->interp->apply_escapes( $_, 'u' ), + map defined $_? $_ : '', @$value; } else { push @params, $key ."=". $m->interp->apply_escapes($value, 'u'); } } -return join '&', sort(@params); +return join '&', @params; </%INIT> diff --git a/rt/share/html/Elements/QueueSummaryByLifecycle b/rt/share/html/Elements/QueueSummaryByLifecycle index da31ebb59..f21cb20c3 100644 --- a/rt/share/html/Elements/QueueSummaryByLifecycle +++ b/rt/share/html/Elements/QueueSummaryByLifecycle @@ -94,11 +94,14 @@ my $build_search_link = sub { my $link_all = sub { my ($queue, $all_statuses) = @_; - return $build_search_link->($queue->{Name}, "(".join(" OR ", map "Status = '$_'", @$all_statuses).")"); + my @escaped = @{$all_statuses}; + s{(['\\])}{\\$1}g for @escaped; + return $build_search_link->($queue->{Name}, "(".join(" OR ", map "Status = '$_'", @escaped).")"); }; my $link_status = sub { my ($queue, $status) = @_; + $status =~ s{(['\\])}{\\$1}g; return $build_search_link->($queue->{Name}, "Status = '$status'"); }; @@ -120,14 +123,14 @@ $m->callback( CallbackName => 'Filter', Queues => \@queues ); { id => $_->Id, Name => $_->Name, Description => $_->Description || '', - Lifecycle => $_->Lifecycle->Name, + Lifecycle => $_->Lifecycle, } } grep $_, @queues; my %lifecycle; for my $queue (@queues) { - my $cycle = RT::Lifecycle->Load( $queue->{'Lifecycle'} ); + my $cycle = RT::Lifecycle->Load( Name => $queue->{'Lifecycle'} ); $lifecycle{ lc $cycle->Name } = $cycle; } @@ -145,9 +148,11 @@ my $statuses = {}; use RT::Report::Tickets; my $report = RT::Report::Tickets->new( RT->SystemUser ); +my @escaped = @statuses; +s{(['\\])}{\\$1}g for @escaped; my $query = "(". - join(" OR ", map {s{(['\\])}{\\$1}g; "Status = '$_'"} @statuses) #' + join(" OR ", map {"Status = '$_'"} @escaped) #' .") AND (". join(' OR ', map "Queue = ".$_->{id}, @queues) .")"; diff --git a/rt/share/html/Elements/QueueSummaryByStatus b/rt/share/html/Elements/QueueSummaryByStatus index 3acf9c9f4..704bca61d 100644 --- a/rt/share/html/Elements/QueueSummaryByStatus +++ b/rt/share/html/Elements/QueueSummaryByStatus @@ -119,14 +119,14 @@ $m->callback( CallbackName => 'Filter', Queues => \@queues ); { id => $_->Id, Name => $_->Name, Description => $_->Description || '', - Lifecycle => $_->Lifecycle->Name, + Lifecycle => $_->Lifecycle, } } grep $_, @queues; my %lifecycle; for my $queue (@queues) { - my $cycle = RT::Lifecycle->Load( $queue->{'Lifecycle'} ); + my $cycle = RT::Lifecycle->Load( Name => $queue->{'Lifecycle'} ); $lifecycle{ lc $cycle->Name } = $cycle; } diff --git a/rt/share/html/Elements/QuickCreate b/rt/share/html/Elements/QuickCreate index fa03f2061..b016314b3 100644 --- a/rt/share/html/Elements/QuickCreate +++ b/rt/share/html/Elements/QuickCreate @@ -69,7 +69,7 @@ </tr> <tr class="input-row"> <td class="label"><&|/l&>Requestors</&>:</td> - <td colspan="3" class="value"><& /Elements/EmailInput, Name => 'Requestors', Size => '40', Default => $args->{Requestors} || $session{CurrentUser}->EmailAddress &></td> + <td colspan="3" class="value"><& /Elements/EmailInput, Name => 'Requestors', Size => '40', Default => $args->{Requestors} || $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1 &></td> </tr> <tr class="input-row"> <td class="labeltop"><&|/l&>Content</&>:</td> diff --git a/rt/share/html/Elements/RT__Article/ColumnMap b/rt/share/html/Elements/RT__Article/ColumnMap index 4abb068c2..5c904274d 100644 --- a/rt/share/html/Elements/RT__Article/ColumnMap +++ b/rt/share/html/Elements/RT__Article/ColumnMap @@ -48,6 +48,7 @@ <%ARGS> $Name => undef $Attr => undef +$GenericMap => {} </%ARGS> @@ -75,16 +76,6 @@ $COLUMN_MAP = { title => 'Class', # loc value => sub { $_[0]->ClassObj->Name }, }, - CreatedRelative => { - attribute => 'Created', - title => 'Created', # loc - value => sub { $_[0]->CreatedObj->AgeAsString }, - }, - LastUpdatedRelative => { - attribute => 'LastUpdated', - title => 'LastUpdated', # loc - value => sub { $_[0]->LastUpdatedObj->AgeAsString }, - }, Topics => { title => 'Topics', # loc value => sub { @@ -102,6 +93,6 @@ $COLUMN_MAP = { </%ONCE> <%init> -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'Once', CallbackOnce => 1 ); +$m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'Once', CallbackOnce => 1 ); return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); </%init> diff --git a/rt/share/html/Elements/RT__Class/ColumnMap b/rt/share/html/Elements/RT__Class/ColumnMap index 091606571..8bc8e4ab8 100644 --- a/rt/share/html/Elements/RT__Class/ColumnMap +++ b/rt/share/html/Elements/RT__Class/ColumnMap @@ -48,14 +48,10 @@ <%ARGS> $Name $Attr => undef +$GenericMap => {} </%ARGS> <%ONCE> my $COLUMN_MAP = { - id => { - title => '#', # loc - attribute => 'id', - value => sub { return $_[0]->id }, - }, Name => { title => 'Name', # loc attribute => 'Name', @@ -66,11 +62,16 @@ my $COLUMN_MAP = { attribute => 'Description', value => sub { return $_[0]->Description() }, }, + Disabled => { + title => 'Status', # loc + attribute => 'Disabled', + value => sub { return $_[0]->Disabled? $_[0]->loc('Disabled'): $_[0]->loc('Enabled') }, + }, }; </%ONCE> <%INIT> -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); +$m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); </%INIT> diff --git a/rt/share/html/Elements/RT__CustomField/ColumnMap b/rt/share/html/Elements/RT__CustomField/ColumnMap index 765be2925..764eaca6b 100644 --- a/rt/share/html/Elements/RT__CustomField/ColumnMap +++ b/rt/share/html/Elements/RT__CustomField/ColumnMap @@ -48,17 +48,12 @@ <%ARGS> $Name => undef $Attr => undef +$GenericMap => {} </%ARGS> <%ONCE> my $COLUMN_MAP = { - id => { - title => '#', # loc - attribute => 'id', - align => 'right', - value => sub { return $_[0]->id }, - }, Disabled => { - title => \' ', + title => 'Status', # loc attribute => 'Disabled', value => sub { return $_[0]->Disabled? $_[0]->loc('Disabled'): $_[0]->loc('Enabled') }, }, @@ -66,7 +61,7 @@ my $COLUMN_MAP = { map( { my $c = $_; $c => { title => $c, attribute => $c, - value => sub { return $_[0]->$c() }, + value => sub { return $_[0]->$c() }, } } qw(Name Description Type LookupType Pattern) ), @@ -74,26 +69,26 @@ my $COLUMN_MAP = { { my $c = $_; my $short = $c; $short =~ s/^Friendly//; $c => { title => $short, attribute => $short, - value => sub { return $_[0]->$c() }, + value => sub { return $_[0]->$c() }, } } qw(FriendlyLookupType FriendlyType FriendlyPattern) ), MaxValues => { title => 'MaxValues', # loc - attribute => 'MaxValues', - value => sub { + attribute => 'MaxValues', + value => sub { my $v = $_[0]->MaxValues; return !$v ? $_[0]->loc('unlimited') : $v == 0 ? $_[0]->loc('one') : $v; }, }, - AppliedTo => { - title => 'Applied', # loc - value => sub { - if ( $_[0]->IsApplied ) { + AddedTo => { + title => 'Added', # loc + value => sub { + if ( $_[0]->IsGlobal ) { return $_[0]->loc('Global'); } - my $collection = $_[0]->AppliedTo; + my $collection = $_[0]->AddedTo; return '' unless $collection; $collection->RowsPerPage(10); @@ -121,13 +116,13 @@ my $COLUMN_MAP = { my $checked = $DECODED_ARGS->{ $name .'All' }? 'checked="checked"': ''; return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked - onclick="setCheckbox(this.form, }, + onclick="setCheckbox(this, }, $m->interp->apply_escapes($name,'j'), - \qq{, this.checked)" />}; + \qq{)" />}; }, value => sub { my $id = $_[0]->id; - return '' if $_[0]->IsApplied; + return '' if $_[0]->IsGlobal; my $name = 'RemoveCustomField'; my $arg = $DECODED_ARGS->{ $name }; @@ -148,7 +143,7 @@ my $COLUMN_MAP = { my $id = $_[0]->id; my $context = $_[2] || 0; - return '' unless $_[0]->IsApplied( $context ); + return '' unless $_[0]->IsAdded( $context ); my $name = 'MoveCustomField'; my $args = $m->caller_args( 1 ); @@ -173,8 +168,10 @@ my $COLUMN_MAP = { }, }; +$COLUMN_MAP->{'AppliedTo'} = $COLUMN_MAP->{'AddedTo'}; + </%ONCE> <%INIT> -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); +$m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); </%INIT> diff --git a/rt/share/html/Elements/RT__Dashboard/ColumnMap b/rt/share/html/Elements/RT__Dashboard/ColumnMap index 3cc7c201a..e9509a219 100644 --- a/rt/share/html/Elements/RT__Dashboard/ColumnMap +++ b/rt/share/html/Elements/RT__Dashboard/ColumnMap @@ -48,15 +48,10 @@ <%ARGS> $Name $Attr => undef +$GenericMap => {} </%ARGS> <%ONCE> my $COLUMN_MAP = { - id => { - title => '#', # loc - attribute => 'id', - align => 'right', - value => sub { return $_[0]->Id }, - }, Name => { title => 'Name', # loc attribute => 'Name', @@ -129,6 +124,6 @@ my $COLUMN_MAP = { </%ONCE> <%INIT> -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); +$m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); </%INIT> diff --git a/rt/share/html/Elements/RT__Group/ColumnMap b/rt/share/html/Elements/RT__Group/ColumnMap index e336f771b..a1d2558ce 100644 --- a/rt/share/html/Elements/RT__Group/ColumnMap +++ b/rt/share/html/Elements/RT__Group/ColumnMap @@ -48,15 +48,10 @@ <%ARGS> $Name $Attr => undef +$GenericMap => {} </%ARGS> <%ONCE> my $COLUMN_MAP = { - id => { - title => '#', # loc - attribute => 'id', - align => 'right', - value => sub { return $_[0]->id }, - }, HasMember => { title => 'Member', # loc value => sub { @@ -75,18 +70,22 @@ my $COLUMN_MAP = { }, Name => { title => 'Name', # loc - attribute => 'Name', - value => sub { return $_[0]->Name() }, + attribute => 'Name', + value => sub { return $_[0]->Name() }, }, Description => { title => 'Description', # loc - attribute => 'Description', - value => sub { return $_[0]->Description() }, + attribute => 'Description', + value => sub { return $_[0]->Description() }, + }, + Disabled => { + title => 'Status', # loc + value => sub { return $_[0]->Disabled? $_[0]->loc('Disabled'): $_[0]->loc('Enabled') }, }, }; </%ONCE> <%INIT> -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); +$m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); </%INIT> diff --git a/rt/share/html/Elements/RT__Queue/ColumnMap b/rt/share/html/Elements/RT__Queue/ColumnMap index 8bc333167..1878abeb5 100644 --- a/rt/share/html/Elements/RT__Queue/ColumnMap +++ b/rt/share/html/Elements/RT__Queue/ColumnMap @@ -48,17 +48,12 @@ <%ARGS> $Name => undef $Attr => undef +$GenericMap => {} </%ARGS> <%ONCE> my $COLUMN_MAP = { - id => { - title => '#', # loc - attribute => 'id', - align => 'right', - value => sub { return $_[0]->id }, - }, Disabled => { - title => \' ', + title => 'Status', # loc attribute => 'Disabled', value => sub { return $_[0]->Disabled? $_[0]->loc('Disabled'): $_[0]->loc('Enabled') }, }, @@ -88,7 +83,15 @@ my $COLUMN_MAP = { Lifecycle => { title => 'Lifecycle', attribute => 'Lifecycle', - value => sub { return $_[0]->Lifecycle->Name }, + value => sub { return $_[0]->Lifecycle }, + }, + ScripStage => { + title => 'Stage', # loc + value => sub { + my $os = RT::ObjectScrip->new( $_[0]->CurrentUser ); + $os->LoadByCols( Scrip => $_[-1], ObjectId => $_[0]->id ); + return $_[0]->loc( $os->FriendlyStage ); + }, }, }; @@ -105,7 +108,7 @@ foreach my $field (qw( </%ONCE> <%INIT> -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); +$m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); </%INIT> diff --git a/rt/share/html/Elements/RT__SavedSearch/ColumnMap b/rt/share/html/Elements/RT__SavedSearch/ColumnMap index dd02cbcc7..12a708154 100644 --- a/rt/share/html/Elements/RT__SavedSearch/ColumnMap +++ b/rt/share/html/Elements/RT__SavedSearch/ColumnMap @@ -48,15 +48,10 @@ <%ARGS> $Name $Attr => undef +$GenericMap => {} </%ARGS> <%ONCE> my $COLUMN_MAP = { - id => { - title => '#', # loc - attribute => 'id', - align => 'right', - value => sub { return $_[0]->Id }, - }, Name => { title => 'Name', # loc attribute => 'Name', @@ -80,6 +75,6 @@ my $COLUMN_MAP = { </%ONCE> <%INIT> -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); +$m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); </%INIT> diff --git a/rt/share/html/Elements/RT__Scrip/ColumnMap b/rt/share/html/Elements/RT__Scrip/ColumnMap index eb2f06566..e51d93874 100644 --- a/rt/share/html/Elements/RT__Scrip/ColumnMap +++ b/rt/share/html/Elements/RT__Scrip/ColumnMap @@ -48,25 +48,38 @@ <%ARGS> $Name $Attr => undef +$GenericMap => {} </%ARGS> <%ONCE> my $COLUMN_MAP = { - id => { - title => '#', # loc - attribute => 'id', - align => 'right', - value => sub { return $_[0]->id }, - }, Queue => { title => 'Queue', # loc value => sub { - return $_[0]->QueueObj->Name if $_[0]->Queue; - return $_[0]->loc('Global'); + return $_[0]->loc('Global') if $_[0]->IsGlobal; + return join(", ", map {$_->Name} @{$_[0]->AddedTo->ItemsArrayRef}); }, }, QueueId => { title => 'Queue', # loc - value => sub { $_[0]->Queue }, + value => sub { + return 0 if $_[0]->IsGlobal; + return join(", ", map {$_->Id} @{$_[0]->AddedTo->ItemsArrayRef}); + }, + }, + From => { + title => 'Queue', + value => sub { + my $request_path = $HTML::Mason::Commands::r->path_info; + my $queue_id = $m->request_args->{'id'}; + if ( $request_path =~ m{/Admin/Queues/Scrips\.html} and $queue_id ) { + return '&From=' . $queue_id; + } elsif ( $request_path =~ m{/Admin/Global/Scrips\.html} ) { + return '&From=Global'; + } + else { + return q{}; + } + }, }, Condition => { title => 'Condition', # loc @@ -78,30 +91,93 @@ my $COLUMN_MAP = { }, Template => { title => 'Template', # loc - value => sub { return $_[0]->loc( $_[0]->TemplateObj->Name ) }, + value => sub { return $_[0]->loc( $_[0]->Template ) }, }, AutoDescription => { title => 'Condition, Action and Template', # loc value => sub { return $_[0]->loc( "[_1] [_2] with template [_3]", $_[0]->loc($_[0]->ConditionObj->Name), $_[0]->loc($_[0]->ActionObj->Name), - $_[0]->loc($_[0]->TemplateObj->Name), + $_[0]->loc($_[0]->Template), ) }, }, Description => { title => 'Description', # loc - attribute => 'Description', - value => sub { return $_[0]->Description() }, + attribute => 'Description', + value => sub { return $_[0]->Description() }, + }, + Disabled => { + title => 'Status', # loc + attribute => 'Disabled', + value => sub { return $_[0]->Disabled? $_[0]->loc('Disabled'): $_[0]->loc('Enabled') }, + }, + RemoveCheckBox => { + title => sub { + my $name = 'RemoveScrip'; + my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': ''; + + return \qq{<input type="checkbox" name="${name}All" value="1" $checked + onclick="setCheckbox(this, '$name')" />}; + }, + value => sub { + my $id = $_[0]->id; + return '' if $_[0]->IsGlobal; + + my $name = 'RemoveScrip'; + my $arg = $m->request_args->{ $name }; + + my $checked = ''; + if ( $arg && ref $arg ) { + $checked = 'checked="checked"' if grep $_ == $id, @$arg; + } + elsif ( $arg ) { + $checked = 'checked="checked"' if $arg == $id; + } + return \qq{<input type="checkbox" name="$name" value="$id" $checked />} + }, + }, + Move => { + title => 'Move', # loc + value => sub { + my $id = $_[0]->id; + + my $context = $_[2] || 0; + return '' unless $_[0]->IsAdded( $context ); + + my $name = 'MoveScrip'; + my $args = $m->caller_args( 1 ); + my @pass = ref $args->{'PassArguments'} + ? @{$args->{'PassArguments'}} + : ($args->{'PassArguments'}); + my %pass = map { $_ => $args->{$_} } grep exists $args->{$_}, @pass; + + my $uri = RT->Config->Get('WebPath') . $m->request_path; + + my @res = ( + \'<a href="', + $uri .'?'. $m->comp("/Elements/QueryString", %pass, MoveScripUp => $id ), + \'">', loc('[Up]'), \'</a>', + \' <a href="', + $uri .'?'. $m->comp("/Elements/QueryString", %pass, MoveScripDown => $id ), + \'">', loc('[Down]'), \'</a>' + ); + + return @res; + }, }, Stage => { - title => 'Stage', # loc - attribute => 'Stage', - value => sub { return $_[0]->Stage() }, + title => 'Stage', # loc + value => sub { + my $os = RT::ObjectScrip->new( $_[0]->CurrentUser ); + my $id = $_[0]->IsGlobal ? 0 : $_[-1]; + $os->LoadByCols( Scrip => $_[0]->id, ObjectId => $id ); + return $_[0]->loc( $os->FriendlyStage ); + }, }, }; </%ONCE> <%INIT> -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); +$m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); </%INIT> diff --git a/rt/share/html/Elements/RT__Template/ColumnMap b/rt/share/html/Elements/RT__Template/ColumnMap index da712c281..142b6950d 100644 --- a/rt/share/html/Elements/RT__Template/ColumnMap +++ b/rt/share/html/Elements/RT__Template/ColumnMap @@ -48,24 +48,19 @@ <%ARGS> $Name $Attr => undef +$GenericMap => {} </%ARGS> <%ONCE> my $COLUMN_MAP = { - id => { - title => '#', # loc - attribute => 'id', - align => 'right', - value => sub { return $_[0]->id }, - }, Name => { title => 'Name', # loc - attribute => 'Name', - value => sub { return $_[0]->Name() }, + attribute => 'Name', + value => sub { return $_[0]->Name() }, }, Description => { title => 'Description', # loc - attribute => 'Description', - value => sub { return $_[0]->Description() }, + attribute => 'Description', + value => sub { return $_[0]->Description() }, }, Queue => { title => 'Queue', # loc @@ -78,10 +73,28 @@ my $COLUMN_MAP = { title => 'Queue', # loc value => sub { $_[0]->Queue }, }, + IsEmpty => { + title => 'Empty', # loc + value => sub { $_[0]->IsEmpty? $_[0]->loc('Yes') : $_[0]->loc('No') }, + }, + UsedBy => { + title => 'Used by scrips', # loc + value => sub { + my @res; + my $scrips = $_[0]->UsedBy; + while ( my $scrip = $scrips->Next ) { + push @res, ', ' if @res; + push @res, \'<a href="', RT->Config->Get('WebPath'), '/Admin/Scrips/Modify.html'; + push @res, '?id='. $scrip->id; + push @res, \'" title="', $scrip->Description, \'">', $scrip->id, \'</a>'; + } + return @res; + }, + }, }; </%ONCE> <%INIT> -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); +$m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); </%INIT> diff --git a/rt/share/html/Elements/RT__Ticket/ColumnMap b/rt/share/html/Elements/RT__Ticket/ColumnMap index 1668d3207..cde8d59e1 100644 --- a/rt/share/html/Elements/RT__Ticket/ColumnMap +++ b/rt/share/html/Elements/RT__Ticket/ColumnMap @@ -48,6 +48,7 @@ <%ARGS> $Name => undef $Attr => undef +$GenericMap => {} </%ARGS> @@ -57,20 +58,36 @@ my $COLUMN_MAP; my $LinkCallback = sub { my $method = shift; - my $mode = $RT::Ticket::LINKTYPEMAP{$method}{Mode}; - my $type = $RT::Ticket::LINKTYPEMAP{$method}{Type}; + my $mode = $RT::Link::TYPEMAP{$method}{Mode}; + my $type = $RT::Link::TYPEMAP{$method}{Type}; my $other_mode = ($mode eq "Target" ? "Base" : "Target"); my $mode_uri = $mode.'URI'; - my $local_type = 'Local'.$mode; return sub { + my $ObjectType = $_[2]||''; map { \'<a href="', - $_->$mode_uri->Resolver->HREF, + $_->$mode_uri->AsHREF, \'">', - ( $_->$mode_uri->IsLocal && $_->$local_type ? $_->$local_type : $_->$mode_uri->Resolver->AsString ), + ( $_->$mode_uri->AsString ), \'</a><br />', - } @{ $_[0]->Links($other_mode,$type)->ItemsArrayRef } + } # if someone says __RefersTo.{Ticket}__ filter for only local links that are tickets + grep { $ObjectType + ? ( $_->$mode_uri->IsLocal + && ( $_->$mode_uri->Object->RecordType eq $ObjectType )) + : 1 + } + @{ $_[0]->Links($other_mode,$type)->ItemsArrayRef } + } +}; + +my $trustSub = sub { + my $user = shift; + my %key = RT::Crypt->GetKeyInfo( Key => $user->EmailAddress ); + if (!defined $key{'info'}) { + return $m->interp->apply_escapes(' ' . loc("(no pubkey!)"), "h"); + } elsif ($key{'info'}{'TrustLevel'} == 0) { + return $m->interp->apply_escapes(' ' . loc("(untrusted!)"), "h"); } }; @@ -118,7 +135,7 @@ $COLUMN_MAP = { my $SearchURL = RT->Config->Get('WebPath') . '/Search/Results.html?' . $m->comp('/Elements/QueryString', Query => $Query); - return \'<a href="',$SearchURL,\'">', loc('(pending [quant,_1,other ticket])',$count), \'</a>'; + return \'<a href="',$SearchURL,\'">', loc('(pending [quant,_1,other ticket,other tickets])',$count), \'</a>'; } } else { @@ -169,21 +186,6 @@ $COLUMN_MAP = { title => 'Time Estimated', # loc value => sub { return $_[0]->TimeEstimated } }, - Requestors => { - title => 'Requestors', # loc - attribute => 'Requestor.EmailAddress', - value => sub { return $_[0]->Requestors->MemberEmailAddressesAsString } - }, - Cc => { - title => 'Cc', # loc - attribute => 'Cc.EmailAddress', - value => sub { return $_[0]->Cc->MemberEmailAddressesAsString } - }, - AdminCc => { - title => 'AdminCc', # loc - attribute => 'AdminCc.EmailAddress', - value => sub { return $_[0]->AdminCc->MemberEmailAddressesAsString } - }, StartsRelative => { title => 'Starts', # loc attribute => 'Starts', @@ -205,7 +207,7 @@ $COLUMN_MAP = { value => sub { my $date = $_[0]->DueObj; # Highlight the date if it was due in the past, and it's still active - if ( $date && $date->Unix > 0 && $date->Diff < 0 && $_[0]->QueueObj->IsActiveStatus($_[0]->Status)) { + if ( $date && $date->IsSet && $date->Diff < 0 && $_[0]->QueueObj->IsActiveStatus($_[0]->Status)) { return (\'<span class="overdue">' , $date->AgeAsString , \'</span>'); } else { return $date->AgeAsString; @@ -264,21 +266,7 @@ $COLUMN_MAP = { KeyRequestors => { title => 'Requestors', # loc attribute => 'Requestor.EmailAddress', - value => sub { - my $t = shift; - my @requestors = $t->Requestors->MemberEmailAddresses; - for my $email (@requestors) - { - my %key = RT::Crypt::GnuPG::GetKeyInfo($email); - if (!defined $key{'info'}) { - $email .= ' ' . loc("(no pubkey!)"); - } - elsif ($key{'info'}{'TrustLevel'} == 0) { - $email .= ' ' . loc("(untrusted!)"); - } - } - return join ', ', @requestors; - } + value => sub { return \($m->scomp("/Elements/ShowPrincipal", Object => $_[0]->Requestor, PostUser => $trustSub ) ) } }, KeyOwnerName => { title => 'Owner', # loc @@ -286,7 +274,7 @@ $COLUMN_MAP = { value => sub { my $t = shift; my $name = $t->OwnerObj->Name; - my %key = RT::Crypt::GnuPG::GetKeyInfo($t->OwnerObj->EmailAddress); + my %key = RT::Crypt->GetKeyInfo( Key => $t->OwnerObj->EmailAddress ); if (!defined $key{'info'}) { $name .= ' '. loc("(no pubkey!)"); } @@ -297,11 +285,16 @@ $COLUMN_MAP = { return $name; } }, + KeyOwner => { + title => 'Owner', # loc + attribute => 'Owner', + value => sub { return \($m->scomp("/Elements/ShowPrincipal", Object => $_[0]->OwnerObj, PostUser => $trustSub ) ) } + }, # Everything from LINKTYPEMAP (map { $_ => { value => $LinkCallback->( $_ ) } - } keys %RT::Ticket::LINKTYPEMAP), + } keys %RT::Link::TYPEMAP), '_CLASS' => { value => sub { return $_[1] % 2 ? 'oddline' : 'evenline' } @@ -328,20 +321,15 @@ $COLUMN_MAP = { $m->comp('/Elements/CustomerFields', 'ColumnMap'), $m->comp('/Elements/ServiceFields', 'ColumnMap'), }; - -# if no GPG support, then KeyOwnerName and KeyRequestors fall back to the regular -# versions -if (RT->Config->Get('GnuPG')->{'Enable'}) { - require RT::Crypt::GnuPG; -} -else { - $COLUMN_MAP->{KeyOwnerName} = $COLUMN_MAP->{OwnerName}; - $COLUMN_MAP->{KeyRequestors} = $COLUMN_MAP->{Requestors}; -} </%ONCE> <%init> -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'Once', CallbackOnce => 1 ); -# backward compatibility -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap' ); +# if no encryption support, then KeyOwnerName and KeyRequestors fall back to the regular +# versions +unless (RT->Config->Get('Crypt')->{'Enable'}) { + $COLUMN_MAP->{KeyOwnerName} = $COLUMN_MAP->{OwnerName}; + $COLUMN_MAP->{KeyRequestors} = $GenericMap->{Requestors}; +} + +$m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'Once', CallbackOnce => 1 ); return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); </%init> diff --git a/rt/share/html/Elements/RT__User/ColumnMap b/rt/share/html/Elements/RT__User/ColumnMap index 9c3aa272f..3b9877c99 100644 --- a/rt/share/html/Elements/RT__User/ColumnMap +++ b/rt/share/html/Elements/RT__User/ColumnMap @@ -48,15 +48,10 @@ <%ARGS> $Name $Attr => undef +$GenericMap => {} </%ARGS> <%ONCE> my $COLUMN_MAP = { - id => { - title => '#', # loc - attribute => 'id', - align => 'right', - value => sub { return $_[0]->id }, - }, Name => { title => 'Name', # loc attribute => 'Name', @@ -147,10 +142,14 @@ my $COLUMN_MAP = { attribute => 'FreeformContactInfo', value => sub { return $_[0]->FreeformContactInfo() }, }, + Disabled => { + title => 'Status', # loc + value => sub { return $_[0]->Disabled? $_[0]->loc('Disabled'): $_[0]->loc('Enabled') }, + }, }; </%ONCE> <%INIT> -$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); +$m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); </%INIT> diff --git a/rt/share/html/Elements/Refresh b/rt/share/html/Elements/Refresh index 9a4448143..655d1b867 100755 --- a/rt/share/html/Elements/Refresh +++ b/rt/share/html/Elements/Refresh @@ -56,7 +56,7 @@ % if ( $Default && ($value == $Default)) { selected="selected" % } -><&|/l, $value/60 &>Refresh this page every [_1] minutes.</&></option> +><&|/l, $value/60 &>Refresh this page every [quant,_1,minute,minutes].</&></option> %} </select> diff --git a/rt/share/html/Elements/SelectBoolean b/rt/share/html/Elements/SelectBoolean index 79283da78..02b49e747 100755 --- a/rt/share/html/Elements/SelectBoolean +++ b/rt/share/html/Elements/SelectBoolean @@ -63,9 +63,9 @@ $False => loc("isn't") my $TrueDefault = ''; my $FalseDefault = ''; if ($Default && $Default !~ /true/i) { - $FalseDefault = 'selected="selected"'; + $FalseDefault = 'selected="selected"'; } else { - $TrueDefault = 'selected="selected"'; + $TrueDefault = 'selected="selected"'; } </%INIT> diff --git a/rt/share/html/Elements/SelectCustomFieldValue b/rt/share/html/Elements/SelectCustomFieldValue index 38f0f6205..02a95c27b 100755 --- a/rt/share/html/Elements/SelectCustomFieldValue +++ b/rt/share/html/Elements/SelectCustomFieldValue @@ -45,19 +45,21 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -% $m->callback( Name => $Name, CustomField => $CustomField ); +% $m->callback( Name => $Name, CustomField => $CustomField, Default => \$Default ); +% $Default = "" unless defined $Default; % if ($CustomField->Type =~ /Select/i) { % my $values = $CustomField->Values; <select name="<%$Name%>"> <option value="" selected="selected">-</option> <option value="NULL"><&|/l&>(no value)</&></option> % while (my $value = $values->Next) { -<option value="<%$value->Name%>"><%$value->Name%></option> +<option value="<%$value->Name%>"<% ($value->Name eq $Default) ? q[ selected="selected"] : ''%>><%$value->Name%></option> % } </select> -% } -% elsif ( $CustomField->Type eq 'Autocomplete' ) { -<input type="text" id="CF-<% $CustomField->id %>" name="<% $Name %>" size="20" /> +% } elsif ($CustomField->Type =~ /^Date(Time)?$/) { +<& /Elements/SelectDate, ShowTime => ($1 ? 1 : 0), Name => $Name, Value => $Default &> +% } elsif ( $CustomField->Type eq 'Autocomplete' ) { +<input type="text" id="CF-<% $CustomField->id %>" name="<% $Name %>" size="20" value="<% $Default %>" /> <script type="text/javascript"> % my @options; % my $values = $CustomField->Values; @@ -70,9 +72,10 @@ jQuery('#'+'CF-' + <% $CustomField->id %>).autocomplete({ source: <% JSON::to_json(\@options) |n %> }); </script> % } else { -<input name="<%$Name%>" size="20" /> +<input name="<%$Name%>" size="20" value="<% $Default %>" type="text" /> % } <%args> $Name => undef -$CustomField =>undef +$CustomField => undef +$Default => undef </%args> diff --git a/rt/share/html/Elements/SelectDate b/rt/share/html/Elements/SelectDate index 14835272b..1fd196843 100755 --- a/rt/share/html/Elements/SelectDate +++ b/rt/share/html/Elements/SelectDate @@ -47,7 +47,7 @@ %# END BPS TAGGED BLOCK }}} %# in PageLayout instead, once <% include('/elements/init_calendar.html') |n %> % $m->callback( %ARGS, Name => $Name, CallbackName => 'BeforeDateInput' ); -<input type="text" class="ui-datepicker<% $ShowTime ? ' withtime' : '' %>" id="<% $Name %>" name="<% $Name %>" value="<% $Value %>" size="<% $Size %>" /> +<input type="text" class="datepicker<% $ShowTime ? ' withtime' : '' %>" id="<% $Name %>" name="<% $Name %>" value="<% $Value %>" size="<% $Size %>" /> <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $Name %>_date_button" STYLE="cursor: pointer" TITLE="Select date"> % $m->callback( %ARGS, Name => $Name, CallbackName => 'AfterDateInput' ); <script type="text/javascript"> @@ -63,23 +63,21 @@ Calendar.setup({ }); </script> <%init> -unless ((defined $Default) or - ($current <= 0)) { - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = - localtime($current); - $Default = sprintf("%04d-%02d-%02d %02d:%02d", - $year+1900,$mon+1,$mday, - $hour,$min); +unless ((defined $Default) or ($current <= 0)) { + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = + localtime($current); + $Default = sprintf("%04d-%02d-%02d %02d:%02d", + $year+1900,$mon+1,$mday, + $hour,$min); } $Value = $Value || $Default; unless ($Name) { - $Name = $menu_prefix. "_Date"; + $Name = $menu_prefix. "_Date"; } </%init> <%args> - $ShowTime => 1 $menu_prefix=>'' $current=>time diff --git a/rt/share/html/Elements/SelectDateRelation b/rt/share/html/Elements/SelectDateRelation index 6c662fa69..74ae9a163 100755 --- a/rt/share/html/Elements/SelectDateRelation +++ b/rt/share/html/Elements/SelectDateRelation @@ -55,6 +55,6 @@ $Name => undef $Default => undef $Before => loc('before') -$On => loc('on') +$On => loc('on') $After => loc('after') </%ARGS> diff --git a/rt/share/html/Elements/SelectLang b/rt/share/html/Elements/SelectLang index 9ba09f8b2..cf4d2971d 100755 --- a/rt/share/html/Elements/SelectLang +++ b/rt/share/html/Elements/SelectLang @@ -59,11 +59,9 @@ </select> <%ARGS> $ShowNullOption => 1 -$ShowAllQueues => 1 $Name => undef $Verbose => undef $Default => 0 -$Lite => 0 </%ARGS> <%ONCE> diff --git a/rt/share/html/Elements/SelectMatch b/rt/share/html/Elements/SelectMatch index 65e108da0..d76cfbba7 100755 --- a/rt/share/html/Elements/SelectMatch +++ b/rt/share/html/Elements/SelectMatch @@ -68,15 +68,15 @@ my $LikeDefault=''; my $NotLikeDefault =''; if ($Default && $Default =~ /false|!=/i) { - $FalseDefault = qq[ selected="selected"]; + $FalseDefault = qq[ selected="selected"]; } elsif ($Default && $Default =~ /true|=/i) { - $TrueDefault = qq[ selected="selected"]; -} + $TrueDefault = qq[ selected="selected"]; +} elsif ($Default && $Default =~ /notlike|NOT LIKE/i) { - $NotLikeDefault = qq[ selected="selected"]; + $NotLikeDefault = qq[ selected="selected"]; } else { - $LikeDefault = qq[ selected="selected"]; + $LikeDefault = qq[ selected="selected"]; } </%INIT> diff --git a/rt/share/html/Elements/SelectObject b/rt/share/html/Elements/SelectObject new file mode 100644 index 000000000..5952ce26a --- /dev/null +++ b/rt/share/html/Elements/SelectObject @@ -0,0 +1,141 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 ($Lite) { +% my $d = $ObjectType->new($session{'CurrentUser'}); +% $d->Load($Default); +<input name="<%$Name%>" size="25" value="<%$d->Name%>" class="<%$Class%>" /> +% } +% else { +<select name="<%$Name%>" <% ($Multiple) ? qq{multiple="multiple" size="$Size"} : '' |n%> <% ($OnChange) ? 'onchange="'.$OnChange.'"' : '' |n %> class="<%$Class%>"> +% if ($ShowNullOption) { + <option value=""><% $DefaultLabel %></option> +% } +% for my $object (@{$session{$cache_key}{objects}}) { + <option value="<% ($NamedValues ? $object->{Name} : $object->{Id}) %>"\ +% if ($object->{Id} eq ($Default||'') || $object->{Name} eq ($Default||'')) { + selected="selected"\ +% } +><%$object->{Name}%>\ +% if ($Verbose and $object->{Description}) { + (<%$object->{Description}%>)\ +% } +</option> +% } +</select> +% } +<%args> +$ObjectType +$CheckRight => undef +$ShowNullOption => 1 +$ShowAll => 1 +$Name => undef +$Verbose => undef +$NamedValues => 0 +$DefaultLabel => "-" +$Default => 0 +$Lite => 0 +$OnChange => undef +$Multiple => 0 +$Size => 6 +$Class => "" +$CacheNeedsUpdate => undef +</%args> +<%init> +$ObjectType = "RT::$ObjectType" unless $ObjectType =~ /::/; +$Class ||= "select-" . CSSClass("\L$1") if $ObjectType =~ /RT::(.+)$/; + +my $cache_key = join "---", "SelectObject", $ObjectType, + $session{'CurrentUser'}->Id, $CheckRight || "", $ShowAll; + +if ( defined $session{$cache_key} && ref $session{$cache_key} eq 'ARRAY') { + delete $session{$cache_key}; +} +if ( defined $session{$cache_key} && defined $CacheNeedsUpdate && + $session{$cache_key}{lastupdated} <= $CacheNeedsUpdate ) { + delete $session{$cache_key}; +} + +if ( not defined $session{$cache_key} and not $Lite ) { + my $collection = "${ObjectType}s"->new($session{'CurrentUser'}); + $collection->UnLimit; + + $m->callback( CallbackName => 'ModifyCollection', ARGSRef => \%ARGS, + Collection => $collection, ObjectType => $ObjectType ); + + if ( $Default ) { + my $object = $ObjectType->new($session{'CurrentUser'}); + $object->Load( $Default ); + unless ( $ShowAll + or not $CheckRight + or $session{CurrentUser}->HasRight( Object => $object, Right => $CheckRight ) ) + { + if ( $object->id ) { + push @{$session{$cache_key}{objects}}, { + Id => $object->id, + Name => '#' . $object->id, + Description => '#' . $object->id, + }; + } + } + } + + while (my $object = $collection->Next) { + if ($ShowAll + or not $CheckRight + or $session{CurrentUser}->HasRight( Object => $object, Right => $CheckRight )) + { + push @{$session{$cache_key}{objects}}, { + Id => $object->Id, + Name => $object->Name, + Description => $object->_Accessible("Description" => "read") ? $object->Description : undef, + }; + } + } + $session{$cache_key}{lastupdated} = time(); +} +</%init> diff --git a/rt/share/html/Elements/SelectOwnerAutocomplete b/rt/share/html/Elements/SelectOwnerAutocomplete index d54bce39d..95c0f2c53 100644 --- a/rt/share/html/Elements/SelectOwnerAutocomplete +++ b/rt/share/html/Elements/SelectOwnerAutocomplete @@ -86,7 +86,7 @@ my $query = $m->comp('/Elements/QueryString', } else { jQuery.ajax({ - url: <% RT->Config->Get('WebPath')|n,j%>+"/Helpers/Autocomplete/Owners?"+<% $query|n,j %>, + url: RT.Config.WebPath + "/Helpers/Autocomplete/Owners?"+<% $query|n,j %>, dataType: "json", data: request, success: function( data ) { diff --git a/rt/share/html/Elements/SelectOwnerDropdown b/rt/share/html/Elements/SelectOwnerDropdown index acd8bc964..60e9cde7e 100644 --- a/rt/share/html/Elements/SelectOwnerDropdown +++ b/rt/share/html/Elements/SelectOwnerDropdown @@ -49,10 +49,6 @@ %if ($DefaultValue) { <option value=""<% !$Default ? qq[ selected="selected"] : '' |n %>><%$DefaultLabel |n%></option> %} -% $Default = 0 unless defined $Default && $Default =~ /^\d+$/; -% my @formatednames = sort {lc $a->[1] cmp lc $b->[1]} map {[$_, $m->scomp('/Elements/ShowUser', User => $_)]} grep { $_->id != RT->Nobody->id } @users; -% my $nobody = [RT->Nobody, $m->scomp('/Elements/ShowUser', User => RT->Nobody)]; -% unshift @formatednames, $nobody; %foreach my $UserRef ( @formatednames) { %my $User = $UserRef->[0]; <option <% ( $User->Id == $Default) ? qq[ selected="selected"] : '' |n %> @@ -61,7 +57,7 @@ %} elsif ($ValueAttribute eq 'Name') { value="<%$User->Name%>" %} -><% $UserRef->[1] |n %></option> +><% $UserRef->[1] %></option> %} </select> <%INIT> @@ -71,6 +67,7 @@ my $isSU = $session{CurrentUser} ->HasRight( Right => 'SuperUser', Object => $RT::System ); foreach my $object (@$Objects) { my $Users = RT::Users->new( $session{CurrentUser} ); + $Users->LimitToPrivileged; $Users->WhoHaveRight( Right => 'OwnTicket', Object => $object, @@ -81,12 +78,35 @@ foreach my $object (@$Objects) { $user_uniq_hash{ $User->Id() } = $User; } } + +my $dropdown_limit = 50; +$m->callback( CallbackName => 'ModifyDropdownLimit', DropdownLimit => \$dropdown_limit ); + +if (keys(%user_uniq_hash) > $dropdown_limit ) { + if ($Objects->[0]->id) { + my $desc = $Objects->[0]->RecordType." ".$Objects->[0]->id; + RT->Logger->notice("More than $dropdown_limit possible Owners found for $desc; switching to autocompleter. See the \$AutocompleteOwners configuration option"); + } + $m->comp("/Elements/SelectOwnerAutocomplete", %ARGS); + return; +} + if ($Default && $Default != RT->Nobody->id && !$user_uniq_hash{$Default}) { $user_uniq_hash{$Default} = RT::User->new($session{CurrentUser}); $user_uniq_hash{$Default}->Load($Default); } +$Default = 0 unless defined $Default && $Default =~ /^\d+$/; + +my @formatednames = sort {lc $a->[1] cmp lc $b->[1]} + map {[$_, $_->Format]} + grep { $_->id != RT->Nobody->id } + values %user_uniq_hash; + +my $nobody_user = RT::User->new( $session{CurrentUser} ); +$nobody_user->Load( RT->Nobody->id ); +my $nobody = [$nobody_user, $nobody_user->Format]; +unshift @formatednames, $nobody; -my @users = values %user_uniq_hash; </%INIT> <%ARGS> diff --git a/rt/share/html/Elements/SelectQueue b/rt/share/html/Elements/SelectQueue index 76440d124..ae645a773 100755 --- a/rt/share/html/Elements/SelectQueue +++ b/rt/share/html/Elements/SelectQueue @@ -45,92 +45,14 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -% if ($Lite) { -% my $d = RT::Queue->new($session{'CurrentUser'}); -% $d->Load($Default); -<input name="<%$Name%>" size="25" value="<%$d->Name%>" class="<%$Class%>" /> -% } -% else { -<select name="<%$Name%>" <% ($Multiple) ? qq{multiple="multiple" size="$Size"} : '' |n%> <% ($OnChange) ? 'onchange="'.$OnChange.'"' : '' |n %> class="<%$Class%>"> -% if ($ShowNullOption) { - <option value=""><% $DefaultLabel %></option> -% } -% for my $queue (@{$session{$cache_key}{queues}}) { - <option value="<% ($NamedValues ? $queue->{Name} : $queue->{Id}) %>"\ -% if ($queue->{Id} eq ($Default||'') || $queue->{Name} eq ($Default||'')) { - selected="selected"\ -% } -><%$queue->{Name}%>\ -% if ($Verbose and $queue->{Description}) { - (<%$queue->{Description}%>)\ -% } -</option> -% } -</select> -% } +<& SelectObject, + %ARGS, + ObjectType => "Queue", + CheckRight => $CheckQueueRight, + ShowAll => $ShowAllQueues, + CacheNeedsUpdate => RT->System->QueueCacheNeedsUpdate, + &> <%args> $CheckQueueRight => 'CreateTicket' -$ShowNullOption => 1 $ShowAllQueues => 1 -$Name => undef -$Verbose => undef -$NamedValues => 0 -$DefaultLabel => "-" -$Default => 0 -$Lite => 0 -$OnChange => undef -$Multiple => 0 -$Size => 6 -$Class => 'select-queue' </%args> -<%init> -my $cache_key = "SelectQueue---" - . $session{'CurrentUser'}->Id - . "---$CheckQueueRight---$ShowAllQueues"; - -if ( defined $session{$cache_key} && ref $session{$cache_key} eq 'ARRAY') { - delete $session{$cache_key}; -} -if ( defined $session{$cache_key} && - $session{$cache_key}{lastupdated} <= RT->System->QueueCacheNeedsUpdate ) { - delete $session{$cache_key}; -} - -if ( defined $session{$cache_key} && ref $session{$cache_key} eq 'ARRAY') { - delete $session{$cache_key}; -} -if ( defined $session{$cache_key} && - $session{$cache_key}{lastupdated} <= RT->System->QueueCacheNeedsUpdate ) { - delete $session{$cache_key}; -} - -if ( not defined $session{$cache_key} and not $Lite ) { - my $q = RT::Queues->new($session{'CurrentUser'}); - $q->UnLimit; - - if ( $Default ) { - my $d = RT::Queue->new($session{'CurrentUser'}); - $d->Load($Default); - unless ( $d->CurrentUserHasRight('SeeQueue') ) { - if ( $d->id ) { - push @{$session{$cache_key}{queues}}, { - Id => $d->id, - Name => '#' . $d->id, - Description => '#' . $d->id, - }; - } - } - } - - while (my $queue = $q->Next) { - if ($ShowAllQueues || $queue->CurrentUserHasRight($CheckQueueRight)) { - push @{$session{$cache_key}{queues}}, { - Id => $queue->Id, - Name => $queue->Name, - Description => $queue->Description, - }; - } - } - $session{$cache_key}{lastupdated} = time(); -} -</%init> diff --git a/rt/share/html/Elements/SelectStatus b/rt/share/html/Elements/SelectStatus index 3c833d85c..faae3dc3d 100755 --- a/rt/share/html/Elements/SelectStatus +++ b/rt/share/html/Elements/SelectStatus @@ -70,39 +70,35 @@ my %statuses_by_lifecycle; if ( @Statuses ) { $statuses_by_lifecycle{''} = \@Statuses; -} -elsif ( $TicketObj ) { - my @status; - my $current = $TicketObj->Status; - push @status, $current; - - my $lifecycle = $TicketObj->QueueObj->Lifecycle; +} else { + if ( $Object ) { + my $lifecycle = $Object->LifecycleObj; + if ($Object->_Accessible("Status", "read")) { + my $current = $Object->Status; + my @status; + push @status, $current; - my %has = (); - foreach my $next ( $lifecycle->Transitions( $current ) ) { - my $check = $lifecycle->CheckRight( $current => $next ); - $has{ $check } = $TicketObj->CurrentUserHasRight( $check ) - unless exists $has{ $check }; - push @status, $next if $has{ $check }; + my %has = (); + foreach my $next ( $lifecycle->Transitions( $current ) ) { + my $check = $lifecycle->CheckRight( $current => $next ); + $has{ $check } = $Object->CurrentUserHasRight( $check ) + unless exists $has{ $check }; + push @status, $next if $has{ $check }; + } + $statuses_by_lifecycle{$lifecycle->Name} = \@status; + } else { + $statuses_by_lifecycle{$lifecycle->Name} = [ $lifecycle->Transitions('') ]; + } } - $statuses_by_lifecycle{$lifecycle->Name} = \@status; -} -elsif ( $QueueObj ) { - my $lifecycle = $QueueObj->Lifecycle; - $statuses_by_lifecycle{$lifecycle->Name} = [ $lifecycle->Transitions('') ]; -} elsif ( %Queues ) { - for my $id (keys %Queues) { - my $queue = RT::Queue->new($session{'CurrentUser'}); - $queue->Load($id); - if ($queue->id) { - my $lifecycle = $queue->Lifecycle; + for my $lifecycle ( @Lifecycles ) { + $statuses_by_lifecycle{$lifecycle->Name} ||= [ $lifecycle->Valid ]; + } + + if (not keys %statuses_by_lifecycle) { + for my $lifecycle (map { RT::Lifecycle->Load($_) } RT::Lifecycle->List($Type)) { $statuses_by_lifecycle{$lifecycle->Name} = [ $lifecycle->Valid ]; } } -} else { - for my $lifecycle (map { RT::Lifecycle->Load($_) } RT::Lifecycle->List) { - $statuses_by_lifecycle{$lifecycle->Name} = [ $lifecycle->Valid ]; - } } if (keys %statuses_by_lifecycle) { @@ -132,11 +128,11 @@ my $group_by_lifecycle = keys %statuses_by_lifecycle > 1; </%INIT> <%ARGS> $Name => undef +$Type => undef, @Statuses => () -$TicketObj => undef -$QueueObj => undef -%Queues => () +$Object => undef, +@Lifecycles => (), $Default => '' $SkipDeleted => 0 diff --git a/rt/share/html/Elements/SelectTicketTypes b/rt/share/html/Elements/SelectTicketTypes deleted file mode 100755 index cecf61729..000000000 --- a/rt/share/html/Elements/SelectTicketTypes +++ /dev/null @@ -1,58 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2015 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 }}} -<select name="<%$Name%>"> -%foreach (@Types) { -<option value="<% $_ %>"<% ($_ eq $Default) && qq[ selected="selected"] |n %>><% loc($_) %> -%} -</select> - -<%ARGS> -$Name => 'TickType' -$Default => undef -@Types => qw(Approval Ticket) -</%ARGS> diff --git a/rt/share/html/Elements/SelectTimeUnits b/rt/share/html/Elements/SelectTimeUnits index 660e5c7fa..da5376b5d 100755 --- a/rt/share/html/Elements/SelectTimeUnits +++ b/rt/share/html/Elements/SelectTimeUnits @@ -46,17 +46,17 @@ %# %# END BPS TAGGED BLOCK }}} <select class="TimeUnits" id="<% $Name %>" name="<% $Name %>"> -<option value="minutes" <% $HoursDefault ? '' : 'selected="selected"' |n%>> +<option value="minutes" <% $Default eq 'minutes' ? 'selected="selected"' : '' |n%>> <% loc('Minutes') %> </option> -<option value="hours" <% $HoursDefault ? 'selected="selected"' : '' |n%>> +<option value="hours" <% $Default eq 'hours' ? 'selected="selected"' : '' |n%>> <% loc('Hours') %> </option> </select> <%INIT> $Name .= '-TimeUnits' unless $Name =~ /-TimeUnits$/io; -my $HoursDefault = RT->Config->Get('DefaultTimeUnitsToHours', $session{'CurrentUser'}); </%INIT> <%ARGS> $Name => '' +$Default => RT->Config->Get('DefaultTimeUnitsToHours', $session{'CurrentUser'}) ? 'hours' : 'minutes' </%ARGS> diff --git a/rt/share/html/Elements/SelectSortOrder b/rt/share/html/Elements/ShowCustomFieldCustomGroupings index 0075df61d..dfce725b2 100755..100644 --- a/rt/share/html/Elements/SelectSortOrder +++ b/rt/share/html/Elements/ShowCustomFieldCustomGroupings @@ -45,21 +45,34 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<select name="<%$Name%>"> -%foreach my $order (@orders) { -<option value="<%$order%>"<%$order eq $Default && qq[ selected="selected"] |n %>> -<% shift @order_names %> -</option> +<%perl> +for my $group ( @Groupings ) { + my %grouping_args = ( + title => $group? loc($group) : loc('Custom Fields'), + class => $css_class .' '. ($group? CSSClass("$css_class-$group") : ''), + hide_empty => 1, + title_href => $title_href ? "$title_href?id=".$Object->id.($group?";Grouping=".$m->interp->apply_escapes($group,'u')."#".CSSClass("$css_class-$group") : "#".$css_class) : undef, + %$TitleBoxARGS, + ); + $m->callback( CallbackName => 'TitleBox', Object => $Object, Grouping => $group, ARGSRef => \%grouping_args ); +</%perl> +<&| /Widgets/TitleBox, %grouping_args &> +<& ShowCustomFields, %ARGS, Object => $Object, Grouping => $group &> +</&> % } -</select> - +<%ARGS> +$Object +$title_href => "" +@Groupings => () +</%ARGS> <%INIT> -my @orders = qw (ASC DESC); -my @order_names = (loc('Ascending'), loc('Descending')); +my $css_class = lc(ref($Object)||$Object); +$css_class =~ s/^rt:://; +$css_class =~ s/::/-/g; +$css_class = CSSClass($css_class); +$css_class .= '-info-cfs'; -</%INIT> +my $TitleBoxARGS = delete $ARGS{TitleBoxARGS} || {}; -<%ARGS> -$Name => 'SortOrder' -$Default => 'ASC' -</%ARGS> +@Groupings = (RT::CustomField->CustomGroupings( $Object ), '') unless @Groupings; +</%INIT> diff --git a/rt/share/html/Elements/ShowCustomFieldImage b/rt/share/html/Elements/ShowCustomFieldImage index 6df80cc0e..f5a1886e9 100644 --- a/rt/share/html/Elements/ShowCustomFieldImage +++ b/rt/share/html/Elements/ShowCustomFieldImage @@ -46,7 +46,7 @@ %# %# END BPS TAGGED BLOCK }}} % my $url = RT->Config->Get('WebPath') . "/Download/CustomFieldValue/".$Object->Id.'/'.$m->interp->apply_escapes($Object->Content, 'u'); -<a href="<% $url %>"><% $Object->Content %></a> +<a href="<% $url %>"><% $Object->Content %></a><br> <img type="<% $Object->ContentType %>" height="64" src="<% $url %>" align="middle" /> <%ARGS> $Object diff --git a/rt/share/html/Elements/ShowCustomFields b/rt/share/html/Elements/ShowCustomFields index 75e302bd3..d8e6acf5b 100644 --- a/rt/share/html/Elements/ShowCustomFields +++ b/rt/share/html/Elements/ShowCustomFields @@ -45,7 +45,8 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -% $m->callback( CallbackName => 'BeforeCustomFields' ); +% $m->callback( CallbackName => 'BeforeCustomFields', Object => $Object, +% Grouping => $Grouping, ARGSRef => \%ARGS, CustomFields => $CustomFields, Table => $Table ); % if ($Table) { <table> % } @@ -54,6 +55,7 @@ % while ( my $CustomField = $set->Next ) { % my $Values = $Object->CustomFieldValues( $CustomField->Id ); % my $count = $Values->Count; +% next if $HideEmpty and not $count; % next if $count == 0 and $CustomField->Disabled; <tr id="CF-<%$CustomField->id%>-ShowRow"> <td class="label"><% $CustomField->Name %>:</td> @@ -72,19 +74,23 @@ </ul> % } </td> +% $m->callback( CallbackName => 'AfterCustomFieldValue', CustomField => $CustomField, +% Object => $Object, Grouping => $Grouping, Table => $Table ); </tr> % } % } % if ($Table) { </table> % } -% $m->callback( CallbackName => 'AfterCustomFields', Object => $Object ); +% $m->callback( CallbackName => 'AfterCustomFields', Object => $Object, +% Grouping => $Grouping, ARGSRef => \%ARGS, Table => $Table ); <%INIT> $m->callback( %ARGS, CallbackName => 'MassageCustomFields', - Object => $Object, + Object => $Object, CustomFields => $CustomFields, + Table => $Table, ); # kludge to allow "Support time" to be displayed even though it's been @@ -94,6 +100,8 @@ $HiddenCustomFields->LimitToChildType(ref $Object); $HiddenCustomFields->Limit( FIELD => 'Type', VALUE => 'TimeValue' ); $HiddenCustomFields->LimitToDeleted; +$CustomFields->LimitToGrouping( $Object => $Grouping ) if defined $Grouping; + # don't print anything if there is no custom fields return unless $CustomFields->Count > 0 or $HiddenCustomFields->Count > 0; @@ -102,7 +110,7 @@ my $print_value = sub { my $linked = $value->LinkValueTo; if ( defined $linked && length $linked ) { my $linked = $m->interp->apply_escapes( $linked, 'h' ); - $m->out('<a href="'. $linked .'" target="_new">'); + $m->out('<a href="'. $linked .'" target="_blank">'); } my $comp = "ShowCustomField". $cf->Type; $m->callback( @@ -110,6 +118,7 @@ my $print_value = sub { Name => \$comp, CustomField => $cf, Object => $Object, + Table => $Table, ); if ( $m->comp_exists( $comp ) ) { $m->comp( $comp, Object => $value ); @@ -137,6 +146,7 @@ my $print_value = sub { <%ARGS> $Object => undef $CustomFields => $Object->CustomFields - +$Grouping => undef $Table => 1 +$HideEmpty => 0 </%ARGS> diff --git a/rt/share/html/Elements/ShowHistory b/rt/share/html/Elements/ShowHistory new file mode 100644 index 000000000..d585453af --- /dev/null +++ b/rt/share/html/Elements/ShowHistory @@ -0,0 +1,191 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 }}} +<div class="history <% lc $record_type %>" id="<% $histid %>"> +<%perl> +if ( $ShowDisplayModes or $ShowTitle ) { + my $title = $ShowTitle + ? loc('History') + : ' '; + + my $titleright = ''; + if ( $ShowDisplayModes ) { + if ( RT->Config->Get( 'QuoteFolding', $session{CurrentUser} ) ) { + my $open_all = $m->interp->apply_escapes( loc("Show all quoted text"), 'j' ); + my $open_html = $m->interp->apply_escapes( loc("Show all quoted text"), 'h' ); + my $close_all = $m->interp->apply_escapes( loc("Hide all quoted text"), 'j' ); + $titleright .= '<a href="#" data-direction="open" ' + . qq{onclick="return toggle_all_folds(this, $open_all, $close_all);"} + . ">$open_html</a> — "; + } + + if ($ShowHeaders) { + $titleright .= qq{<a href="?ForceShowHistory=1;id=} . + $Object->id.qq{#$histid">} . + loc("Show brief headers") . + qq{</a>}; + } else { + $titleright .= qq{<a href="?ForceShowHistory=1;ShowHeaders=1;id=} . + $Object->id.qq{#$histid">} . + loc("Show full headers") . + qq{</a>}; + } + } +</%perl> +<& /Widgets/TitleBoxStart, title => $title, titleright_raw => $titleright &> +% } + +<div class="history-container"> +<%perl> +$m->callback( %ARGS, Object => $Object, CallbackName => 'BeforeTransactions' ); +my $i = 1; +while ( my $Transaction = $Transactions->Next ) { + my $skip = 0; + + # Skip display of SetWatcher transactions for ticket Owner groups. Owner + # was a single member role group and denormalized into a column well before + # the generic role group handling and transactions came about. For + # tickets, we rely on rendering ownership changes using the Set-Owner + # transaction. For all other record types, or even potential ticket single + # role groups which aren't Owner, we use SetWatcher to render history and + # skip the Set transactions. This complication is necessary to avoid + # creating backdated transactions on upgrade which normalize to one type or + # another. + # + # These conditions assumes ticket Owner is a single-member denormalized + # role group, which is safe since that is unlikely to ever change in the + # future. + if ($Object->isa("RT::Ticket") and ($Transaction->Field || '') eq "Owner") { + $skip = 1 if $Transaction->Type eq "SetWatcher"; + } else { + $skip = 1 if $Transaction->Type eq "Set" + and $Transaction->Field + and $Object->DOES("RT::Record::Role::Roles") + and $Object->HasRole( $Transaction->Field ) + and $Object->RoleGroup( $Transaction->Field )->SingleMemberRoleGroupColumn; + } + + $m->callback( + %ARGS, + Transaction => $Transaction, + skip => \$skip, + CallbackName => 'SkipTransaction', + ); + next if $skip; + + # ARGS is first because we're clobbering the "Attachments" parameter + $m->comp( 'ShowTransaction', + %ARGS, + Object => $Object, + Transaction => $Transaction, + ShowHeaders => $ShowHeaders, + RowNum => $i, + Attachments => $trans_attachments->{$Transaction->id} || {}, + AttachmentContent => $trans_content, + HasTxnCFs => $HasTxnCFs, + ); + + # manually flush the content buffer after each txn, + # so the user sees some update + $m->flush_buffer; + + $i++; +} + +</%perl> +</div> +% if ($ShowDisplayModes or $ShowTitle) { +<& /Widgets/TitleBoxEnd &> +% } +</div> +<%INIT> +my $trans_content = {}; +my $trans_attachments = {}; + +for my $content (@{$AttachmentContent->ItemsArrayRef()}) { + $trans_content->{$content->TransactionId}->{$content->Id} = $content; +} + +for my $attachment (@{$Attachments->ItemsArrayRef()}) { + my $tmp = $trans_attachments->{ $attachment->TransactionId } ||= {}; + push @{ $tmp->{ $attachment->Parent || 0 } ||= [] }, $attachment; +} + +{ + my %tmp = ( + DisplayPath => 'Display.html', + AttachmentPath => 'Attachment', + UpdatePath => 'Update.html', + ForwardPath => 'Forward.html', + EmailRecordPath => 'ShowEmailRecord.html', + EncryptionPath => 'Crypt.html', + ); + + my $prefix = $ARGS{PathPrefix}||''; + while ( my ($arg, $path) = each %tmp ) { + next if defined $ARGS{ $arg }; + $ARGS{ $arg } = $prefix.$path; + } +} + +my $record_type = $Object->RecordType; +my $histid = "\L$record_type\E-" . $Object->id . "-history"; + +my $HasTxnCFs = ($Object->can("TransactionCustomFields") and $Object->TransactionCustomFields->Count); +</%INIT> +<%ARGS> +$Object +$Transactions => $Object->SortedTransactions +$Attachments => $Object->Attachments( WithHeaders => 1 ) +$AttachmentContent => $Object->TextAttachments + +$ShowHeaders => 0 +$ShowTitle => 1 +$ShowDisplayModes => 1 + +$PathPrefix => '' +</%ARGS> diff --git a/rt/share/html/Elements/ShowLink b/rt/share/html/Elements/ShowLink index b533c83dc..d1629b146 100644 --- a/rt/share/html/Elements/ShowLink +++ b/rt/share/html/Elements/ShowLink @@ -45,33 +45,26 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<a href="<% $href %>"> -% if ($URI->IsLocal) { % my $member = $URI->Object; -% my $has_name = UNIVERSAL::can($member, 'Name') || (UNIVERSAL::can($member, '_Accessible') && $member->_Accessible('Name', 'read')); -% if (UNIVERSAL::isa($member, "RT::Ticket") and $member->CurrentUserHasRight('ShowTicket')) { -% my $inactive = $member->QueueObj->IsInactiveStatus($member->Status); +% if (blessed($member) and $member->isa("RT::Ticket") and $member->CurrentUserHasRight('ShowTicket')) { +% my $class = $member->QueueObj->IsInactiveStatus($member->Status) +% ? 'ticket-inactive' +% : 'ticket-active'; +% $class .= ' '.CSSClass($member->Status); -<span class="<% $inactive ? 'ticket-inactive' : '' %>"> - -<%$member->Id%>: (<& /Elements/ShowUser, User => $member->OwnerObj &>) <%$member->Subject || ''%> [<% loc($member->Status) %>] +<span class="<% $class %>"> +<a href="<% $href %>"><%$member->Id%>: <%$member->Subject || ''%> [<% loc($member->Status) %>]</a> (<& /Elements/ShowUser, User => $member->OwnerObj &>) </span> -% } elsif ($has_name) { -<%$URI->Resolver->AsString%>: <%$member->Name%> -% } else { -<%$URI->Resolver->AsString%> -% } % } else { -<%$URI->Resolver->AsString%> +<a href="<% $href %>"><%$URI->AsString%></a> % } -</a> <%ARGS> $URI => undef </%ARGS> <%INIT> -my $href = $URI->Resolver->HREF; +my $href = $URI->AsHREF; if ( $URI->IsLocal ) { my $base = RT->Config->Get('WebBaseURL'); # URI->rel doesn't contain the leading '/' diff --git a/rt/share/html/Elements/ShowLinks b/rt/share/html/Elements/ShowLinks index 8880224f7..30dff0060 100755 --- a/rt/share/html/Elements/ShowLinks +++ b/rt/share/html/Elements/ShowLinks @@ -46,134 +46,63 @@ %# %# END BPS TAGGED BLOCK }}} <table> +% for my $type (@display) { <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Depends on'), Relation => 'DependsOn' &>:\ -% if ($can_create) { - <span class="create">(<a href="<%$clone->{'DependsOn-new'}%>"><% loc('Create') %></a>)</span> -% } - </td> - <td class="value"> -<%PERL> -my ( $depends_on, @active, @inactive, @not_tickets ); -$depends_on = $Ticket->DependsOn; - -while ( my $link = $depends_on->Next ) { - my $target = $link->TargetObj; - if ( $target && $target->isa('RT::Ticket') ) { - if ( $target->QueueObj->IsInactiveStatus( $target->Status ) ) { - push( @inactive, $link->TargetURI ); - } - else { - push( @active, $link->TargetURI ); - } - } - else { - push( @not_tickets, $link->TargetURI ); - } -} -</%PERL> -<ul> -% for my $Link (@not_tickets, @active, @inactive) { -<li><& ShowLink, URI => $Link &></li> -% } -</ul> - </td> - </tr> - <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Depended on by'), Relation => 'DependedOnBy' &>:\ -% if ($can_create) { - <span class="create">(<a href="<%$clone->{'new-DependsOn'}%>"><% loc('Create') %></a>)</span> -% } - </td> - <td class="value"> -<ul> -% while (my $Link = $Ticket->DependedOnBy->Next) { -<li><& ShowLink, URI => $Link->BaseURI &></li> -% } -</ul> - </td> - </tr> - <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Parents'), Relation => 'Parents' &>:\ -% if ($can_create) { - <span class="create">(<a href="<%$clone->{'MemberOf-new'}%>"><% loc('Create') %></a>)</span> -% } - </td> - <td class="value"><& /Ticket/Elements/ShowParents, Ticket => $Ticket &></td> - </tr> - <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Children'), Relation => 'Children' &>:\ -% if ($can_create) { - <span class="create">(<a href="<%$clone->{'new-MemberOf'}%>"><% loc('Create') %></a>)</span> -% } - </td> - <td class="value"><& /Ticket/Elements/ShowMembers, Ticket => $Ticket &></td> - </tr> - <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Refers to'), Relation => 'RefersTo' &>:\ -% if ($can_create) { - <span class="create">(<a href="<%$clone->{'RefersTo-new'}%>"><% loc('Create') %></a>)</span> -% } + <td class="labeltop"> + <& ShowRelationLabel, Object => $Object, Label => $labels{$type}.':', Relation => $type &> +% if ($clone{$type}) { + <span class="create">(<a href="<% $clone{$type} %>"><% loc('Create') %></a>)</span> +% } </td> <td class="value"> -<ul> -% while (my $Link = $Ticket->RefersTo->Next) { -<li><& ShowLink, URI => $Link->TargetURI &></li> -% } -</ul> + <& ShowLinksOfType, Object => $Object, Type => $type, Recurse => ($type eq 'Members') &> </td> </tr> - <tr> - <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Referred to by'), Relation => 'ReferredToBy' &>:\ -% if ($can_create) { - <span class="create">(<a href="<%$clone->{'new-RefersTo'}%>"><% loc('Create') %></a>)</span> % } - </td> - <td class="value"> - <ul> -% while (my $Link = $Ticket->ReferredToBy->Next) { -% next if (UNIVERSAL::isa($Link->BaseObj, 'RT::Ticket') && $Link->BaseObj->Type eq 'reminder'); -<li><& ShowLink, URI => $Link->BaseURI &></li> -% } -</ul> - </td> - </tr> % # Allow people to add more rows to the table % $m->callback( %ARGS ); + <& /Elements/ShowCustomFields, Object => $Object, Grouping => 'Links', Table => 0 &> </table> <%INIT> +my @display = qw(DependsOn DependedOnBy MemberOf Members RefersTo ReferredToBy); +$m->callback( %ARGS, CallbackName => 'ChangeDisplay', display => \@display ); +my %labels = ( + DependsOn => loc('Depends on'), + DependedOnBy => loc('Depended on by'), + MemberOf => loc('Parents'), + Members => loc('Children'), + RefersTo => loc('Refers to'), + ReferredToBy => loc('Referred to by'), +); +my %clone; -my $id = $Ticket->id; +if ( $Object->isa("RT::Ticket") + and $Object->QueueObj->CurrentUserHasRight('CreateTicket')) +{ + my $id = $Object->id; + my $path + = RT->Config->Get('WebPath') + . '/Ticket/Create.html?Queue=' + . $Object->Queue + . '&CloneTicket=' + . $id; -my $clone = {}; -my $path - = RT->Config->Get('WebPath') - . '/Ticket/Create.html?Queue=' - . $Ticket->Queue - . '&CloneTicket=' - . $id; -my $can_create = $Ticket->QueueObj->CurrentUserHasRight('CreateTicket'); + for my $relation (@display) { + my $mode = $RT::Link::TYPEMAP{$relation}->{Mode}; + my $type = $RT::Link::TYPEMAP{$relation}->{Type}; + my $field = $mode eq 'Base' ? 'new-' . $type : $type . '-new'; + my @copy = ($id); -for my $relation ( - qw(RefersTo ReferredToBy)) { - my $mode = $RT::Ticket::LINKTYPEMAP{$relation}->{Mode}; - my $type = $RT::Ticket::LINKTYPEMAP{$relation}->{Type}; - my $other = "Local" . $mode; - my $field = $mode eq 'Base' ? 'new-' . $type : $type . '-new'; - $clone->{$field} - = $path . "&$field=" - . join( '%20', - ( map { $_->$other() } @{ $Ticket->$relation->ItemsArrayRef } ), $id ); -} + # Canonicalized type captures both directions + if ($type eq "RefersTo") { + my $other = "Local" . $mode; + push @copy, map { $_->$other() } @{ $Object->$relation->ItemsArrayRef }; + } -for my $relation ( qw(MemberOf Members DependsOn DependedOnBy)) { - my $mode = $RT::Ticket::LINKTYPEMAP{$relation}->{Mode}; - my $type = $RT::Ticket::LINKTYPEMAP{$relation}->{Type}; - my $field = $mode eq 'Base' ? 'new-' . $type : $type . '-new'; - $clone->{$field} = $path . "&$field=$id"; + $clone{$relation} = "$path&$field=" . join('%20', grep { $_ } @copy); + } } - </%INIT> <%ARGS> -$Ticket => undef +$Object </%ARGS> diff --git a/rt/share/html/Elements/ShowLinksOfType b/rt/share/html/Elements/ShowLinksOfType new file mode 100644 index 000000000..1bb485e6c --- /dev/null +++ b/rt/share/html/Elements/ShowLinksOfType @@ -0,0 +1,127 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 }}} +<ul> +% for my $link (@not_tickets, @active, @inactive) { +<li><& ShowLink, URI => $link->$ModeURI &> +<%perl> + next unless $Recurse; + + my $ToObj = $link->$ModeObj; + next if $ToObj and $checked->{$ToObj->id}; + + if ($depth <= $MaxDepth) { +</%perl> +<& ShowLinksOfType, %ARGS, Object => $ToObj, depth => ($depth + 1), checked => $checked &> +% } +</li> +% } +</ul> +<%INIT> +return unless $Object; + +unless ($RT::Link::TYPEMAP{$Type}) { + RT->Logger->error("Unknown link Type '$ARGS{Type}'"); + return; +} + +unless ($Object->can($Type)) { + RT->Logger->error("Don't know how to fetch links of '$Type' for object '$Object'"); + return; +} + +my $links = $Object->$Type; +return unless $links->Count; + +return if $checked->{$Object->id}; + +$checked->{$Object->id} = 1; + +my $mode = $RT::Link::TYPEMAP{$Type}->{'Mode'}; +my $ModeURI = "${mode}URI"; +my $ModeObj = "${mode}Obj"; + +# Filter and bucket +my (@active, @inactive, @not_tickets); +while (my $link = $links->Next) { + my $ToObj = $link->$ModeObj; + if ($ToObj and $ToObj->isa('RT::Ticket')) { + next if $Type eq "ReferredToBy" + and $ToObj->Type eq 'reminder'; + + if ( $ToObj->QueueObj->IsInactiveStatus( $ToObj->Status ) ) { + push @inactive, $link; + } + else { + push @active, $link; + } + } + else { + push @not_tickets, $link; + } +} + +$m->callback( + CallbackName => "Init", + ARGSRef => \%ARGS, + Object => $Object, + $Type => $Type, + $Recurse => \$Recurse, + $MaxDepth => \$MaxDepth, + active => \@active, + inactive => \@inactive, + not_tickets => \@not_tickets, +); +</%INIT> +<%ARGS> +$Object => undef +$Type +$Recurse => 0 +$MaxDepth => 7 +$depth => 1 +$checked => {} +</%ARGS> diff --git a/rt/share/html/Elements/ShowMemberships b/rt/share/html/Elements/ShowMemberships index 453beeaaf..7633d68fd 100644 --- a/rt/share/html/Elements/ShowMemberships +++ b/rt/share/html/Elements/ShowMemberships @@ -52,7 +52,7 @@ % if ($Group->Domain eq 'UserDefined') { <li><a href="<%RT->Config->Get('WebPath')%>/Admin/Groups/Modify.html?id=<% $Group->Id %>"><% $Group->Name %></a></li> % } elsif ($Group->Domain eq 'SystemInternal') { -<li><em><% loc($Group->Type) %></em></li> +<li><em><% loc($Group->Name) %></em></li> % } % } </ul> @@ -71,12 +71,14 @@ $GroupMembers->Limit( FIELD => 'Domain', OPERATOR => '=', VALUE => 'SystemInternal', + CASESENSITIVE => 0, ); $GroupMembers->Limit( ALIAS => $alias, FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined', + CASESENSITIVE => 0, ); $GroupMembers->OrderByCols( { ALIAS => $alias, FIELD => 'Domain' }, diff --git a/rt/share/html/Elements/ShowMessageHeaders b/rt/share/html/Elements/ShowMessageHeaders new file mode 100644 index 000000000..27c67bd6a --- /dev/null +++ b/rt/share/html/Elements/ShowMessageHeaders @@ -0,0 +1,101 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 ( @headers ) { +<table> +% foreach my $header (@headers) { + <tr> + <td align="right" class="message-header-key"><% $header->{'Tag'} %>:</td> + <td class="message-header-value <% join(' ', map {CSSClass($_)} @{$header->{Classes} || []}) %>"> + <% $header->{'Value'} | n %></td> + </tr> +% } +</table> +% $m->callback( CallbackName => 'AfterHeaders', message => $Message ); +% } +<%INIT> +my @headers; +foreach my $field( RT->Config->Get('ShowBccHeader')? $Message->_SplitHeaders : $Message->SplitHeaders ) { + my ($tag, $value) = split /:/, $field, 2; + next unless $tag && $value; + push @headers, { Tag => $tag, Value => $value }; +} + +my %display_headers = map { lc($_) => 1 } @DisplayHeaders; + +$m->callback( + message => $Message, + headers => \@headers, + display_headers => \%display_headers, +); + +unless ( $display_headers{'_all'} ) { + @headers = grep $display_headers{ lc $_->{'Tag'} }, @headers; +} + +my $object = $Message->TransactionObj->Object; +foreach my $f (@headers) { + $m->comp('/Elements/MakeClicky', content => \$f->{'Value'}, object => $object, %ARGS); +} + +unshift @headers, $m->comp( 'CryptStatus', Message => $Message, WarnUnsigned => $WarnUnsigned ); + +$m->callback( + CallbackName => 'BeforeLocalization', + headers => \@headers, +); + +if ( $Localize ) { + $_->{'Tag'} = loc($_->{'Tag'}) foreach @headers; +} +</%INIT> +<%ARGS> +$WarnUnsigned => 0 +$Message => undef +$Localize => 1 +@DisplayHeaders => ('_all') +</%ARGS> diff --git a/rt/share/html/Elements/ShowMessageStanza b/rt/share/html/Elements/ShowMessageStanza new file mode 100644 index 000000000..f98cb3a11 --- /dev/null +++ b/rt/share/html/Elements/ShowMessageStanza @@ -0,0 +1,176 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 $plain_text_mono + = RT->Config->Get( 'PlainTextMono', $session{'CurrentUser'} ); +my $Depth = 0; + +my $object = $Transaction ? $Transaction->Object : undef; + +my $print_content = sub { + my $ref = shift; + return unless defined $$ref && length $$ref; + + $m->callback( content => $ref, %ARGS ); + if ( $ContentType eq 'text/plain' ) { + $m->comp( '/Elements/MakeClicky', + content => $ref, + object => $object, + %ARGS + ); + + if ( defined $$ref && !$plain_text_mono ) { + $$ref =~ s{(\r?\n)}{<br />}g; + } + } else { + if ( defined $$ref ) { + $$ref =~ s/^[\r\n]+//g; + } + } + $m->out($$ref); +}; + +$m->out( '<div class="message-stanza' + . ( ($ContentType eq 'text/plain' && $plain_text_mono) ? ' plain-text-white-space' : '' ) . '"' + . '>' ); + +if ( ref $Message ) { + my @stack; + my $para = ''; + my $i = 0; + +AGAIN: foreach ( ; $i < @$Message; $i++ ) { + my $stanza = $Message->[$i]; + if ( ref $stanza eq "HASH" ) { + # Fix message stanza nesting for Outlook's quoting styles + if ( $stanza->{raw} + and not $stanza->{_outlooked} + and $stanza->{raw} =~ /^ # start of an internal line + \s* # optional whitespace + (?: + -{3,} # at least three hyphens + \s* # whitespace varies between Outlook versions + # don't trigger on PGP signed message or signature blocks + (?!(?:BEGIN|END)\s+PGP) + \w # at least one word character + [\w\s]{3,}? # the rest of the word(s), totalling at least 5 characters, + # loose to get different languages + \w # at least one ending word character + \s* # whitespace varies between Outlook versions + -{3,} # at least three hyphens again + | + _{6,} # OR: six or more underscores + ) + \s*$ # optional whitespace until the end of the line + /xm ) + { + # There's content before the quoted message, but in the + # same stanza. Break it out! + if ( my $start = $-[0] ) { + my %preceding = %$stanza; + + # We don't process $stanza->{text} because we don't use it + # and it isn't given to us by HTML::Quoted. If we ever + # need to, we can process it the same way as 'raw'. + $preceding{raw} = substr($stanza->{raw}, 0, $start, ''); + + # Replace the current stanza with the two we just created + splice @$Message, $i, 1, \%preceding, $stanza; + + # Try it again from the top now that we've rejiggered our + # stanzas. We'll process the Outlook stanza again, and hit + # the else below this time. + redo; + } else { + # Nest the current stanza and everything that follows + $stanza->{_outlooked}++; + $stanza = $Message->[ $i ] = [ splice @$Message, $i ]; + } + } + else { + $para .= ( defined $stanza->{raw} ? $stanza->{raw} : '' )."\n"; + } + } + next unless ref $stanza eq "ARRAY"; + + $print_content->( \$para ); + $para = ''; + + $Depth++; + push @stack, [ $Message, $i + 1 ]; + ( $Message, $i ) = ( $stanza, -1 ); + + if ( $Depth == 1 ) { + $m->comp('FoldStanzaJS'); + } + my @classes = ('message-stanza'); + push @classes, $Depth == 1 ? 'closed' : 'open'; + $m->out( '<div class="' . join(" ", @classes) . '">' ); + } + if ( length $para ) { + $print_content->( \$para ); + $para = ''; + } + + if (@stack) { + ( $Message, $i ) = @{ pop @stack }; + $Depth--; + $m->out('</div>'); + goto AGAIN; + } +} else { + $print_content->( \$Message ); +} + +$m->out('</div>'); +</%INIT> +<%ARGS> +$Message => undef +$Transaction => undef +$ContentType => 'text/plain' +</%ARGS> diff --git a/rt/share/html/Elements/ShowUserVerbose b/rt/share/html/Elements/ShowPrincipal index fd26007e6..81c8e62d4 100644 --- a/rt/share/html/Elements/ShowUserVerbose +++ b/rt/share/html/Elements/ShowPrincipal @@ -46,27 +46,27 @@ %# %# END BPS TAGGED BLOCK }}} %# Released under the terms of version 2 of the GNU Public License -<% $display |n %>\ -<%INIT> -my $phrase = ''; -my $address = ''; -my $comment = ''; +<%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 }; -if ($User) { - $address = $User->EmailAddress; - $phrase = $User->RealName - if $User->RealName && ( !$address || lc $User->RealName ne lc $address ); - $comment = $User->Name if !$address || lc $User->Name ne lc $address; + # But don't link the groups + push @ret, sort map {$m->interp->apply_escapes( loc("Group: [_1]", $_->Name), 'h' )} + @{ $Object->GroupMembersObj( Recursively => 0)->ItemsArrayRef }; + + $m->out( join($Separator, @ret) ); } else { - $address = $Address; + $m->comp("/Elements/ShowUser", User => $Object, Link => $Link); + $m->out( $PostUser->($Object) ) if $PostUser; } - -my $display = ($phrase || $comment || '' ) . ($address ? ' <'.$address.'>' : ''); - -$display = $m->interp->apply_escapes( $display, 'h' ) - unless $ARGS{'NoEscape'}; -</%INIT> -<%ARGS> -$User => undef -$Address => undef -</%ARGS> +</%init> diff --git a/rt/share/html/Elements/ShowRecord b/rt/share/html/Elements/ShowRecord new file mode 100644 index 000000000..3acff1375 --- /dev/null +++ b/rt/share/html/Elements/ShowRecord @@ -0,0 +1,100 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 }}} +<%args> +$Object +$Format +$TrustFormat => 0 +$Class => "" +</%args> +<%init> +$Format = ScrubHTML($Format) unless $TrustFormat; + +my @columns = $m->comp('/Elements/CollectionAsTable/ParseFormat', Format => $Format); + +my $fetch_columnmap = sub { + my ($name, $attr, $arguments, $no_escape) = @_; + my $tmp = $m->comp( + '/Elements/ColumnMap', + Class => $Object->ColumnMapClassName, + Name => $name, + Attr => $attr, + ); + return ProcessColumnMapValue( $tmp, Arguments => $arguments, Escape => !$no_escape ); +}; +</%init> +<div class="record <% CSSClass($Class) %> <% CSSClass(blessed($Object)) %>"> +<%perl> +for my $column (@columns) { + my $title = $m->interp->apply_escapes($column->{title} || '', 'h'); + my $attr = $column->{'attribute'} || $column->{'last_attribute'}; + + unless (defined $column->{title}) { + # No format-supplied title, so use the one from the column map as-is. It's + # trustworthy. + $title = $fetch_columnmap->($attr,'title',[$attr]); + } +</%perl> +<div class="record-field <% $Class ? CSSClass("$Class-field") : "" %> <% CSSClass($fetch_columnmap->($attr,'attribute',[$attr],'no_escape')) %>"> +<span class="label"><% loc($title) |n %></span> +<%perl> + my @out; + foreach my $subcol ( @{ $column->{output} } ) { + my ($col) = ($subcol =~ /^__(.*?)__$/); + unless ( $col ) { + push @out, $subcol; + next; + } + push @out, $fetch_columnmap->($col, 'value', [$Object]); + } + @out = grep { defined $_ and length $_ } @out; +</%perl> +<span class="value"><% join('',@out) |n %></span> +% $m->callback(CallbackName => 'AfterValue', Object => $Object, attribute => $attr, column => $column ); +</div> +% } +</div> diff --git a/rt/share/html/Elements/ShowRelationLabel b/rt/share/html/Elements/ShowRelationLabel index 92d8fec8d..e1c684f09 100644 --- a/rt/share/html/Elements/ShowRelationLabel +++ b/rt/share/html/Elements/ShowRelationLabel @@ -45,18 +45,36 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<a href="<%$SearchURL |n %>"><%$Label%></a> +% if ($SearchURL) { +<a href="<% $SearchURL %>"><% $Label %></a> +% } else { +<% $Label %> +% } <%INIT> -my $typemap = RT::Ticket->LINKTYPEMAP->{$Relation}; +my $typemap = $RT::Link::TYPEMAP{$Relation}; my $search_mode = $typemap->{Mode}; my $search_type = $typemap->{Type}; -my $search_relation = RT::Ticket->LINKDIRMAP->{$search_type}{$search_mode}; +my $search_relation = $RT::Link::DIRMAP{$search_type}->{$search_mode}; +my $SearchURL; -my $Query = $search_relation . ' = ' . $id; -my $SearchURL = RT->Config->Get('WebPath') . '/Search/Results.html?' . $m->comp('/Elements/QueryString', Query => $Query); +if ($Object and $Object->id) { + my $id = $Object->id; + + if ($Object->isa("RT::Ticket")) { + $SearchURL = RT->Config->Get('WebPath') + . '/Search/Results.html?' + . $m->comp('/Elements/QueryString', Query => "$search_relation = $id"); + } +} + +$m->callback( + CallbackName => "ModifySearchURL", + SearchURL => \$SearchURL, + ARGSRef => \%ARGS, +); </%INIT> <%ARGS> -$id +$Object => undef $Label $Relation </%ARGS> diff --git a/rt/share/html/Elements/ShowReminders b/rt/share/html/Elements/ShowReminders index 6b5ad3969..61d804075 100644 --- a/rt/share/html/Elements/ShowReminders +++ b/rt/share/html/Elements/ShowReminders @@ -57,7 +57,7 @@ my $i =0; while ( my $reminder = $reminders->Next ) { $i++; my $dueobj = $reminder->DueObj; -my $overdue = $dueobj->Unix > 0 && $dueobj->Diff < 0 ? 1 : 0; +my $overdue = $dueobj->IsSet && $dueobj->Diff < 0 ? 1 : 0; my $targets = RT::Tickets->new($session{'CurrentUser'}); $targets->{'allow_deleted_search'} = 1; @@ -67,7 +67,7 @@ if ( my $ticket= $targets->First ) { </%PERL> <tr class="<% $i%2 ? 'oddline' : 'evenline' %>"> <td class="collection-as-table"> -<a href="<% RT->Config->Get('WebPath') %>/Ticket/Reminders.html?id=<% $ticket->id %>"><% $reminder->Subject %></a> +<a href="<% RT->Config->Get('WebPath') %>/Ticket/Reminders.html?id=<% $ticket->id %>#reminder-<% $reminder->id %>"><% $reminder->Subject %></a> </td> <td class="collection-as-table"> <% $overdue ? '<span class="overdue">' : '' |n %><% $dueobj->AgeAsString || loc('Not set') %><% $overdue ? '</span>' : '' |n %> @@ -76,7 +76,7 @@ if ( my $ticket= $targets->First ) { <a href="<% RT->Config->Get( 'WebPath' ) %>/Ticket/Display.html?id=<% $ticket->id %>">#<% $ticket->Id %>: <% $ticket->Subject %></a> </td> % } else { -<td colspan="3" class="collection-as-table> +<td colspan="3" class="collection-as-table"> <div class="error"><div class="error">Couldn't find Ticket for reminder <% $reminder->id %>. Please contact administrator.</div></div> </td> % } @@ -91,7 +91,7 @@ my $tsql = 'Type = "reminder"' . ' AND ( Owner = "Nobody" OR Owner ="' . $session{'CurrentUser'}->id . '")' . ' AND ( Status = "new" OR Status = "open" )'; -$tsql .= ' AND Due < "now"' if $OnlyOverdue; +$tsql .= ' AND ( Due < "now" OR Due IS NULL )' if $OnlyOverdue; $reminders->FromSQL($tsql); $reminders->OrderBy( FIELD => 'Due', ORDER => 'ASC' ); diff --git a/rt/share/html/Elements/ShowSearch b/rt/share/html/Elements/ShowSearch index 20ce55b9a..b53238f13 100644 --- a/rt/share/html/Elements/ShowSearch +++ b/rt/share/html/Elements/ShowSearch @@ -67,9 +67,10 @@ if ($SavedSearch) { $m->out(loc("Either you have no rights to view saved search [_1] or identifier is incorrect", $m->interp->apply_escapes($SavedSearch, 'h'))); return; } - $search = $container_object->Attributes->WithId($search_id); + $search = RT::Attribute->new( $session{'CurrentUser'} ); + $search->Load($search_id); unless ( $search->Id && ref( $SearchArg = $search->Content ) eq 'HASH' ) { - $m->out(loc("Saved Search [_1] not found", $m->interp->apply_escapes($SavedSearch, 'h'))) unless $IgnoreMissing; + $m->out(loc("Saved search [_1] not found", $m->interp->apply_escapes($SavedSearch, 'h'))) unless $IgnoreMissing; return; } $SearchArg->{'SavedSearchId'} ||= $SavedSearch; @@ -122,17 +123,21 @@ foreach ( $SearchArg, $ProcessedSearchArg ) { $_->{'Format'} ||= ''; $_->{'Query'} ||= ''; - $_->{'Format'} =~ s/__(Web(?:Path|Base|BaseURL))__/scalar RT->Config->Get($1)/ge; # extract-message-catalog would "$1", so we avoid quotes for loc calls $_->{'Format'} =~ s/__loc\(["']?(\w+)["']?\)__/my $f = "$1"; loc($f)/ge; if ( $_->{'Query'} =~ /__Bookmarks__/ ) { $_->{'Rows'} = 999; - # DEPRECATED: will be here for a while up to 3.10/4.0 - my $bookmarks = $session{'CurrentUser'}->UserObj->FirstAttribute('Bookmarks'); - $bookmarks = $bookmarks->Content if $bookmarks; - $bookmarks ||= {}; - my $query = join(" OR ", map " id = '$_' ", grep $bookmarks->{ $_ }, keys %$bookmarks ) || 'id=0'; + # DEPRECATED: will be here for a while up to 4.4 + RT->Deprecated( + Remove => "4.4", + Instead => "id = '__Bookmarked__'", + Message => "The __Bookmarks__ query syntax is deprecated", + Object => $search, + ); + + my @bookmarks = $session{'CurrentUser'}->UserObj->Bookmarks; + my $query = join(" OR ", map " id = '$_' ", @bookmarks ) || 'id=0'; $_->{'Query'} =~ s/__Bookmarks__/( $query )/g; } } diff --git a/rt/share/html/Elements/ShowTransaction b/rt/share/html/Elements/ShowTransaction new file mode 100644 index 000000000..1018ec618 --- /dev/null +++ b/rt/share/html/Elements/ShowTransaction @@ -0,0 +1,265 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 }}} +<div class="<% join ' ', @classes %>"> + <div class="metadata"> + <span class="type"> + <a name="txn-<% $Transaction->id %>" \ +% if ( $DisplayPath ) { + href="<% $DisplayPath %>?id=<% $Object->id %>#txn-<% $Transaction->id %>" \ +% } + >#</a> + </span> +% $m->callback( %ARGS, Transaction => $Transaction, CallbackName => 'AfterAnchor' ); + <span class="date"><% $date |n %></span> + <span class="description"> + <& /Elements/ShowUser, User => $Transaction->CreatorObj &> - <% $desc |n %> +% $m->callback( %ARGS, Transaction => $Transaction, CallbackName => 'AfterDescription' ); + </span> + <span class="time-taken"><% $time %></span> +% if ( $actions ) { + <span class="actions"><% $actions |n %></span> +% } + </div> + + <div class="content"> +<%PERL> +$m->comp('/Elements/ShowCustomFields', Object => $Transaction, HideEmpty => 1 ) if $HasTxnCFs; +$m->comp( + 'ShowTransactionAttachments', + %ARGS, + Parent => 0 +) if $ShowBody; +</%PERL> + </div> +% $m->callback( %ARGS, Transaction => $Transaction, CallbackName => 'AfterContent' ); +</div> + +<%ARGS> +$Transaction +$Object => $Transaction->Object + +$Attachments => undef +$AttachmentContent => undef +$HasTxnCFs => 1 + +$ShowBody => 1 +$ShowActions => 1 +$RowNum => 1 + +$DisplayPath => undef +$AttachmentPath => undef +$UpdatePath => undef +$ForwardPath => undef +$EncryptionPath => undef +$EmailRecordPath => undef +</%ARGS> + +<%ONCE> + +</%ONCE> +<%INIT> +my $record_type = $Object->RecordType; +my $type_class = $Object->ClassifyTransaction( $Transaction ); + +$m->callback( + CallbackName => 'MassageTypeClass', + Transaction => $Transaction, + TypeClassRef => \$type_class, + ARGSRef => \%ARGS, +); + +my @classes = ( + "transaction", + "$record_type-transaction", + $type_class, + ($RowNum % 2 ? 'odd' : 'even') +); + +my $desc = $Transaction->BriefDescriptionAsHTML; +if ( $Object->id != $Transaction->ObjectId ) { + # merged objects + $desc = join " - ", + $m->interp->apply_escapes( + loc("[_1] #[_2]:", loc($record_type), $Transaction->ObjectId), 'h'), + $desc; +} + +my $date = $Transaction->CreatedAsString; + +my $time = ''; +$time = loc('[quant,_1,minute,minutes]', $Transaction->TimeTaken) + if $Transaction->TimeTaken; + +if ( $ShowBody && !$Attachments ) { + $ARGS{'Attachments'} = $Attachments = {}; + + my $attachments = $Transaction->Attachments( WithHeaders => 1 ); + push @{ $Attachments->{ $_->Parent || 0 } ||= [] }, $_ + foreach @{ $attachments->ItemsArrayRef }; +} + +my @actions = (); +my $txn_type = $Transaction->Type; +if ( $txn_type =~ /EmailRecord$/ ) { + push @actions, { + title => loc('Show'), + target => '_blank', + path => $EmailRecordPath + .'?id='. $Object->id + .'&Transaction='. $Transaction->id + .'&Attachment='. ( $Attachments->{0}[0] && $Attachments->{0}[0]->id ), + } if $EmailRecordPath; + + $ShowBody = 0; +} + +# If the transaction has anything attached to it at all +elsif ( %$Attachments && $ShowActions ) { + my %has_right = map { + $_ => RT::ACE->CanonicalizeRightName( $_ . $record_type ) + } qw(Modify CommentOn ReplyTo); + $has_right{'Forward'} = RT::ACE->CanonicalizeRightName('ForwardMessage'); + + my $can_modify = $has_right{'Modify'} + && $Object->CurrentUserHasRight( $has_right{'Modify'} ); + + if ( $UpdatePath && $has_right{'ReplyTo'} + && ( $can_modify + || $Object->CurrentUserHasRight( $has_right{'ReplyTo'} ) + ) + ) { + push @actions, { + class => "reply-link", + title => loc('Reply'), + path => $UpdatePath + .'?id='. $Object->id + .'&QuoteTransaction='. $Transaction->id + .'&Action=Respond' + , + }; + } + if ( $UpdatePath && $has_right{'CommentOn'} + && ( $can_modify + || $Object->CurrentUserHasRight( $has_right{'CommentOn'} ) + ) + ) { + push @actions, { + class => "comment-link", + title => loc('Comment'), + path => $UpdatePath + .'?id='. $Object->id + .'&QuoteTransaction='. $Transaction->id + .'&Action=Comment' + , + }; + } + if ( $ForwardPath && $has_right{'Forward'} + && $Object->CurrentUserHasRight( $has_right{'Forward'} ) + ) { + push @actions, { + class => "forward-link", + title => loc('Forward'), + path => $ForwardPath + .'?id='. $Object->id + .'&QuoteTransaction='. $Transaction->id + , + }; + } + if ( $EncryptionPath && $can_modify + && RT->Config->Get('Crypt')->{'Enable'} + && RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'} + ) { + push @actions, { + class => "encryption-link", + title => loc('Encrypt/Decrypt'), + path => $EncryptionPath + .'?id='. $Transaction->id + .'&QuoteTransaction='. $Transaction->id + , + }; + } +} + +$m->callback( + %ARGS, + Transaction => $Transaction, + Object => $Object, + + Classes => \@classes, + Actions => \@actions, + Created => \$date, + TimeTaken => \$time, + Description => \$desc, + ShowBody => \$ShowBody, +); + +my $actions = ''; +if ( @actions ) { + my $i = $m->interp; + + foreach my $a ( @actions ) { + $a = '<a' + .' href="'. $i->apply_escapes( $a->{'path'}, 'h' ) .'"' + . ($a->{'target'} + ? ' target="'. $i->apply_escapes( $a->{'target'}, 'h' ) .'"' + : '' + ) + . ($a->{'class'} + ? ' class="'. $i->apply_escapes( $a->{'class'}, 'h' ) .'"' + : '' + ) + .'>'. $i->apply_escapes( $a->{'title'}, 'h' ) .'</a>' + ; + } + $actions = join ' ', map "[$_]", @actions; +} + +# make date unbreakable +$date = $m->interp->apply_escapes( $date, 'h' ); +$date =~ s/\s/ /g; +</%INIT> diff --git a/rt/share/html/Elements/ShowTransactionAttachments b/rt/share/html/Elements/ShowTransactionAttachments new file mode 100644 index 000000000..7aeded644 --- /dev/null +++ b/rt/share/html/Elements/ShowTransactionAttachments @@ -0,0 +1,293 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 }}} +<%PERL> +# Find all the attachments which have parent $Parent +# For each of these attachments +foreach my $message ( @{ $Attachments->{ $Parent || 0 } || [] } ) { + $m->comp( 'ShowMessageHeaders', + WarnUnsigned => $WarnUnsigned, + Message => $message, + DisplayHeaders => \@DisplayHeaders, + ); + + my $name = defined $message->Filename && length $message->Filename ? $message->Filename : ''; + if ( $message->ContentLength or $name ) { +</%PERL> +<div class="downloadattachment"> +<a href="<% $AttachmentPath %>/<% $Transaction->Id %>/<% $message->Id %>/<% $name | u%>"><&|/l&>Download</&> <% length $name ? $name : loc('(untitled)') %></a>\ +% if ( $DownloadableHeaders && ! length $name && $message->ContentType =~ /text/ ) { + / <a href="<% $AttachmentPath %>/WithHeaders/<% $message->Id %>"><% loc('with headers') %></a> +% } +% $m->callback(CallbackName => 'AfterDownloadLinks', ARGSRef => \%ARGS, Object => $Object, Transaction => $Transaction, Attachment => $message); +<br /> +<span class="downloadcontenttype"><% $message->ContentType %> <% $message->FriendlyContentLength %></span> +</div> +% } +%# If there is sub-messages, open a dedicated div +% if ( $Attachments->{ $message->id } ) { +<div class="messageattachments"> +% } else { +<div class="messagebody"> +% } +<%PERL> + +$render_attachment->( $message ); + +$m->comp( + $m->current_comp, + %ARGS, + Parent => $message->id, + ParentObj => $message, + + displayed_inline => $displayed_inline, +); + +</%PERL> +</div> +% } +<%ARGS> +$Transaction +$Object => $Transaction->Object +$ShowHeaders => 0 +$DownloadableHeaders => 1 +$AttachmentPath => undef +$Attachments => {} +$AttachmentContent => {} +$Parent => 0 +$ParentObj => undef +$WarnUnsigned => 0 + +# Keep track of CID images we display inline +$displayed_inline => {} +</%ARGS> +<%INIT> +my @DisplayHeaders=qw(_all); +if ( $Transaction->Type =~ /EmailRecord$/ ) { + @DisplayHeaders = qw(To Cc Bcc); +} + +# If the transaction has anything attached to it at all +elsif (!$ShowHeaders) { + @DisplayHeaders = qw(To From RT-Send-Cc Cc Bcc Date Subject); + push @DisplayHeaders, 'RT-Send-Bcc' if RT->Config->Get('ShowBccHeader'); +} + +$m->callback(CallbackName => 'MassageDisplayHeaders', DisplayHeaders => \@DisplayHeaders, Transaction => $Transaction, ShowHeaders => $ShowHeaders); + +my $render_attachment = sub { + my $message = shift; + my $name = defined $message->Filename && length $message->Filename ? $message->Filename : ''; + + my $content_type = lc $message->ContentType; + + # if it has a content-disposition: attachment, don't show inline + my $disposition = $message->GetHeader('Content-Disposition'); + + if ( $disposition && $disposition =~ /^\s*attachment/i ) { + $disposition = 'attachment'; + } else { + $disposition = 'inline'; + } + + # If it's text + if ( $content_type =~ m{^(text|message)/} ) { + my $max_size = RT->Config->Get( 'MaxInlineBody', $session{'CurrentUser'} ); + if ( $disposition ne 'inline' ) { + $m->out('<p>'. loc( 'Message body is not shown because sender requested not to inline it.' ) .'</p>'); + return; + } + elsif ( length $name && RT->Config->Get('SuppressInlineTextFiles', $session{'CurrentUser'} ) ) { + $m->out('<p>'. loc( 'Text file is not shown because it is disabled in preferences.' ) .'</p>'); + return; + } + elsif ( $max_size && $message->ContentLength > $max_size ) { + $m->out('<p>'. loc( 'Message body is not shown because it is too large.' ) .'</p>'); + return; + } + + if ( + + # it's a toplevel object + !$ParentObj + + # or its parent isn't a multipart alternative + || ( $ParentObj->ContentType !~ m{^multipart/(?:alternative|related)$}i ) + + # or it's of our prefered alterative type + || ( + ( + RT->Config->Get('PreferRichText', $session{CurrentUser}) + && ( $content_type =~ m{^text/(?:html|enriched)$} ) + ) + || ( !RT->Config->Get('PreferRichText', $session{CurrentUser}) + && ( $content_type !~ m{^text/(?:html|enriched)$} ) + ) + ) + ) { + + my $content; + # If we've cached the content, use it from there + if (my $x = $AttachmentContent->{ $Transaction->id }->{$message->id}) { + $content = $x->Content; + } + else { + $content = $message->Content; + } + + $RT::Logger->debug( + "Rendering attachment #". $message->id + ." of '$content_type' type" + ); + + # if it's a text/html clean the body and show it + if ( $content_type eq 'text/html' ) { + $content = $m->comp( '/Elements/ScrubHTML', Content => $content ); + + if (RT->Config->Get('ShowTransactionImages')) { + my @rewritten = RT::Interface::Web::RewriteInlineImages( + Content => \$content, + Attachment => $message, + # Not technically correct to search all parts of the + # MIME structure, but it saves having to go to the + # database again and is unlikely to break display. + Related => [ map { @$_ } values %$Attachments ], + AttachmentPath => $AttachmentPath, + ); + $displayed_inline->{$_}++ for @rewritten; + } + + $m->comp( + '/Elements/MakeClicky', + content => \$content, + html => 1, + object => $Object, + ); + + if ( !length $name && RT->Config->Get( 'QuoteFolding', $session{CurrentUser} ) ) { + eval { + require HTML::Quoted; + $content = HTML::Quoted->extract($content) + }; + if ($@) { + RT->Logger->error( + "HTML::Quoted couldn't process attachment #@{[$message->id]}: $@." + . " This is a bug, please report it to rt-bugs\@bestpractical.com."); + } + } + + $m->comp( + 'ShowMessageStanza', + Message => $content, + Transaction => $Transaction, + ContentType => 'text/html', + ); + } + + elsif ( $content_type eq 'text/enriched' ) { + $content = $m->comp( '/Elements/ScrubHTML', Content => $content ); + $m->out( $content ); + } + + # It's a text type we don't have special handling for + else { + if ( !length $name && RT->Config->Get( 'QuoteFolding', $session{CurrentUser} ) ) { + eval { + require Text::Quoted; + Text::Quoted::set_quote_characters(undef); + $content = Text::Quoted::extract($content); + }; + if ($@) { + RT->Logger->error( + "Text::Quoted couldn't process attachment #@{[$message->id]}: $@." + . " This is a bug, please report it to rt-bugs\@bestpractical.com."); + } + } + + $m->comp( + 'ShowMessageStanza', + Message => $content, + Transaction => $Transaction, + ContentType => 'text/plain', + ); + } + } + } + + # if it's an image, show it as an image + elsif ( $content_type =~ m{^image/} ) { + if (not RT->Config->Get('ShowTransactionImages')) { + $m->out('<p><i>'. loc( 'Image not shown because display is disabled in system configuration.' ) .'</i></p>'); + return; + } + elsif ( $displayed_inline->{$message->Id} ) { + $m->out('<p><i>'. loc( 'Image displayed inline above' ) .'</i></p>'); + return; + } + elsif ( $disposition ne 'inline' ) { + $m->out('<p>'. loc( 'Image not shown because sender requested not to inline it.' ) .'</p>'); + return; + } + + my $filename = length $name ? $name : loc('(untitled)'); + my $efilename = $m->interp->apply_escapes( $filename, 'h' ); + $m->out( + qq{<img alt="$efilename" title="$efilename"} + . ' src="'. $AttachmentPath .'/'. $Transaction->Id .'/'. $message->Id .'/' + . $m->interp->apply_escapes( $filename, 'u', 'h' ) + . '" />' + ); + } + elsif ( $message->ContentLength && $message->ContentLength > 0 ) { + $m->out( '<p>' . + loc( 'Message body not shown because it is not plain text.' ) . + '</p>' + ); + } +}; + +</%INIT> diff --git a/rt/share/html/Elements/ShowUser b/rt/share/html/Elements/ShowUser index c58d33b59..dd487a876 100644 --- a/rt/share/html/Elements/ShowUser +++ b/rt/share/html/Elements/ShowUser @@ -45,28 +45,51 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -%# Released under the terms of version 2 of the GNU Public License <%INIT> # $User is an RT::User object # $Address is Email::Address object -my $comp = '/Elements/ShowUser'. ucfirst lc $style; -unless ( RT::Interface::Web->ComponentPathIsSafe($comp) and $m->comp_exists( $comp ) ) { - $RT::Logger->error( - 'Either system config or user #' - . $session{'CurrentUser'}->id - . ' picked UsernameFormat '. $style - . ', but '. $comp . "doesn't exist" - ); - return $m->comp('/Elements/ShowUserConcise', - User => $User, Address => $Address, NoEscape => $NoEscape - ); +my $display = RT::User->Format( + User => $User, + Address => $Address, + CurrentUser => $session{CurrentUser}, + Format => $style, +); + +# RT::User->Format does this itself, but we want to make sure we have a $User +# if at all possible for the rest of our code below. +if ($Address and not $User) { + $User = RT::User->new( $session{CurrentUser} ); + $User->LoadByEmail( $Address->address ); + undef $User unless $User->id; } -return $m->comp( $comp, User => $User, Address => $Address, NoEscape => $NoEscape ); + +my %system_user = ( + RT->Nobody->id => 1, + RT->SystemUser->id => 1, +); + +$m->callback( + ARGSRef => \%ARGS, + User => $User, + Address => $Address, + display => \$display, + system_user => \%system_user, + CallbackName => 'Modify', +); </%INIT> <%ARGS> $User => undef $Address => undef -$NoEscape => 0 -$style => RT->Config->Get('UsernameFormat', $session{'CurrentUser'}) +$style => undef +$Link => 1 </%ARGS> +<span class="user" <% $User && $User->id ? 'data-user-id="'.$User->id.'"' : "" |n %>>\ +% if ($Link and $User and $User->id and not $system_user{$User->id} and $session{CurrentUser}->Privileged) { +<a href="<% RT->Config->Get("WebPath") %>/User/Summary.html?id=<% $User->id %>">\ +<% $display %>\ +</a>\ +% } else { +<% $display %>\ +% } +</span>\ diff --git a/rt/share/html/Elements/SimpleSearch b/rt/share/html/Elements/SimpleSearch index d6287f19b..7db2acaf3 100755 --- a/rt/share/html/Elements/SimpleSearch +++ b/rt/share/html/Elements/SimpleSearch @@ -46,8 +46,11 @@ %# %# END BPS TAGGED BLOCK }}} <form action="<% RT->Config->Get('WebPath') %><% $SendTo %>" id="simple-search"> - <input size="12" name="q" autocomplete="off" accesskey="0" class="field" value="<% $Placeholder %>..." onfocus="if (this.value==(<% $Placeholder, |n,j %>+'...')) this.value=''" /> + <input size="12" name="q" accesskey="0" class="field" value="<% $value %>" placeholder="<% $Placeholder %>..." /> </form> +<%init> +my $value = defined $DECODED_ARGS->{q} ? $DECODED_ARGS->{q} : ''; +</%init> <%ARGS> $SendTo => '/Search/Simple.html' $Placeholder => loc('Search') diff --git a/rt/share/html/Elements/Submit b/rt/share/html/Elements/Submit index c26c468be..a5dcdf7ad 100755 --- a/rt/share/html/Elements/Submit +++ b/rt/share/html/Elements/Submit @@ -52,10 +52,10 @@ id="<%$id%>" > <div class="extra-buttons"> % if ($CheckAll) { - <input type="button" value="<%$CheckAllLabel%>" onclick="setCheckbox(this.form, <% $match %>, true);return false;" class="button" /> + <input type="button" value="<%$CheckAllLabel%>" onclick="setCheckbox(this, <% $match %>, true);return false;" class="button" /> % } % if ($ClearAll) { - <input type="button" value="<%$ClearAllLabel%>" onclick="setCheckbox(this.form, <% $match %>, false);return false;" class="button" /> + <input type="button" value="<%$ClearAllLabel%>" onclick="setCheckbox(this, <% $match %>, false);return false;" class="button" /> % } % if ($Reset) { <input type="reset" value="<%$ResetLabel%>" class="button" /> diff --git a/rt/share/html/Elements/TSVExport b/rt/share/html/Elements/TSVExport new file mode 100644 index 000000000..0c466c4a0 --- /dev/null +++ b/rt/share/html/Elements/TSVExport @@ -0,0 +1,131 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 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 }}} +<%ARGS> +$Class => undef +$Collection +$Format +$PreserveNewLines => 0 +</%ARGS> +<%ONCE> +my $no_html = HTML::Scrubber->new( deny => '*' ); +</%ONCE> +<%INIT> +require HTML::Entities; +$Class ||= $Collection->ColumnMapClassName; + +#no, it isn't# $r->content_type('application/vnd.ms-excel'); +$r->content_type('text/tab-separated-values'); + +my $DisplayFormat = $m->comp('/Elements/ScrubHTML', Content => $Format); + +my @Format = $m->comp('/Elements/CollectionAsTable/ParseFormat', Format => $DisplayFormat); + +my @columns; + +my $should_loc = { map { $_ => 1 } qw(Status) }; + +my $col_entry = sub { + my $col = shift; + # in tsv output, "#" is often a comment character but we use it for "id" + delete $col->{title} + if $col->{title} and $col->{title} =~ /^\s*#\s*$/; + return { + header => loc($col->{title} || $col->{attribute}), + map => $m->comp( + "/Elements/ColumnMap", + Name => $col->{attribute}, + Attr => 'value', + Class => $Class, + ), + should_loc => $should_loc->{$col->{attribute}}, + } +}; + +if ($PreserveNewLines) { + my $col = []; + push @columns, $col; + for (@Format) { + if ($_->{title} eq 'NEWLINE') { + $col = []; + push @columns, $col; + } + else { + push @$col, $col_entry->($_); + } + } +} +else { + push @columns, [map { $_->{attribute} + ? $col_entry->($_) + : () } @Format]; +} + +for (@columns) { + $m->out(join("\t", map { $_->{header} } @$_)."\n"); +} + +my $i = 0; +my $ii = 0; +while (my $row = $Collection->Next) { + for my $col (@columns) { + $m->out(join("\t", map { + my $val = ProcessColumnMapValue($_->{map}, Arguments => [$row, $ii++], Escape => 0); + $val = loc($val) if $_->{should_loc}; + # remove tabs from all field values, they screw up the tsv + $val = '' unless defined $val; + $val =~ s/(?:\n|\r)+/ /g; $val =~ s{\t}{ }g; + $val = $no_html->scrub($val); + $val = HTML::Entities::decode_entities($val); + $val; + } @$col)."\n"); + } + $m->flush_buffer unless ++$i % 10; +} +$m->abort(); + +</%INIT> diff --git a/rt/share/html/Elements/Tabs b/rt/share/html/Elements/Tabs index 3e28e2578..46d2bd89d 100755 --- a/rt/share/html/Elements/Tabs +++ b/rt/share/html/Elements/Tabs @@ -62,7 +62,7 @@ my $query_string = sub { my $build_admin_menu = sub { my $top = shift; - my $admin = $top->child( config => title => loc('Configuration'), path => '/Admin/', sort_order => 99 ); + my $admin = $top->child( admin => title => loc('Admin'), path => '/Admin/' ); if ( $session{'CurrentUser'}->HasRight( Object => RT->System, Right => 'AdminUsers' ) ) { my $users = $admin->child( users => title => loc('Users'), @@ -98,6 +98,16 @@ my $build_admin_menu = sub { $cfs->child( create => title => loc('Create'), path => "/Admin/CustomFields/Modify.html?Create=1" ); } + if ( $session{'CurrentUser'}->HasRight( Object => RT->System, Right => 'ModifyScrips' ) ) { + my $scrips = $admin->child( 'scrips' => + title => loc('Scrips'), + description => loc('Manage scrips'), + path => '/Admin/Scrips/', + ); + $scrips->child( select => title => loc('Select'), path => "/Admin/Scrips/" ); + $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html" ); + } + my $admin_global = $admin->child( global => title => loc('Global'), description => loc('Manage properties and configuration which apply to all queues'), @@ -110,7 +120,7 @@ my $build_admin_menu = sub { path => '/Admin/Global/Scrips.html', ); $scrips->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" ); - $scrips->child( create => title => loc('Create'), path => "/Admin/Global/Scrip.html?Create=1" ); + $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Global=1" ); my $templates = $admin_global->child( templates => title => loc('Templates'), @@ -172,11 +182,11 @@ my $build_admin_menu = sub { my $cfs = $article_admin->child( 'custom-fields' => title => loc('Custom Fields'), - path => '/Admin/CustomFields/index.html?'.$m->comp('/Elements/QueryString', type => 'RT::Class-RT::Article'), + path => '/Admin/CustomFields/index.html?'.$m->comp('/Elements/QueryString', Type => 'RT::Class-RT::Article'), ); $cfs->child( select => title => loc('Select'), - path => '/Admin/CustomFields/index.html?'.$m->comp('/Elements/QueryString', type => 'RT::Class-RT::Article'), + path => '/Admin/CustomFields/index.html?'.$m->comp('/Elements/QueryString', Type => 'RT::Class-RT::Article'), ); $cfs->child( create => title => loc('Create'), @@ -198,6 +208,11 @@ my $build_admin_menu = sub { description => loc('Modify the default "RT at a glance" view'), path => '/Admin/Global/MyRT.html', ); + $admin_global->child( 'dashboards-in-menu' => + title => loc('Dashboards in menu'), + description => loc('Customize dashboards in menu'), + path => '/Admin/Global/DashboardsInMenu.html', + ); $admin_global->child( 'topics' => title => loc('Topics'), description => loc('Modify global article topics'), @@ -281,12 +296,13 @@ my $build_admin_menu = sub { my $scrips = $queue->child( scrips => title => loc('Scrips'), path => "/Admin/Queues/Scrips.html?id=" . $id); $scrips->child( select => title => loc('Select'), path => "/Admin/Queues/Scrips.html?id=" . $id ); - $scrips->child( create => title => loc('Create'), path => "/Admin/Queues/Scrip.html?Create=1;Queue=" . $id); + $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Queue=" . $id); - my $ticket_cfs = $queue->child( 'ticket-custom-fields' => title => loc('Ticket Custom Fields'), + my $cfs = $queue->child( 'custom-fields' => title => loc('Custom Fields') ); + my $ticket_cfs = $cfs->child( 'tickets' => title => loc('Tickets'), path => '/Admin/Queues/CustomFields.html?SubType=RT::Ticket&id=' . $id ); - my $txn_cfs = $queue->child( 'transaction-custom-fields' => title => loc('Transaction Custom Fields'), + my $txn_cfs = $cfs->child( 'transactions' => title => loc('Transactions'), path => '/Admin/Queues/CustomFields.html?SubType=RT::Ticket-RT::Transaction&id='.$id ); $queue->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/Queues/GroupRights.html?id=".$id ); @@ -297,7 +313,7 @@ my $build_admin_menu = sub { } } } - if ( $request_path =~ m{^/Admin/Users} ) { + if ( $request_path =~ m{^(/Admin/Users|/User/(Summary|History)\.html)} and $admin->child("users") ) { if ( $DECODED_ARGS->{'id'} && $DECODED_ARGS->{'id'} =~ /^\d+$/ ) { my $id = $DECODED_ARGS->{'id'}; my $obj = RT::User->new( $session{'CurrentUser'} ); @@ -309,9 +325,14 @@ my $build_admin_menu = sub { $tabs->child( memberships => title => loc('Memberships'), path => "/Admin/Users/Memberships.html?id=" . $id ); $tabs->child( history => title => loc('History'), path => "/Admin/Users/History.html?id=" . $id ); $tabs->child( 'my-rt' => title => loc('RT at a glance'), path => "/Admin/Users/MyRT.html?id=" . $id ); - if ( RT->Config->Get('GnuPG')->{'Enable'} ) { - $tabs->child( pgp => title => loc('GnuPG'), path => "/Admin/Users/GnuPG.html?id=" . $id ); + $tabs->child( 'dashboards-in-menu' => + title => loc('Dashboards in menu'), + path => '/Admin/Users/DashboardsInMenu.html?id=' . $id, + ); + if ( RT->Config->Get('Crypt')->{'Enable'} ) { + $tabs->child( keys => title => loc('Private keys'), path => "/Admin/Users/Keys.html?id=" . $id ); } + $tabs->child( 'summary' => title => loc('User Summary'), path => "/User/Summary.html?id=" . $id ); } } @@ -323,10 +344,11 @@ my $build_admin_menu = sub { my $obj = RT::Group->new( $session{'CurrentUser'} ); $obj->Load($id); - if ( $obj and $obj->id ) { + if ( $obj and $obj->id ) { my $tabs = PageMenu(); $tabs->child( basics => title => loc('Basics'), path => "/Admin/Groups/Modify.html?id=" . $obj->id ); $tabs->child( members => title => loc('Members'), path => "/Admin/Groups/Members.html?id=" . $obj->id ); + $tabs->child( memberships => title => loc('Memberships'), path => "/Admin/Groups/Memberships.html?id=" . $obj->id ); $tabs->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/Groups/GroupRights.html?id=" . $obj->id ); $tabs->child( 'user-rights' => title => loc('User Rights'), path => "/Admin/Groups/UserRights.html?id=" . $obj->id ); $tabs->child( history => title => loc('History'), path => "/Admin/Groups/History.html?id=" . $obj->id ); @@ -342,23 +364,78 @@ my $build_admin_menu = sub { if ( $obj and $obj->id ) { my $tabs = PageMenu(); - $tabs->child( basics => title => loc('Basics'), path => "/Admin/CustomFields/Modify.html?id=".$id ); - $tabs->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/CustomFields/GroupRights.html?id=" . $id ); - $tabs->child( 'user-rights' => title => loc('User Rights'), path => "/Admin/CustomFields/UserRights.html?id=" . $id ); - $tabs->child( 'applies-to' => title => loc('Applies to'), path => "/Admin/CustomFields/Objects.html?id=" . $id ); + $tabs->child( basics => title => loc('Basics'), path => "/Admin/CustomFields/Modify.html?id=".$id ); + $tabs->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/CustomFields/GroupRights.html?id=" . $id ); + $tabs->child( 'user-rights' => title => loc('User Rights'), path => "/Admin/CustomFields/UserRights.html?id=" . $id ); + unless ( $obj->IsOnlyGlobal ) { + $tabs->child( 'applies-to' => title => loc('Applies to'), path => "/Admin/CustomFields/Objects.html?id=" . $id ); + } } } } - if ( $request_path =~ m{^/Admin/Global/(Scrip|Template)s?\.html} ) { - my $type = $1; - my $tabs = PageMenu(); + if ( $request_path =~ m{^/Admin/Scrips/} ) { + if ( $m->request_args->{'id'} && $m->request_args->{'id'} =~ /^\d+$/ ) { + my $id = $m->request_args->{'id'}; + my $obj = RT::Scrip->new( $session{'CurrentUser'} ); + $obj->Load($id); + + my $tabs = PageMenu(); + + my ( $admin_cat, $create_path_arg, $from_query_param ); + my $from_arg = $DECODED_ARGS->{'From'} || q{}; + my ($from_queue) = $from_arg =~ /^(\d+)$/; + if ( $from_queue ) { + $admin_cat = "Queues/Scrips.html?id=$from_queue"; + $create_path_arg = "?Queue=$from_queue"; + $from_query_param = "&From=$from_queue"; + } + elsif ( $from_arg eq 'Global' ) { + $admin_cat = 'Global/Scrips.html'; + $create_path_arg = '?Global=1'; + $from_query_param = '&From=Global'; + } + else { + $admin_cat = 'Scrips'; + $from_query_param = $create_path_arg = q{}; + } + my $scrips = $tabs->child( scrips => title => loc('Scrips'), path => "/Admin/${admin_cat}" ); + $scrips->child( select => title => loc('Select'), path => "/Admin/${admin_cat}" ); + $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html${create_path_arg}" ); - # With only two elements, swapping between dropdown and menu is kinda dumb - # In the glorious future this should be cleaner. + $tabs->child( basics => title => loc('Basics') => path => "/Admin/Scrips/Modify.html?id=" . $id . $from_query_param ); + $tabs->child( 'applies-to' => title => loc('Applies to'), path => "/Admin/Scrips/Objects.html?id=" . $id . $from_query_param ); + } + elsif ( $request_path =~ m{^/Admin/Scrips/(index\.html)?$} ) { + PageMenu->child( select => title => loc('Select') => path => "/Admin/Scrips/" ); + PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html" ); + } + elsif ( $request_path =~ m{^/Admin/Scrips/Create\.html$} ) { + my ($queue) = $DECODED_ARGS->{'Queue'} && $DECODED_ARGS->{'Queue'} =~ /^(\d+)$/; + my $global_arg = $DECODED_ARGS->{'Global'}; + if ($queue) { + PageMenu->child( select => title => loc('Select') => path => "/Admin/Queues/Scrips.html?id=$queue" ); + PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html?Queue=$queue" ); + } elsif ($global_arg) { + PageMenu->child( select => title => loc('Select') => path => "/Admin/Global/Scrips.html" ); + PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html?Global=1" ); + } else { + PageMenu->child( select => title => loc('Select') => path => "/Admin/Scrips" ); + PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html" ); + } + } + } + + if ( $request_path =~ m{^/Admin/Global/Scrips\.html} ) { + my $tabs = PageMenu(); + $tabs->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" ); + $tabs->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Global=1" ); + } - $tabs->child( select => title => loc('Select'), path => "/Admin/Global/${type}s.html" ); - $tabs->child( create => title => loc('Create'), path => "/Admin/Global/${type}.html?Create=1" ); + if ( $request_path =~ m{^/Admin/Global/Templates?\.html} ) { + my $tabs = PageMenu(); + $tabs->child( select => title => loc('Select'), path => "/Admin/Global/Templates.html" ); + $tabs->child( create => title => loc('Create'), path => "/Admin/Global/Template.html?Create=1" ); } if ( $request_path =~ m{^/Admin/Articles/Classes/} ) { @@ -386,24 +463,40 @@ my $build_admin_menu = sub { } }; - my $build_main_nav = sub { PageWidgets()->child( simple_search => raw_html => $m->scomp('SimpleSearch') ); PageWidgets()->child( create_ticket => raw_html => $m->scomp('CreateTicket') ); my $home = Menu->child( home => title => loc('Homepage'), path => '/' ); - # We explicitly exclude superusers; otherwise the dashboards for - # groups you're not in (but can see the dashboards of by dint of - # being a superuser) would push the useful ones from the groups - # you're actually in off of the stack. - my @dashboards = $m->comp("/Dashboards/Elements/ListOfDashboards", IncludeSuperuserGroups => 0); - my $limit = 7; + unless ($session{'dashboards_in_menu'}) { + my $dashboards_in_menu = $session{CurrentUser}->UserObj->Preferences( + 'DashboardsInMenu', + {}, + ); + + unless ($dashboards_in_menu->{dashboards}) { + my ($default_dashboards) = + RT::System->new( $session{'CurrentUser'} ) + ->Attributes + ->Named('DashboardsInMenu'); + if ($default_dashboards) { + $dashboards_in_menu = $default_dashboards->Content; + } + } - my $more = 0; - if ( @dashboards > $limit ) { - $more = 1; - splice @dashboards, $limit; + $session{'dashboards_in_menu'} = $dashboards_in_menu->{dashboards} || []; + } + + my @dashboards; + for my $id ( @{$session{'dashboards_in_menu'}} ) { + my $dash = RT::Dashboard->new( $session{CurrentUser} ); + my ( $status, $msg ) = $dash->LoadById($id); + if ( $status ) { + push @dashboards, $dash; + } else { + $RT::Logger->warning( "Failed to load dashboard $id: $msg" ); + } } my $dashes = Menu()->child('home'); @@ -414,24 +507,34 @@ my $build_main_nav = sub { path => '/Dashboards/' . $dash->id . '/' . $dash->Name ); } - - $dashes->child( more => title => loc('All Dashboards'), path => 'Dashboards/index.html' ); } + $dashes->child( edit => title => loc('Update This Menu'), path => 'Prefs/DashboardsInMenu.html' ); + $dashes->child( more => title => loc('All Dashboards'), path => 'Dashboards/index.html' ); my $dashboard = RT::Dashboard->new( $session{CurrentUser} ); if ( $dashboard->CurrentUserCanCreateAny ) { $dashes->child('dashboard_create' => title => loc('New Dashboard'), path => "/Dashboards/Modify.html?Create=1" ); } - my $tickets = Menu->child( search => title => loc('Tickets'), path => '/Search/Build.html' ); + my $search = Menu->child( search => title => loc('Search'), path => '/Search/Simple.html' ); + + my $tickets = $search->child( tickets => title => loc('Tickets'), path => '/Search/Build.html' ); $tickets->child( simple => title => loc('Simple Search'), path => "/Search/Simple.html" ); $tickets->child( new => title => loc('New Search'), path => "/Search/Build.html?NewQuery=1" ); + $search->child( articles => title => loc('Articles'), path => "/Articles/Article/Search.html" ) + if $session{CurrentUser}->HasRight( Right => 'ShowArticlesMenu', Object => RT->System ); + + $search->child( users => title => loc('Users'), path => "/User/Search.html" ); + + if ($session{CurrentUser}->HasRight( Right => 'ShowArticlesMenu', Object => RT->System )) { + my $articles = Menu->child( articles => title => loc('Articles'), path => "/Articles/index.html"); + $articles->child( articles => title => loc('Overview'), path => "/Articles/index.html" ); + $articles->child( topics => title => loc('Topics'), path => "/Articles/Topics.html" ); + $articles->child( create => title => loc('Create'), path => "/Articles/Article/PreCreate.html" ); + $articles->child( search => title => loc('Search'), path => "/Articles/Article/Search.html" ); + } my $tools = Menu->child( tools => title => loc('Tools'), path => '/Tools/index.html' ); - my $articles = $tools->child( articles => title => loc('Articles'), path => "/Articles/index.html"); - $articles->child( articles => title => loc('Overview'), path => "/Articles/index.html" ); - $articles->child( search => title => loc('Search'), path => "/Articles/Article/Search.html" ); - $articles->child( topics => title => loc('Topics'), path => "/Articles/Topics.html" ); $tools->child( my_day => title => loc('My Day'), @@ -447,12 +550,6 @@ my $build_main_nav = sub { ); } - $tools->child( offline => - title => loc('Offline'), - description => loc('Create tickets offline'), - path => '/Tools/Offline.html', - ); - if ( $session{'CurrentUser'}->HasRight( Right => 'ShowApprovalsTab', Object => RT->System ) ) { $tools->child( approval => title => loc('Approval'), @@ -463,7 +560,7 @@ my $build_main_nav = sub { if ( $session{'CurrentUser'}->HasRight( Right => 'ShowConfigTab', Object => RT->System ) ) { - $build_admin_menu->($tools); + $build_admin_menu->(Menu()); } my $username = '<span class="current-user">' @@ -472,6 +569,7 @@ my $build_main_nav = sub { my $about_me = Menu->child( 'preferences' => title => loc('Logged in as [_1]', $username), escape_title => 0, + path => '/User/Summary.html?id=' . $session{CurrentUser}->id, sort_order => 99, ); @@ -479,10 +577,14 @@ my $build_main_nav = sub { if ( $session{'CurrentUser'}->UserObj && $session{'CurrentUser'}->HasRight( Right => 'ModifySelf', Object => RT->System )) { my $settings = $about_me->child( settings => title => loc('Settings'), path => '/Prefs/Other.html' ); - $settings->child( options => title => loc('Options'), path => '/Prefs/Other.html' ); + $settings->child( options => title => loc('Preferences'), path => '/Prefs/Other.html' ); $settings->child( about_me => title => loc('About me'), path => '/User/Prefs.html' ); $settings->child( search_options => title => loc('Search options'), path => '/Prefs/SearchOptions.html' ); $settings->child( myrt => title => loc('RT at a glance'), path => '/Prefs/MyRT.html' ); + $settings->child( dashboards_in_menu => + title => loc('Dashboards in menu'), + path => '/Prefs/DashboardsInMenu.html', + ); $settings->child( quicksearch => title => loc('Quick search'), path => '/Prefs/Quicksearch.html' ); my $search_menu = $settings->child( 'saved-searches' => title => loc('Saved Searches') ); @@ -500,8 +602,8 @@ my $build_main_nav = sub { } } if ( $session{'CurrentUser'}->Name - && ( !RT->Config->Get('WebExternalAuth') - || RT->Config->Get('WebFallbackToInternalAuth') )) { + && ( !RT->Config->Get('WebRemoteUserAuth') + || RT->Config->Get('WebFallbackToRTLogin') )) { $about_me->child( logout => title => loc('Logout'), path => '/NoAuth/Logout.html' ); } if ( $request_path =~ m{^/Dashboards/(\d+)?}) { @@ -539,7 +641,7 @@ my $build_main_nav = sub { $tabs->child( history => title => loc('History'), path => "/Ticket/History.html?id=" . $id ); my %can = %{ $obj->CurrentUser->PrincipalObj->HasRights( Object => $obj ) }; - $can{'_ModifyOwner'} = $can{'OwnTicket'} || $can{'TakeTicket'} || $can{'StealTicket'}; + $can{'_ModifyOwner'} = $obj->CurrentUserCanSetOwner(); my $can = sub { unless ($_[0] eq 'ExecuteCode') { return $can{$_[0]} || $can{'SuperUser'}; @@ -591,7 +693,7 @@ my $build_main_nav = sub { && $obj->HasUnresolvedDependencies; my $current = $obj->Status; - my $lifecycle = $obj->QueueObj->Lifecycle; + my $lifecycle = $obj->LifecycleObj; my $i = 1; foreach my $info ( $lifecycle->Actions($current) ) { my $next = $info->{'to'}; @@ -606,42 +708,31 @@ my $build_main_nav = sub { my $action = $info->{'update'} || ''; my $url = '/Ticket/'; - if ($action) { - $url .= "Update.html?" - . $query_string->( - Action => $action, - DefaultStatus => $next, - id => $id, - ); - } else { - $url .= "Display.html?" - . $query_string->( - Status => $next, - id => $id, - ); - } + $url .= "Update.html?". $query_string->( + $action + ? (Action => $action) + : (SubmitTicket => 1, Status => $next), + DefaultStatus => $next, + id => $id, + ); my $key = $info->{'label'} || ucfirst($next); $actions->child( $key => title => loc( $key ), path => $url); } - if ( $can->('OwnTicket') ) { - if ( $obj->OwnerObj->Id == RT->Nobody->id - && ( $can->('ModifyTicket') or $can->('TakeTicket') ) ) { - $actions->child( take => title => loc('Take'), path => "/Ticket/Display.html?Action=Take;id=" . $id ); - } - - elsif ( $obj->OwnerObj->id != RT->Nobody->id - && $obj->OwnerObj->id != $session{CurrentUser}->id - && ( $can->('ModifyTicket') or $can->('StealTicket') ) ) { - $actions->child( steal => title => loc('Steal'), path => "/Ticket/Display.html?Action=Steal;id=" . $id ); - } + my ($can_take, $tmsg) = $obj->CurrentUserCanSetOwner( Type => 'Take' ); + my ($can_steal, $smsg) = $obj->CurrentUserCanSetOwner( Type => 'Steal' ); + if ( $can_take ){ + $actions->child( take => title => loc('Take'), path => "/Ticket/Display.html?Action=Take;id=" . $id ); + } + elsif ( $can_steal ){ + $actions->child( steal => title => loc('Steal'), path => "/Ticket/Display.html?Action=Steal;id=" . $id ); } # TODO needs a "Can extract article into a class applied to this queue" check $actions->child( 'extract-article' => title => loc('Extract Article'), path => "/Articles/Article/ExtractIntoClass.html?Ticket=".$obj->id, - ); + ) if $session{CurrentUser}->HasRight( Right => 'ShowArticlesMenu', Object => RT->System ); if ( defined $session{"tickets"} ) { # we have to update session data if we get new ItemMap @@ -653,7 +744,7 @@ my $build_main_nav = sub { $session{"tickets"}->PrepForSerialization(); } - my $search = Menu()->child('search'); + my $search = Menu()->child('search')->child('tickets'); # Don't display prev links if we're on the first ticket if ( $item_map->{$id}->{prev} ) { $search->child( first => @@ -684,7 +775,7 @@ my $build_main_nav = sub { && $DECODED_ARGS->{'q'} ) ) { - my $search = Menu()->child('search'); + my $search = Menu()->child('search')->child('tickets'); my $args = ''; my $has_query = ''; my $current_search = $session{"CurrentSearchHash"} || {}; @@ -844,11 +935,22 @@ my $build_main_nav = sub { } + if ( $request_path =~ m{^/User/(Summary|History)\.html} ) { + if (PageMenu()->child('summary')) { + # Already set up from having AdminUser and ShowConfigTab; + # but rename "Basics" to "Edit" in this context + PageMenu()->child( 'basics' )->title( loc('Edit') ); + } elsif ( $session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'ShowUserHistory' ) ) { + PageMenu()->child( display => title => loc('Summary'), path => '/User/Summary.html?id=' . $DECODED_ARGS->{'id'} ); + PageMenu()->child( history => title => loc('History'), path => '/User/History.html?id=' . $DECODED_ARGS->{'id'} ); + } + } + if ( $request_path =~ /^\/(?:index.html|$)/ ) { PageMenu()->child( edit => title => loc('Edit'), path => '/Prefs/MyRT.html' ); } - $m->callback( CallbackName => 'Privileged' ); + $m->callback( CallbackName => 'Privileged', Path => $request_path ); }; my $build_selfservice_nav = sub { @@ -890,8 +992,8 @@ my $build_selfservice_nav = sub { } if ( $session{'CurrentUser'}->Name - && ( !RT->Config->Get('WebExternalAuth') - || RT->Config->Get('WebFallbackToInternalAuth') )) { + && ( !RT->Config->Get('WebRemoteUserAuth') + || RT->Config->Get('WebFallbackToRTLogin') )) { $about_me->child( logout => title => loc('Logout'), path => '/NoAuth/Logout.html' ); } @@ -901,7 +1003,7 @@ my $build_selfservice_nav = sub { PageWidgets->child( goto => raw_html => $m->scomp('/SelfService/Elements/GotoTicket') ); - $m->callback( CallbackName => 'SelfService' ); + $m->callback( CallbackName => 'SelfService', Path => $request_path ); }; diff --git a/rt/share/html/Elements/TicketList b/rt/share/html/Elements/TicketList index 4bade4934..b252af6a0 100644 --- a/rt/share/html/Elements/TicketList +++ b/rt/share/html/Elements/TicketList @@ -46,11 +46,15 @@ %# %# END BPS TAGGED BLOCK }}} <%INIT> +RT->Deprecated( + Remove => "4.4", + Instead => "/Elements/CollectionList", +); $m->comp( - '/Elements/CollectionList', - %ARGS, - Class => 'RT::Tickets' - ); + '/Elements/CollectionList', + %ARGS, + Class => 'RT::Tickets' + ); </%INIT> <%ARGS> $Collection => undef diff --git a/rt/share/html/Elements/TitleBox b/rt/share/html/Elements/TitleBox index 3bb0d0345..8d801468a 100644 --- a/rt/share/html/Elements/TitleBox +++ b/rt/share/html/Elements/TitleBox @@ -48,5 +48,6 @@ <&| /Widgets/TitleBox, %ARGS &><% $m->content |n%></&> <%init> # For compatibility with 3.4 +RT->Deprecated( Remove => 4.4, Instead => "/Widgets/TitleBox" ); # $m->comp('/Widgets/TitleBox', %ARGS ); Doesn't actually work </%init> diff --git a/rt/share/html/Elements/TitleBoxEnd b/rt/share/html/Elements/TitleBoxEnd index bf5ef42b7..8b52b077e 100644 --- a/rt/share/html/Elements/TitleBoxEnd +++ b/rt/share/html/Elements/TitleBoxEnd @@ -47,5 +47,6 @@ %# END BPS TAGGED BLOCK }}} <%init> # For compatibility with 3.4 +RT->Deprecated( Remove => 4.4, Instead => "/Widgets/TitleBoxEnd" ); $m->comp('/Widgets/TitleBoxEnd', %ARGS ); </%init> diff --git a/rt/share/html/Elements/TitleBoxStart b/rt/share/html/Elements/TitleBoxStart index 15671aa1c..c639d190c 100644 --- a/rt/share/html/Elements/TitleBoxStart +++ b/rt/share/html/Elements/TitleBoxStart @@ -47,5 +47,6 @@ %# END BPS TAGGED BLOCK }}} <%init> # For compatibility with 3.4 +RT->Deprecated( Remove => 4.4, Instead => "/Widgets/TitleBoxStart" ); $m->comp('/Widgets/TitleBoxStart', %ARGS ); </%init> diff --git a/rt/share/html/Elements/ValidateCustomFields b/rt/share/html/Elements/ValidateCustomFields index 55c1fcebb..7c8edde54 100644 --- a/rt/share/html/Elements/ValidateCustomFields +++ b/rt/share/html/Elements/ValidateCustomFields @@ -48,38 +48,50 @@ <%INIT> my ($valid, @res) = (1, ()); $CustomFields->GotoFirstItem; + +my $CFArgs = _ParseObjectCustomFieldArgs( $ARGSRef )->{ref($Object)}{$Object->Id || 0} || {}; + while ( my $CF = $CustomFields->Next ) { - my $field = $NamePrefix . $CF->Id . "-Value"; + my $submitted = $CFArgs->{$CF->Id}; + # Pick the first grouping + $submitted = $submitted ? $submitted->{(keys %$submitted)[0]} : {}; - my $value; - if ($ARGSRef->{"${field}s-Magic"} and exists $ARGSRef->{"${field}s"}) { - $value = $ARGSRef->{"${field}s"}; + # If we don't have a value and we don't see the Magic, then we're not + # submitting a field. + next if not $ValidateUnsubmitted + and not exists $submitted->{"Value"} + and not exists $submitted->{"Upload"} + and not exists $submitted->{"Values"} + and not $submitted->{"Values-Magic"}; - # We only validate Single Combos -- multis can never be user input - next if ref $value; - } - else { - $value = $ARGSRef->{$field}; - } - $m->notes(('Field-' . $CF->Id) => $value); + # We only validate Single Combos -- multis can never be user input + next if $submitted->{"Values-Magic"} and exists $submitted->{"Values"} + and ref $submitted->{"Values"}; - my @values = (); - if ( ref $value eq 'ARRAY' ) { - @values = @$value; - } elsif ( $CF->Type =~ /text/i ) { - @values = ($value); - } else { - @values = split /\r*\n/, ( defined $value ? $value : ''); - } - @values = grep $_ ne '', - map { - s/\r+\n/\n/g; - s/^\s+//; - s/\s+$//; - $_; + $m->notes(('Field-' . $CF->Id) => $submitted->{Values} // $submitted->{Value}); + + my @values = _NormalizeObjectCustomFieldValue( + CustomField => $CF, + Value => ($submitted->{Values} // $submitted->{Value} // $submitted->{Upload}), + ); + if ($CF->Type =~ /^Date(?:Time)?$/) { + if (not @values) { + my $values = $Object->CustomFieldValues($CF->Id); + while (my $ocfv = $values->Next) { + push @values, $ocfv->Content; + } } - grep defined, @values; - @values = ('') unless @values; + @values = grep { + my $DateObj = RT::Date->new ( $session{'CurrentUser'} ); + $DateObj->Set( + Format => 'unknown', + Value => $_, + ($CF->Type eq "Date" ? (Timezone => 'utc') : ()) + ); + $DateObj->IsSet + } @values; + } + push @values, '' unless @values; for my $value( @values ) { if ($value) { @@ -87,7 +99,7 @@ while ( my $CF = $CustomFields->Next ) { my ($ok, $msg) = $CF->_CanonicalizeValue( $ref ); unless ($ok) { $m->notes( ( 'InvalidField-' . $CF->Id ) => $msg ); - push @res, $msg; + push @res, $CF->Name .': '. $msg; $valid = 0; } } @@ -96,7 +108,7 @@ while ( my $CF = $CustomFields->Next ) { my $msg = loc("Input must match [_1]", $CF->FriendlyPattern); $m->notes( ('InvalidField-' . $CF->Id) => $msg ); - push @res, $msg; + push @res, $CF->Name .': '. $msg; $valid = 0; } } @@ -104,7 +116,8 @@ $m->notes('ValidFields', $valid); return wantarray? ($valid, @res): $valid; </%INIT> <%ARGS> +$Object => RT::Ticket->new( $session{'CurrentUser'}) $CustomFields $ARGSRef -$NamePrefix => "Object-RT::Ticket--CustomField-" +$ValidateUnsubmitted => 0 </%ARGS> |