From 1c538bfabc2cd31f27067505f0c3d1a46cba6ef0 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Thu, 9 Jul 2015 22:18:55 -0700 Subject: RT 4.2.11, ticket#13852 --- rt/share/html/Elements/AddLinks | 106 ++++++++ rt/share/html/Elements/BevelBoxRaisedEnd | 50 ---- rt/share/html/Elements/BevelBoxRaisedStart | 50 ---- rt/share/html/Elements/BulkCustomFields | 105 ++++++++ rt/share/html/Elements/BulkLinks | 197 ++++++++++++++ rt/share/html/Elements/Callback | 5 +- rt/share/html/Elements/CollectionAsTable/Header | 21 +- rt/share/html/Elements/CollectionAsTable/Row | 1 - rt/share/html/Elements/CollectionList | 31 +-- rt/share/html/Elements/CollectionListPaging | 20 +- rt/share/html/Elements/ColumnMap | 110 +++++--- rt/share/html/Elements/Crypt/KeyIssues | 94 +++++++ .../html/Elements/Crypt/SelectKeyForEncryption | 80 ++++++ rt/share/html/Elements/Crypt/SelectKeyForSigning | 67 +++++ rt/share/html/Elements/Crypt/SignEncryptWidget | 188 +++++++++++++ rt/share/html/Elements/CryptStatus | 195 ++++++++++++++ rt/share/html/Elements/EditCustomField | 50 ++-- rt/share/html/Elements/EditCustomFieldAutocomplete | 15 +- rt/share/html/Elements/EditCustomFieldBinary | 17 +- rt/share/html/Elements/EditCustomFieldCombobox | 13 +- .../html/Elements/EditCustomFieldCustomGroupings | 73 +++++ rt/share/html/Elements/EditCustomFieldDate | 3 +- rt/share/html/Elements/EditCustomFieldDateTime | 3 +- rt/share/html/Elements/EditCustomFieldFreeform | 5 +- rt/share/html/Elements/EditCustomFieldImage | 17 +- rt/share/html/Elements/EditCustomFieldSelect | 31 ++- rt/share/html/Elements/EditCustomFieldText | 6 +- rt/share/html/Elements/EditCustomFieldWikitext | 6 +- rt/share/html/Elements/EditCustomFields | 119 +++++++++ rt/share/html/Elements/EditLinks | 94 ++----- rt/share/html/Elements/EditTimeValue | 12 +- rt/share/html/Elements/EmailInput | 4 +- rt/share/html/Elements/Error | 13 +- rt/share/html/Elements/FindUser | 50 ++++ rt/share/html/Elements/FoldStanzaJS | 50 ++++ rt/share/html/Elements/GnuPG/KeyIssues | 92 ------- .../html/Elements/GnuPG/SelectKeyForEncryption | 81 ------ rt/share/html/Elements/GnuPG/SelectKeyForSigning | 64 ----- rt/share/html/Elements/GnuPG/SignEncryptWidget | 181 ------------- rt/share/html/Elements/GotoUser | 62 +++++ rt/share/html/Elements/Header | 52 ++-- rt/share/html/Elements/HeaderJavascript | 20 +- rt/share/html/Elements/JavascriptConfig | 84 ++++++ rt/share/html/Elements/ListActions | 5 +- rt/share/html/Elements/ListMenu | 4 + rt/share/html/Elements/Login | 7 +- rt/share/html/Elements/LoginHelp | 54 ++++ rt/share/html/Elements/Logo | 5 +- rt/share/html/Elements/MakeClicky | 18 +- rt/share/html/Elements/Menu | 36 +-- rt/share/html/Elements/MessageBox | 26 +- rt/share/html/Elements/MyRT | 28 +- rt/share/html/Elements/MyReminders | 1 - rt/share/html/Elements/MyRequests | 49 ---- rt/share/html/Elements/MyTickets | 49 ---- rt/share/html/Elements/PageLayout | 11 + rt/share/html/Elements/QueryString | 3 +- rt/share/html/Elements/QueueSummaryByLifecycle | 13 +- rt/share/html/Elements/QueueSummaryByStatus | 4 +- rt/share/html/Elements/QuickCreate | 2 +- rt/share/html/Elements/RT__Article/ColumnMap | 13 +- rt/share/html/Elements/RT__Class/ColumnMap | 13 +- rt/share/html/Elements/RT__CustomField/ColumnMap | 39 ++- rt/share/html/Elements/RT__Dashboard/ColumnMap | 9 +- rt/share/html/Elements/RT__Group/ColumnMap | 21 +- rt/share/html/Elements/RT__Queue/ColumnMap | 21 +- rt/share/html/Elements/RT__SavedSearch/ColumnMap | 9 +- rt/share/html/Elements/RT__Scrip/ColumnMap | 110 ++++++-- rt/share/html/Elements/RT__Template/ColumnMap | 35 ++- rt/share/html/Elements/RT__Ticket/ColumnMap | 94 +++---- rt/share/html/Elements/RT__User/ColumnMap | 13 +- rt/share/html/Elements/Refresh | 2 +- rt/share/html/Elements/SelectBoolean | 4 +- rt/share/html/Elements/SelectCustomFieldValue | 17 +- rt/share/html/Elements/SelectDate | 18 +- rt/share/html/Elements/SelectDateRelation | 2 +- rt/share/html/Elements/SelectLang | 2 - rt/share/html/Elements/SelectLinkType | 57 ---- rt/share/html/Elements/SelectMatch | 10 +- rt/share/html/Elements/SelectObject | 141 ++++++++++ rt/share/html/Elements/SelectOwnerAutocomplete | 2 +- rt/share/html/Elements/SelectOwnerDropdown | 32 ++- rt/share/html/Elements/SelectQueue | 92 +------ rt/share/html/Elements/SelectSortOrder | 65 ----- rt/share/html/Elements/SelectStatus | 58 ++-- rt/share/html/Elements/SelectTicketSortBy | 62 ----- rt/share/html/Elements/SelectTicketTypes | 58 ---- rt/share/html/Elements/SelectTimeUnits | 6 +- .../html/Elements/ShowCustomFieldCustomGroupings | 78 ++++++ rt/share/html/Elements/ShowCustomFieldImage | 2 +- rt/share/html/Elements/ShowCustomFields | 20 +- rt/share/html/Elements/ShowHistory | 191 ++++++++++++++ rt/share/html/Elements/ShowLink | 25 +- rt/share/html/Elements/ShowLinks | 155 +++-------- rt/share/html/Elements/ShowLinksOfType | 127 +++++++++ rt/share/html/Elements/ShowMemberships | 4 +- rt/share/html/Elements/ShowMessageHeaders | 101 +++++++ rt/share/html/Elements/ShowMessageStanza | 176 +++++++++++++ rt/share/html/Elements/ShowPrincipal | 72 +++++ rt/share/html/Elements/ShowRecord | 100 +++++++ rt/share/html/Elements/ShowRelationLabel | 30 ++- rt/share/html/Elements/ShowReminders | 8 +- rt/share/html/Elements/ShowSearch | 21 +- rt/share/html/Elements/ShowTransaction | 265 +++++++++++++++++++ rt/share/html/Elements/ShowTransactionAttachments | 293 +++++++++++++++++++++ rt/share/html/Elements/ShowUser | 53 ++-- rt/share/html/Elements/ShowUserConcise | 67 ----- rt/share/html/Elements/ShowUserVerbose | 72 ----- rt/share/html/Elements/SimpleSearch | 5 +- rt/share/html/Elements/Submit | 4 +- rt/share/html/Elements/TSVExport | 131 +++++++++ rt/share/html/Elements/Tabs | 270 +++++++++++++------ rt/share/html/Elements/TicketList | 12 +- rt/share/html/Elements/TitleBox | 1 + rt/share/html/Elements/TitleBoxEnd | 1 + rt/share/html/Elements/TitleBoxStart | 1 + rt/share/html/Elements/ValidateCustomFields | 73 ++--- 117 files changed, 4286 insertions(+), 1929 deletions(-) create mode 100644 rt/share/html/Elements/AddLinks delete mode 100755 rt/share/html/Elements/BevelBoxRaisedEnd delete mode 100755 rt/share/html/Elements/BevelBoxRaisedStart create mode 100644 rt/share/html/Elements/BulkCustomFields create mode 100644 rt/share/html/Elements/BulkLinks create mode 100644 rt/share/html/Elements/Crypt/KeyIssues create mode 100644 rt/share/html/Elements/Crypt/SelectKeyForEncryption create mode 100644 rt/share/html/Elements/Crypt/SelectKeyForSigning create mode 100644 rt/share/html/Elements/Crypt/SignEncryptWidget create mode 100644 rt/share/html/Elements/CryptStatus create mode 100644 rt/share/html/Elements/EditCustomFieldCustomGroupings create mode 100644 rt/share/html/Elements/EditCustomFields create mode 100644 rt/share/html/Elements/FindUser create mode 100644 rt/share/html/Elements/FoldStanzaJS delete mode 100644 rt/share/html/Elements/GnuPG/KeyIssues delete mode 100644 rt/share/html/Elements/GnuPG/SelectKeyForEncryption delete mode 100644 rt/share/html/Elements/GnuPG/SelectKeyForSigning delete mode 100644 rt/share/html/Elements/GnuPG/SignEncryptWidget create mode 100644 rt/share/html/Elements/GotoUser create mode 100644 rt/share/html/Elements/JavascriptConfig create mode 100644 rt/share/html/Elements/LoginHelp delete mode 100755 rt/share/html/Elements/MyRequests delete mode 100755 rt/share/html/Elements/MyTickets delete mode 100755 rt/share/html/Elements/SelectLinkType create mode 100644 rt/share/html/Elements/SelectObject delete mode 100755 rt/share/html/Elements/SelectSortOrder delete mode 100755 rt/share/html/Elements/SelectTicketSortBy delete mode 100755 rt/share/html/Elements/SelectTicketTypes create mode 100644 rt/share/html/Elements/ShowCustomFieldCustomGroupings create mode 100644 rt/share/html/Elements/ShowHistory create mode 100644 rt/share/html/Elements/ShowLinksOfType create mode 100644 rt/share/html/Elements/ShowMessageHeaders create mode 100644 rt/share/html/Elements/ShowMessageStanza create mode 100644 rt/share/html/Elements/ShowPrincipal create mode 100644 rt/share/html/Elements/ShowRecord create mode 100644 rt/share/html/Elements/ShowTransaction create mode 100644 rt/share/html/Elements/ShowTransactionAttachments delete mode 100644 rt/share/html/Elements/ShowUserConcise delete mode 100644 rt/share/html/Elements/ShowUserVerbose create mode 100644 rt/share/html/Elements/TSVExport (limited to 'rt/share/html/Elements') 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 +%# +%# +%# (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 + +<%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; + +% if (ref($Object) eq 'RT::Ticket') { +<&|/l&>Enter tickets or URIs to link tickets to. Separate multiple entries with spaces. +
<&|/l&>You may enter links to Articles as "a:###", where ### represents the number of the Article. +% $m->callback( CallbackName => 'ExtraLinkInstructions' ); +

+% } elsif (ref($Object) eq 'RT::Queue') { +<&|/l&>Enter queues or URIs to link queues to. Separate multiple entries with spaces. +
+% } else { +<&|/l&>Enter objects or URIs to link objects to. Separate multiple entries with spaces.
+% } + + + + + + + + + + + + + + + + + + + + + + + + + + <& /Elements/EditCustomFields, + Object => $Object, + Grouping => 'Links', + InTable => 1, + ($CustomFields + ? (CustomFields => $CustomFields) + : ()), + &> +% $m->callback( CallbackName => 'NewLink' ); +
<& ShowRelationLabel, Object => $Object, Label => loc('Depends on').':', Relation => 'DependsOn' &>" <% $exclude |n%>/>
<& ShowRelationLabel, Object => $Object, Label => loc('Depended on by').':', Relation => 'DependedOnBy' &>" <% $exclude |n%>/>
<& ShowRelationLabel, Object => $Object, Label => loc('Parents').':', Relation => 'Parents' &>" <% $exclude |n%>/>
<& ShowRelationLabel, Object => $Object, Label => loc('Children').':', Relation => 'Children' &> " <% $exclude |n%>/>
<& ShowRelationLabel, Object => $Object, Label => loc('Refers to').':', Relation => 'RefersTo' &>" <% $exclude |n%>/>
<& ShowRelationLabel, Object => $Object, Label => loc('Referred to by').':', Relation => 'ReferredToBy' &> " <% $exclude |n%>/>
diff --git a/rt/share/html/Elements/BevelBoxRaisedEnd b/rt/share/html/Elements/BevelBoxRaisedEnd deleted file mode 100755 index e51efe2fc..000000000 --- a/rt/share/html/Elements/BevelBoxRaisedEnd +++ /dev/null @@ -1,50 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC -%# -%# -%# (Except where explicitly superseded by other copyright notices) -%# -%# -%# LICENSE: -%# -%# This work is made available to you under the terms of Version 2 of -%# the GNU General Public License. A copy of that license should have -%# been provided with this software, but in any event can be snarfed -%# from www.gnu.org. -%# -%# This work is distributed in the hope that it will be useful, but -%# WITHOUT ANY WARRANTY; without even the implied warranty of -%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%# General Public License for more details. -%# -%# You should have received a copy of the GNU General Public License -%# along with this program; if not, write to the Free Software -%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -%# 02110-1301 or visit their web page on the internet at -%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -%# -%# -%# CONTRIBUTION SUBMISSION POLICY: -%# -%# (The following paragraph is not intended to limit the rights granted -%# to you to modify and distribute this software under the terms of -%# the GNU General Public License and is only of importance to you if -%# you choose to contribute your changes and enhancements to the -%# community by submitting them to Best Practical Solutions, LLC.) -%# -%# By intentionally submitting any modifications, corrections or -%# derivatives to this work, or any other work intended for use with -%# Request Tracker, to Best Practical Solutions, LLC, you confirm that -%# you are the copyright holder for those contributions and you grant -%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -%# royalty-free, perpetual, license to use, copy, create derivative -%# works based on those contributions, and sublicense and distribute -%# those contributions and any derivatives thereof. -%# -%# END BPS TAGGED BLOCK }}} - - - diff --git a/rt/share/html/Elements/BevelBoxRaisedStart b/rt/share/html/Elements/BevelBoxRaisedStart deleted file mode 100755 index 9c9b41082..000000000 --- a/rt/share/html/Elements/BevelBoxRaisedStart +++ /dev/null @@ -1,50 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC -%# -%# -%# (Except where explicitly superseded by other copyright notices) -%# -%# -%# LICENSE: -%# -%# This work is made available to you under the terms of Version 2 of -%# the GNU General Public License. A copy of that license should have -%# been provided with this software, but in any event can be snarfed -%# from www.gnu.org. -%# -%# This work is distributed in the hope that it will be useful, but -%# WITHOUT ANY WARRANTY; without even the implied warranty of -%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%# General Public License for more details. -%# -%# You should have received a copy of the GNU General Public License -%# along with this program; if not, write to the Free Software -%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -%# 02110-1301 or visit their web page on the internet at -%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -%# -%# -%# CONTRIBUTION SUBMISSION POLICY: -%# -%# (The following paragraph is not intended to limit the rights granted -%# to you to modify and distribute this software under the terms of -%# the GNU General Public License and is only of importance to you if -%# you choose to contribute your changes and enhancements to the -%# community by submitting them to Best Practical Solutions, LLC.) -%# -%# By intentionally submitting any modifications, corrections or -%# derivatives to this work, or any other work intended for use with -%# Request Tracker, to Best Practical Solutions, LLC, you confirm that -%# you are the copyright holder for those contributions and you grant -%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -%# royalty-free, perpetual, license to use, copy, create derivative -%# works based on those contributions, and sublicense and distribute -%# those contributions and any derivatives thereof. -%# -%# END BPS TAGGED BLOCK }}} - - - '); } 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/
/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('
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 +%# +%# +%# (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 }}} + + + + + + + +% my $i = 0; +% while (my $cf = $CustomFields->Next) { + + +% 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') { + + + + + + + + + +% } +
<&|/l&>Name<&|/l&>Add values<&|/l&>Delete values
<% $cf->Name %>
+(<% $cf->FriendlyType %>)
<& /Elements/EditCustomFieldSelect, @add &><& /Elements/EditCustomFieldSelect, @del &>
+% } elsif ($cf->Type eq 'Combobox') { +
<& /Elements/EditCustomFieldCombobox, @add &><& /Elements/EditCustomFieldCombobox, @del &>
+% } elsif ($cf->Type eq 'Freeform') { +
<& /Elements/EditCustomFieldFreeform, @add &><& /Elements/EditCustomFieldFreeform, @del &>
+% } elsif ($cf->Type eq 'Text') { +
<& /Elements/EditCustomFieldText, @add &> +% } elsif ($cf->Type eq 'Wikitext') { +<& /Elements/EditCustomFieldWikitext, @add &> +% } elsif ($cf->Type eq 'Date') { +<& /Elements/EditCustomFieldDate, @add, Default => undef &><& /Elements/EditCustomFieldDate, @del, Default => undef &>
+% } elsif ($cf->Type eq 'DateTime') { +% # Pass datemanip format to prevent another tz date conversion +
<& /Elements/EditCustomFieldDateTime, @add, Default => undef, Format => 'datemanip' &><& /Elements/EditCustomFieldDateTime, @del, Default => undef, Format => 'datemanip' &>
+% } else { +% $RT::Logger->crit("Unknown CustomField type: " . $cf->Type); +% next +% } + +
+<%ARGS> +$CustomFields + +<%INIT> +return unless $CustomFields->Count; + 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 +%# +%# +%# (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 }}} + + + + + +
+

<&|/l&>Current Links

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
<&|/l&>Depends on: +% if ( $hash{DependsOn} ) { +% for my $link ( values %{$hash{DependsOn}} ) { + +
+% } } +
<&|/l&>Depended on by: +% if ( $hash{DependedOnBy} ) { +% for my $link ( values %{$hash{DependedOnBy}} ) { + +
+% } } +
<&|/l&>Parents: +% if ( $hash{MemberOf} ) { +% for my $link ( values %{$hash{MemberOf}} ) { + +
+% } } +
<&|/l&>Children: +% if ( $hash{Members} ) { +% for my $link ( values %{$hash{Members}} ) { + +
+% } } +
<&|/l&>Refers to: +% if ( $hash{RefersTo} ) { +% for my $link ( values %{$hash{RefersTo}} ) { + +
+% } } +
<&|/l&>Referred to by: +% if ( $hash{ReferredToBy} ) { +% for my $link ( values %{$hash{ReferredToBy}} ) { +% # Skip reminders +% next if (UNIVERSAL::isa($link->BaseObj, 'RT::Ticket') && $link->BaseObj->Type eq 'reminder'); + +
+% } } +
<&|/l&>(Check box to delete)
+
+

<&|/l&>New Links

+<&|/l&>Enter tickets or URIs to link to. Separate multiple entries with spaces. +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
<&|/l&>Depends on:
<&|/l&>Depended on by:
<&|/l&>Parents:
<&|/l&>Children:
<&|/l&>Refers to:
<&|/l&>Referred to by:
+
+ +<%ARGS> +$Collection + + +<%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{$_}; + } + } + } +} + 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 ); 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('
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) .'' + . '">'. $loc_title .'' ); } else { - $m->out( loc($title) ); + $m->out( $loc_title ); } $m->out('
'); + ($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{}); + $m->out(qq{}); if ( $number == $CurrentPage ) { $m->out(qq{$number }); } @@ -96,7 +96,7 @@ for my $number ( 1 .. $Pages ) { $dots = 1; $m->out(qq{...}); } - $m->out(qq{}); + $m->out(qq{}); } 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 <%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, \"
  • ", $value, \"
  • \n"; } - push @values, \'
    '; # this is deeply silly - } while ($v = $values->Next); - pop @values; # Remove that last
    + @values = (\"
      ", @values, \"
    "); + } return @values; }, }, @@ -146,9 +150,9 @@ my $COLUMN_MAP = { my $checked = $DECODED_ARGS->{ $name .'All' }? 'checked="checked"': ''; return \qq{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 = {}; + <%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 ); diff --git a/rt/share/html/Elements/Crypt/KeyIssues b/rt/share/html/Elements/Crypt/KeyIssues new file mode 100644 index 000000000..35c12641e --- /dev/null +++ b/rt/share/html/Elements/Crypt/KeyIssues @@ -0,0 +1,94 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +% if ( @$Issues || @$SignAddresses ) { +
    +<&| /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:") %> +
      +% for my $address (@$SignAddresses) { +
    • <% $address %>
    • +% } +
    +% } + +% if (@$Issues == 1) { +<% 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/certificates. You have to fix the problems with the keys/certificates, disable sending a message to the recipients with problems, or disable encryption.") %> +% } + +
      +% foreach my $issue ( @$Issues ) { +
    • +% if ( $issue->{'User'} ) { +User <&/Elements/ShowUser, User => $issue->{'User'} &> has a problem. +% } else { +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'} %> +
      +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 ), +&> +
    • +% } +
    + +
    +% } + +<%ARGS> +$Issues => [] +$SignAddresses => [] + + diff --git a/rt/share/html/Elements/Crypt/SelectKeyForEncryption b/rt/share/html/Elements/Crypt/SelectKeyForEncryption new file mode 100644 index 000000000..2bf79bc4d --- /dev/null +++ b/rt/share/html/Elements/Crypt/SelectKeyForEncryption @@ -0,0 +1,80 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +% if (!@keys) { +<% loc("No usable keys.") %> +% } else { + +% } + +<%INIT> +my $d; + +my %res = RT::Crypt->GetKeysForEncryption($EmailAddress); +# move the preferred key to the top of the list +my @keys = map { + $_->{'Fingerprint'} eq ( $Default || '' ) + ? do { $d = $_; () } + : $_ + } + @{ $res{'info'} }; + +@keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys; + +unshift @keys, $d if defined $d; + + +<%ARGS> +$Name => 'PreferredKey' +$EmailAddress => undef +$Default => undef + + diff --git a/rt/share/html/Elements/Crypt/SelectKeyForSigning b/rt/share/html/Elements/Crypt/SelectKeyForSigning new file mode 100644 index 000000000..bbd9bda84 --- /dev/null +++ b/rt/share/html/Elements/Crypt/SelectKeyForSigning @@ -0,0 +1,67 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +% if ( $user_key ) { + +% } else { +<% loc("Queue's key") %> +% } +<%ARGS> +$Name => 'SignUsing', +$User => undef, + +<%INIT> +return unless RT->Config->Get('Crypt')->{'Enable'}; + +# 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'; + diff --git a/rt/share/html/Elements/Crypt/SignEncryptWidget b/rt/share/html/Elements/Crypt/SignEncryptWidget new file mode 100644 index 000000000..33136f968 --- /dev/null +++ b/rt/share/html/Elements/Crypt/SignEncryptWidget @@ -0,0 +1,188 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +
    +% my $columnsplit = " +% } else { + +% } + + + +
    "; +% if ( RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG' ) { +<% loc( 'Sign[_1][_2] using [_3]', + $columnsplit, + $m->scomp('/Widgets/Form/Boolean:InputOnly', + Name => 'Sign', CurrentValue => $self->{'Sign'} + ), + $m->scomp('SelectKeyForSigning', User => $session{'CurrentUser'}->UserObj ), +) |n %><% loc( 'Sign[_1][_2]', + $columnsplit, + $m->scomp('/Widgets/Form/Boolean:InputOnly', + Name => 'Sign', CurrentValue => $self->{'Sign'} + ), +) |n %><% loc('Encrypt')%><& /Widgets/Form/Boolean:InputOnly, Name => 'Encrypt', CurrentValue => $self->{'Encrypt'} &>
    +<%ARGS> +$self => undef, + +<%INIT> +return unless $self; + + +<%METHOD new> +<%ARGS> +$Arguments => {} + +<%INIT> +return undef unless RT->Config->Get('Crypt')->{'Enable'}; +return { %$Arguments }; + + + +<%METHOD ShowIssues> +<%ARGS> +$self => undef, + +<%INIT> +return unless $self; + +return $m->comp( '/Elements/Crypt/KeyIssues', + Issues => $self->{'GnuPGRecipientsKeyIssues'} || [], + SignAddresses => $self->{'GnuPGCanNotSignAs'} || [], +); + + + + +<%METHOD Process> +<%ARGS> +$self => undef +$QueueObj => undef +$TicketObj => undef + +<%INIT> +return unless $self; + +$QueueObj ||= $TicketObj->QueueObj if $TicketObj; + +foreach ( qw(Sign Encrypt) ) { + $self->{ $_ } = $m->comp( '/Widgets/Form/Boolean:Process', + Name => $_, + DefaultValue => $QueueObj->$_, + Arguments => $self, + ); +} + + + +<%METHOD Check> +<%ARGS> +$self => undef +$Operation => 'Update' +$TicketObj => undef +$QueueObj => undef + +<%INIT> +return 1 unless $self; + +my $checks_failure = 0; + +if ( $self->{'Sign'} ) { + $QueueObj ||= $TicketObj->QueueObj + if $TicketObj; + + my $private = $session{'CurrentUser'}->UserObj->PrivateKey || ''; + my $queue = ($self->{'UpdateType'} && $self->{'UpdateType'} eq "private") + ? ( $QueueObj->CommentAddress || RT->Config->Get('CommentAddress') ) + : ( $QueueObj->CorrespondAddress || RT->Config->Get('CorrespondAddress') ); + + my $address = $self->{'SignUsing'} || $queue; + if ($address ne $private and $address ne $queue) { + push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address; + $checks_failure = 1; + } elsif ( not RT::Crypt->DrySign( Signer => $address ) ) { + push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address; + $checks_failure = 1; + } else { + RT::Crypt->UseKeyForSigning( $self->{'SignUsing'} ) + if $self->{'SignUsing'}; + } +} + +if ( $self->{'Encrypt'} ) { + + my @recipients; + + if ( $Operation eq 'Update' ) { + @recipients = $TicketObj->DryRun(%$self)->Recipients; + } + elsif ( $Operation eq 'Create' ) { + $TicketObj = RT::Ticket->new( $session{'CurrentUser'} ); + @recipients = $TicketObj->DryRunCreate(%$self)->Recipients; + } + else { + $RT::Logger->crit('Incorrect operation: '. $Operation ); + } + + my %seen; + @recipients = grep !$seen{ lc $_ }++, @recipients; + + RT::Crypt->UseKeyForEncryption( + map { (/^UseKey-(.*)$/)[0] => $self->{ $_ } } + grep $self->{ $_ } && /^UseKey-/, + keys %$self + ); + + my ($status, @issues) = RT::Crypt->CheckRecipients( @recipients ); + push @{ $self->{'GnuPGRecipientsKeyIssues'} ||= [] }, @issues; + $checks_failure = 1 unless $status; +} + +return $checks_failure ? 0 : 1; + + 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 +%# +%# +%# (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 + +<%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; + 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".''."\n"); +$m->out("\n".''."\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) ) : (), ); <%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 || '' %> +name="<% $name %>" id="<% $name %>" class="CF-<%$CustomField->id%>-Edit"><% $Default || '' %> <%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? -<% $value->Content %>
    +<% $value->Content %>
    % } -% if (!$MaxValues || !$Values || $Values->Count < $MaxValues) { - +% if ($MaxValues && $Values && $Values->Count >= $MaxValues ) { +
    +<&|/l&>Reached maximum number, so new values will override old ones. +
    % } + + +<%INIT> +my $name = $Name || $NamePrefix . $CustomField->Id . '-Upload'; +my $delete_name = $name; +$delete_name =~ s!-Upload$!-DeleteValueIds!; + + <%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) { -<% $value->Content %> + +
    % } % (!$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!; + + <%ARGS> $Object => undef $CustomField => undef @@ -66,4 +74,5 @@ $Values => undef $Multiple => 0 $Rows => undef $MaxValues => undef +$Name => undef diff --git a/rt/share/html/Elements/EditCustomFieldCustomGroupings b/rt/share/html/Elements/EditCustomFieldCustomGroupings new file mode 100644 index 000000000..0287b189f --- /dev/null +++ b/rt/share/html/Elements/EditCustomFieldCustomGroupings @@ -0,0 +1,73 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +% 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 &> + +% } +<%ARGS> +$Object +$CustomFieldGenerator => undef, +@Groupings => (RT::CustomField->CustomGroupings( $Object ), '') + +<%INIT> +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} || {}; + 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 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 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) { +name="<%$name%>" id="<%$name%>" wrap="off" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %> % } else { 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 ) { -<& ShowCustomFieldImage, Object => $value &> +<& ShowCustomFieldImage, Object => $value &>
    % } -% if (!$MaxValues or !$Values or $Values->Count < $MaxValues) { - +% if ($MaxValues && $Values && $Values->Count >= $MaxValues ) { +
    +<&|/l&>Reached maximum number, so new values will override old ones. +
    % } + + +<%INIT> +my $name = $Name || $NamePrefix . $CustomField->Id . '-Upload'; +my $delete_name = $name; +$delete_name =~ s!-Upload$!-DeleteValueIds!; + + <%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) { - %# XXX - Hide this select from w3m? - % foreach my $cat (@category) { % my ($depth, $name) = @$cat; @@ -63,10 +61,10 @@ % }
    % } elsif ($CustomField->BasedOnObj->id) { - + + % } -% if ( $RichText and RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'}) ) { - -% } -<%ARGS> -$RichText => 1 - <%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"; } diff --git a/rt/share/html/Elements/JavascriptConfig b/rt/share/html/Elements/JavascriptConfig new file mode 100644 index 000000000..9437567f0 --- /dev/null +++ b/rt/share/html/Elements/JavascriptConfig @@ -0,0 +1,84 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<%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 $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, +); + + 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) {
    <&| /Widgets/TitleBox, title => loc('Results'), %{$titlebox || {}} &>
      -% foreach my $action (@actions) { +% foreach my $action (grep {not ref $_} @actions) {
    • <%$action%>
    • % }
    +% } <%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
      % for my $child ($menu->children) { @@ -55,6 +56,9 @@ $menu <% $description %>\ % } +% if ($show_children && $child->children) { +<& /Elements/ListMenu, menu => $child &> +% } % }
    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 &> diff --git a/rt/share/html/Elements/LoginHelp b/rt/share/html/Elements/LoginHelp new file mode 100644 index 000000000..fbb4c0cc0 --- /dev/null +++ b/rt/share/html/Elements/LoginHelp @@ -0,0 +1,54 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} + +<%init> +my $source = RT->Config->Meta('OwnerEmail')->{Source}; +return unless $source->{SiteConfig} or $source->{Extension}; + 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 { <%loc($ARGS{'LogoAltText'}||RT->Config->Get('LogoAltText'))%> + alt="<%loc($ARGS{'LogoAltText'}||RT->Config->Get('LogoAltText'))%>" /> % } % if ( $ShowName ) { <% $Name || loc("RT for [_1]", RT->Config->Get('rtname')) %> @@ -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{[}. loc('Open URL') .qq{]}; - return $args{value} . qq{ $result}; + my $result = qq{[}. loc('Open URL') .qq{]}; + return $args{value} . qq{ $result$post}; }, url_overwrite => sub { my %args = @_; + my $post = ""; + $post = ")" if $args{value} !~ /\(/ and $args{value} =~ s/\)$//; $args{value} = $escaper->($args{value}); - my $result = qq{$args{value}}; - return qq{$result}; + my $result = qq{$args{value}}; + return qq{$result$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%><% $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)%>
  • \ -% 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'); -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 %>\ -\ -% } -% if ($child->has_children) { - -<& Menu, menu => $child, toplevel => 0, parent_id => ($parent_id? $parent_id."-": '').$child->key, depth=> ($depth+1) &> -<%" " x ($depth+1)%>
  • -% } else { - -% } -% } -<%" " x $depth%>\ <%ARGS> $menu $id => undef @@ -82,6 +52,6 @@ $toplevel => 1 $parent_id => '' $depth => 0 -<%init> -$id = $m->interp->apply_escapes($id, 'h'); - +<%INIT> +RenderMenu( %ARGS ); + 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 }}} - % $m->callback( %ARGS, CallbackName => 'AfterTextArea' ); -% if (RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'})) { -" /> +% if ($Type eq 'text/html') { +" /> % } <%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{\n}{
    }g; + $signature = "

    $signature

    "; + } } # 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'; 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; -% if ( $summary ) { +% if ( $sidebar ) { -% $show_cb->($_) foreach @$summary; +% $show_cb->($_) foreach @$sidebar; % } @@ -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 -%# -%# -%# (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/MyTickets b/rt/share/html/Elements/MyTickets deleted file mode 100755 index 583d17d19..000000000 --- a/rt/share/html/Elements/MyTickets +++ /dev/null @@ -1,49 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC -%# -%# -%# (Except where explicitly superseded by other copyright notices) -%# -%# -%# LICENSE: -%# -%# This work is made available to you under the terms of Version 2 of -%# the GNU General Public License. A copy of that license should have -%# been provided with this software, but in any event can be snarfed -%# from www.gnu.org. -%# -%# This work is distributed in the hope that it will be useful, but -%# WITHOUT ANY WARRANTY; without even the implied warranty of -%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%# General Public License for more details. -%# -%# You should have received a copy of the GNU General Public License -%# along with this program; if not, write to the Free Software -%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -%# 02110-1301 or visit their web page on the internet at -%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -%# -%# -%# CONTRIBUTION SUBMISSION POLICY: -%# -%# (The following paragraph is not intended to limit the rights granted -%# to you to modify and distribute this software under the terms of -%# the GNU General Public License and is only of importance to you if -%# you choose to contribute your changes and enhancements to the -%# community by submitting them to Best Practical Solutions, LLC.) -%# -%# By intentionally submitting any modifications, corrections or -%# derivatives to this work, or any other work intended for use with -%# Request Tracker, to Best Practical Solutions, LLC, you confirm that -%# you are the copyright holder for those contributions and you grant -%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -%# royalty-free, perpetual, license to use, copy, create derivative -%# works based on those contributions, and sublicense and distribute -%# those contributions and any derivatives thereof. -%# -%# END BPS TAGGED BLOCK }}} -%# DEPRECATED -<& /Elements/ShowSearch, Name => 'My Tickets' &> 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
    <& /Elements/WidgetBar, menu => PageWidgets() &>
    % } + +% if ($m->comp_exists($stylesheet_plugin) ) { +<& $stylesheet_plugin &> +% } +
    % $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 +<%init> +my $style = $session{'CurrentUser'} + ? $session{'CurrentUser'}->Stylesheet + : RT->Config->Get('WebDefaultStylesheet'); +my $stylesheet_plugin = "/NoAuth/css/".$style."/AfterMenus"; + 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; 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 @@ <&|/l&>Requestors: - <& /Elements/EmailInput, Name => 'Requestors', Size => '40', Default => $args->{Requestors} || $session{CurrentUser}->EmailAddress &> + <& /Elements/EmailInput, Name => 'Requestors', Size => '40', Default => $args->{Requestors} || $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1 &> <&|/l&>Content: 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 => {} @@ -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 = { <%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 ); 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 => {} <%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') }, + }, }; <%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 ); 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 => {} <%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{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'}; + <%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 ); 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 => {} <%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 = { <%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 ); 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 => {} <%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') }, }, }; <%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 ); 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 => {} <%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( <%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 ); 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 => {} <%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 = { <%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 ); 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 => {} <%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{}; + }, + 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{} + }, + }, + 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 = ( + \' $id ), + \'">', loc('[Up]'), \'', + \' $id ), + \'">', loc('[Down]'), \'' + ); + + 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 ); + }, }, }; <%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 ); 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 => {} <%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, \'', $scrip->id, \''; + } + return @res; + }, + }, }; <%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 ); 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 => {} @@ -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 { \'', - ( $_->$mode_uri->IsLocal && $_->$local_type ? $_->$local_type : $_->$mode_uri->Resolver->AsString ), + ( $_->$mode_uri->AsString ), \'
    ', - } @{ $_[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 \'', loc('(pending [quant,_1,other ticket])',$count), \''; + return \'', loc('(pending [quant,_1,other ticket,other tickets])',$count), \''; } } 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 (\'' , $date->AgeAsString , \''); } 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}; -} <%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 ); 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 => {} <%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') }, + }, }; <%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 ); 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. +><&|/l, $value/60 &>Refresh this page every [quant,_1,minute,minutes]. %} 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"'; } 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; -% } -% elsif ( $CustomField->Type eq 'Autocomplete' ) { - +% } elsif ($CustomField->Type =~ /^Date(Time)?$/) { +<& /Elements/SelectDate, ShowTime => ($1 ? 1 : 0), Name => $Name, Value => $Default &> +% } elsif ( $CustomField->Type eq 'Autocomplete' ) { + % } else { - + % } <%args> $Name => undef -$CustomField =>undef +$CustomField => undef +$Default => undef 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' ); - + % $m->callback( %ARGS, Name => $Name, CallbackName => 'AfterDateInput' ); <%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"; } <%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') 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 @@ <%ARGS> $ShowNullOption => 1 -$ShowAllQueues => 1 $Name => undef $Verbose => undef $Default => 0 -$Lite => 0 <%ONCE> diff --git a/rt/share/html/Elements/SelectLinkType b/rt/share/html/Elements/SelectLinkType deleted file mode 100755 index 5d70f04e4..000000000 --- a/rt/share/html/Elements/SelectLinkType +++ /dev/null @@ -1,57 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC -%# -%# -%# (Except where explicitly superseded by other copyright notices) -%# -%# -%# LICENSE: -%# -%# This work is made available to you under the terms of Version 2 of -%# the GNU General Public License. A copy of that license should have -%# been provided with this software, but in any event can be snarfed -%# from www.gnu.org. -%# -%# This work is distributed in the hope that it will be useful, but -%# WITHOUT ANY WARRANTY; without even the implied warranty of -%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%# General Public License for more details. -%# -%# You should have received a copy of the GNU General Public License -%# along with this program; if not, write to the Free Software -%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -%# 02110-1301 or visit their web page on the internet at -%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -%# -%# -%# CONTRIBUTION SUBMISSION POLICY: -%# -%# (The following paragraph is not intended to limit the rights granted -%# to you to modify and distribute this software under the terms of -%# the GNU General Public License and is only of importance to you if -%# you choose to contribute your changes and enhancements to the -%# community by submitting them to Best Practical Solutions, LLC.) -%# -%# By intentionally submitting any modifications, corrections or -%# derivatives to this work, or any other work intended for use with -%# Request Tracker, to Best Practical Solutions, LLC, you confirm that -%# you are the copyright holder for those contributions and you grant -%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -%# royalty-free, perpetual, license to use, copy, create derivative -%# works based on those contributions, and sublicense and distribute -%# those contributions and any derivatives thereof. -%# -%# END BPS TAGGED BLOCK }}} - - -<%ARGS> -$Name => "LinkType" -$Default => '' - 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"]; } 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 +%# +%# +%# (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); + +% } +% else { + +% } +<%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 + +<%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(); +} + 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) { %} -% $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]; +><% $UserRef->[1] %> %} <%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; <%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); - -% } -% else { - -% } +<& 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' -<%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(); -} - diff --git a/rt/share/html/Elements/SelectSortOrder b/rt/share/html/Elements/SelectSortOrder deleted file mode 100755 index 0075df61d..000000000 --- a/rt/share/html/Elements/SelectSortOrder +++ /dev/null @@ -1,65 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC -%# -%# -%# (Except where explicitly superseded by other copyright notices) -%# -%# -%# LICENSE: -%# -%# This work is made available to you under the terms of Version 2 of -%# the GNU General Public License. A copy of that license should have -%# been provided with this software, but in any event can be snarfed -%# from www.gnu.org. -%# -%# This work is distributed in the hope that it will be useful, but -%# WITHOUT ANY WARRANTY; without even the implied warranty of -%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%# General Public License for more details. -%# -%# You should have received a copy of the GNU General Public License -%# along with this program; if not, write to the Free Software -%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -%# 02110-1301 or visit their web page on the internet at -%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -%# -%# -%# CONTRIBUTION SUBMISSION POLICY: -%# -%# (The following paragraph is not intended to limit the rights granted -%# to you to modify and distribute this software under the terms of -%# the GNU General Public License and is only of importance to you if -%# you choose to contribute your changes and enhancements to the -%# community by submitting them to Best Practical Solutions, LLC.) -%# -%# By intentionally submitting any modifications, corrections or -%# derivatives to this work, or any other work intended for use with -%# Request Tracker, to Best Practical Solutions, LLC, you confirm that -%# you are the copyright holder for those contributions and you grant -%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -%# royalty-free, perpetual, license to use, copy, create derivative -%# works based on those contributions, and sublicense and distribute -%# those contributions and any derivatives thereof. -%# -%# END BPS TAGGED BLOCK }}} - - -<%INIT> -my @orders = qw (ASC DESC); -my @order_names = (loc('Ascending'), loc('Descending')); - - - -<%ARGS> -$Name => 'SortOrder' -$Default => 'ASC' - 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; <%ARGS> $Name => undef +$Type => undef, @Statuses => () -$TicketObj => undef -$QueueObj => undef -%Queues => () +$Object => undef, +@Lifecycles => (), $Default => '' $SkipDeleted => 0 diff --git a/rt/share/html/Elements/SelectTicketSortBy b/rt/share/html/Elements/SelectTicketSortBy deleted file mode 100755 index d06692a80..000000000 --- a/rt/share/html/Elements/SelectTicketSortBy +++ /dev/null @@ -1,62 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC -%# -%# -%# (Except where explicitly superseded by other copyright notices) -%# -%# -%# LICENSE: -%# -%# This work is made available to you under the terms of Version 2 of -%# the GNU General Public License. A copy of that license should have -%# been provided with this software, but in any event can be snarfed -%# from www.gnu.org. -%# -%# This work is distributed in the hope that it will be useful, but -%# WITHOUT ANY WARRANTY; without even the implied warranty of -%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%# General Public License for more details. -%# -%# You should have received a copy of the GNU General Public License -%# along with this program; if not, write to the Free Software -%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -%# 02110-1301 or visit their web page on the internet at -%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -%# -%# -%# CONTRIBUTION SUBMISSION POLICY: -%# -%# (The following paragraph is not intended to limit the rights granted -%# to you to modify and distribute this software under the terms of -%# the GNU General Public License and is only of importance to you if -%# you choose to contribute your changes and enhancements to the -%# community by submitting them to Best Practical Solutions, LLC.) -%# -%# By intentionally submitting any modifications, corrections or -%# derivatives to this work, or any other work intended for use with -%# Request Tracker, to Best Practical Solutions, LLC, you confirm that -%# you are the copyright holder for those contributions and you grant -%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -%# royalty-free, perpetual, license to use, copy, create derivative -%# works based on those contributions, and sublicense and distribute -%# those contributions and any derivatives thereof. -%# -%# END BPS TAGGED BLOCK }}} - - -<%INIT> -my $tickets = RT::Tickets->new($session{'CurrentUser'}); -my @sortfields = $tickets->SortFields(); - - -<%ARGS> -$Name => 'SortTicketsBy' -$Default => 'id' - 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 -%# -%# -%# (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> -$Name => 'TickType' -$Default => undef -@Types => qw(Approval Ticket) - 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 }}} <%INIT> $Name .= '-TimeUnits' unless $Name =~ /-TimeUnits$/io; -my $HoursDefault = RT->Config->Get('DefaultTimeUnitsToHours', $session{'CurrentUser'}); <%ARGS> $Name => '' +$Default => RT->Config->Get('DefaultTimeUnitsToHours', $session{'CurrentUser'}) ? 'hours' : 'minutes' diff --git a/rt/share/html/Elements/ShowCustomFieldCustomGroupings b/rt/share/html/Elements/ShowCustomFieldCustomGroupings new file mode 100644 index 000000000..dfce725b2 --- /dev/null +++ b/rt/share/html/Elements/ShowCustomFieldCustomGroupings @@ -0,0 +1,78 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<%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 ); + +<&| /Widgets/TitleBox, %grouping_args &> +<& ShowCustomFields, %ARGS, Object => $Object, Grouping => $group &> + +% } +<%ARGS> +$Object +$title_href => "" +@Groupings => () + +<%INIT> +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} || {}; + +@Groupings = (RT::CustomField->CustomGroupings( $Object ), '') unless @Groupings; + 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'); -<% $Object->Content %> +<% $Object->Content %>
    <%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) { % } @@ -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; @@ -72,19 +74,23 @@ % } +% $m->callback( CallbackName => 'AfterCustomFieldValue', CustomField => $CustomField, +% Object => $Object, Grouping => $Grouping, Table => $Table ); % } % } % if ($Table) {
    <% $CustomField->Name %>:
    % } -% $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(''); + $m->out(''); } 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 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 +%# +%# +%# (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> +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 .= '$open_html — "; + } + + if ($ShowHeaders) { + $titleright .= qq{} . + loc("Show brief headers") . + qq{}; + } else { + $titleright .= qq{} . + loc("Show full headers") . + qq{}; + } + } + +<& /Widgets/TitleBoxStart, title => $title, titleright_raw => $titleright &> +% } + +
    +<%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++; +} + + +
    +% if ($ShowDisplayModes or $ShowTitle) { +<& /Widgets/TitleBoxEnd &> +% } +
    +<%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); + +<%ARGS> +$Object +$Transactions => $Object->SortedTransactions +$Attachments => $Object->Attachments( WithHeaders => 1 ) +$AttachmentContent => $Object->TextAttachments + +$ShowHeaders => 0 +$ShowTitle => 1 +$ShowDisplayModes => 1 + +$PathPrefix => '' + 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 }}} - -% 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); - - -<%$member->Id%>: (<& /Elements/ShowUser, User => $member->OwnerObj &>) <%$member->Subject || ''%> [<% loc($member->Status) %>] + +<%$member->Id%>: <%$member->Subject || ''%> [<% loc($member->Status) %>] (<& /Elements/ShowUser, User => $member->OwnerObj &>) -% } elsif ($has_name) { -<%$URI->Resolver->AsString%>: <%$member->Name%> -% } else { -<%$URI->Resolver->AsString%> -% } % } else { -<%$URI->Resolver->AsString%> +<%$URI->AsString%> % } - <%ARGS> $URI => undef <%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 }}} +% for my $type (@display) { - - - - - - - - - - - - - - - - - - - - - % # Allow people to add more rows to the table % $m->callback( %ARGS ); + <& /Elements/ShowCustomFields, Object => $Object, Grouping => 'Links', Table => 0 &>
    <& ShowRelationLabel, id => $id, Label => loc('Depends on'), Relation => 'DependsOn' &>:\ -% if ($can_create) { - (<% loc('Create') %>) -% } - -<%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 ); - } -} - -
      -% for my $Link (@not_tickets, @active, @inactive) { -
    • <& ShowLink, URI => $Link &>
    • -% } -
    -
    <& ShowRelationLabel, id => $id, Label => loc('Depended on by'), Relation => 'DependedOnBy' &>:\ -% if ($can_create) { - (<% loc('Create') %>) -% } - -
      -% while (my $Link = $Ticket->DependedOnBy->Next) { -
    • <& ShowLink, URI => $Link->BaseURI &>
    • -% } -
    -
    <& ShowRelationLabel, id => $id, Label => loc('Parents'), Relation => 'Parents' &>:\ -% if ($can_create) { - (<% loc('Create') %>) -% } - <& /Ticket/Elements/ShowParents, Ticket => $Ticket &>
    <& ShowRelationLabel, id => $id, Label => loc('Children'), Relation => 'Children' &>:\ -% if ($can_create) { - (<% loc('Create') %>) -% } - <& /Ticket/Elements/ShowMembers, Ticket => $Ticket &>
    <& ShowRelationLabel, id => $id, Label => loc('Refers to'), Relation => 'RefersTo' &>:\ -% if ($can_create) { - (<% loc('Create') %>) -% } + + <& ShowRelationLabel, Object => $Object, Label => $labels{$type}.':', Relation => $type &> +% if ($clone{$type}) { + (<% loc('Create') %>) +% } -
      -% while (my $Link = $Ticket->RefersTo->Next) { -
    • <& ShowLink, URI => $Link->TargetURI &>
    • -% } -
    + <& ShowLinksOfType, Object => $Object, Type => $type, Recurse => ($type eq 'Members') &>
    <& ShowRelationLabel, id => $id, Label => loc('Referred to by'), Relation => 'ReferredToBy' &>:\ -% if ($can_create) { - (<% loc('Create') %>) % } - -
      -% while (my $Link = $Ticket->ReferredToBy->Next) { -% next if (UNIVERSAL::isa($Link->BaseObj, 'RT::Ticket') && $Link->BaseObj->Type eq 'reminder'); -
    • <& ShowLink, URI => $Link->BaseURI &>
    • -% } -
    -
    <%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); + } } - <%ARGS> -$Ticket => undef +$Object 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 +%# +%# +%# (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 }}} +
      +% for my $link (@not_tickets, @active, @inactive) { +
    • <& ShowLink, URI => $link->$ModeURI &> +<%perl> + next unless $Recurse; + + my $ToObj = $link->$ModeObj; + next if $ToObj and $checked->{$ToObj->id}; + + if ($depth <= $MaxDepth) { + +<& ShowLinksOfType, %ARGS, Object => $ToObj, depth => ($depth + 1), checked => $checked &> +% } +
    • +% } +
    +<%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, +); + +<%ARGS> +$Object => undef +$Type +$Recurse => 0 +$MaxDepth => 7 +$depth => 1 +$checked => {} + 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') {
  • <% $Group->Name %>
  • % } elsif ($Group->Domain eq 'SystemInternal') { -
  • <% loc($Group->Type) %>
  • +
  • <% loc($Group->Name) %>
  • % } % } @@ -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 +%# +%# +%# (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 ) { + +% foreach my $header (@headers) { + + + + +% } +
    <% $header->{'Tag'} %>: + <% $header->{'Value'} | n %>
    +% $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; +} + +<%ARGS> +$WarnUnsigned => 0 +$Message => undef +$Localize => 1 +@DisplayHeaders => ('_all') + 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 +%# +%# +%# (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)}{
    }g; + } + } else { + if ( defined $$ref ) { + $$ref =~ s/^[\r\n]+//g; + } + } + $m->out($$ref); +}; + +$m->out( '
    ' ); + +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( '
    ' ); + } + if ( length $para ) { + $print_content->( \$para ); + $para = ''; + } + + if (@stack) { + ( $Message, $i ) = @{ pop @stack }; + $Depth--; + $m->out('
    '); + goto AGAIN; + } +} else { + $print_content->( \$Message ); +} + +$m->out('
    '); + +<%ARGS> +$Message => undef +$Transaction => undef +$ContentType => 'text/plain' + diff --git a/rt/share/html/Elements/ShowPrincipal b/rt/share/html/Elements/ShowPrincipal new file mode 100644 index 000000000..81c8e62d4 --- /dev/null +++ b/rt/share/html/Elements/ShowPrincipal @@ -0,0 +1,72 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +%# Released under the terms of version 2 of the GNU Public License +<%args> +$Object +$PostUser => undef +$Separator => ", " +$Link => 1 + +<%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 }; + + # 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 { + $m->comp("/Elements/ShowUser", User => $Object, Link => $Link); + $m->out( $PostUser->($Object) ) if $PostUser; +} + 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 +%# +%# +%# (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 => "" + +<%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 ); +}; + +
    +<%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]); + } + +
    <% CSSClass($fetch_columnmap->($attr,'attribute',[$attr],'no_escape')) %>"> +<% loc($title) |n %> +<%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; + +<% join('',@out) |n %> +% $m->callback(CallbackName => 'AfterValue', Object => $Object, attribute => $attr, column => $column ); +
    +% } +
    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 }}} -<%$Label%> +% if ($SearchURL) { +<% $Label %> +% } 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, +); <%ARGS> -$id +$Object => undef $Label $Relation 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 ) { -<% $reminder->Subject %> +<% $reminder->Subject %> <% $overdue ? '' : '' |n %><% $dueobj->AgeAsString || loc('Not set') %><% $overdue ? '' : '' |n %> @@ -76,7 +76,7 @@ if ( my $ticket= $targets->First ) { #<% $ticket->Id %>: <% $ticket->Subject %> % } else { -
    Couldn't find Ticket for reminder <% $reminder->id %>. Please contact administrator.
    % } @@ -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 +%# +%# +%# (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> +$m->comp('/Elements/ShowCustomFields', Object => $Transaction, HideEmpty => 1 ) if $HasTxnCFs; +$m->comp( + 'ShowTransactionAttachments', + %ARGS, + Parent => 0 +) if $ShowBody; + +
    +% $m->callback( %ARGS, Transaction => $Transaction, CallbackName => 'AfterContent' ); +
    + +<%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 + + +<%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 = '{'target'} + ? ' target="'. $i->apply_escapes( $a->{'target'}, 'h' ) .'"' + : '' + ) + . ($a->{'class'} + ? ' class="'. $i->apply_escapes( $a->{'class'}, 'h' ) .'"' + : '' + ) + .'>'. $i->apply_escapes( $a->{'title'}, 'h' ) .'' + ; + } + $actions = join ' ', map "[$_]", @actions; +} + +# make date unbreakable +$date = $m->interp->apply_escapes( $date, 'h' ); +$date =~ s/\s/ /g; + 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 +%# +%# +%# (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 ) { + +
    +<&|/l&>Download <% length $name ? $name : loc('(untitled)') %>\ +% if ( $DownloadableHeaders && ! length $name && $message->ContentType =~ /text/ ) { + / <% loc('with headers') %> +% } +% $m->callback(CallbackName => 'AfterDownloadLinks', ARGSRef => \%ARGS, Object => $Object, Transaction => $Transaction, Attachment => $message); +
    +<% $message->ContentType %> <% $message->FriendlyContentLength %> +
    +% } +%# If there is sub-messages, open a dedicated div +% if ( $Attachments->{ $message->id } ) { +
    +% } else { +
    +% } +<%PERL> + +$render_attachment->( $message ); + +$m->comp( + $m->current_comp, + %ARGS, + Parent => $message->id, + ParentObj => $message, + + displayed_inline => $displayed_inline, +); + + +
    +% } +<%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 => {} + +<%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('

    '. loc( 'Message body is not shown because sender requested not to inline it.' ) .'

    '); + return; + } + elsif ( length $name && RT->Config->Get('SuppressInlineTextFiles', $session{'CurrentUser'} ) ) { + $m->out('

    '. loc( 'Text file is not shown because it is disabled in preferences.' ) .'

    '); + return; + } + elsif ( $max_size && $message->ContentLength > $max_size ) { + $m->out('

    '. loc( 'Message body is not shown because it is too large.' ) .'

    '); + 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('

    '. loc( 'Image not shown because display is disabled in system configuration.' ) .'

    '); + return; + } + elsif ( $displayed_inline->{$message->Id} ) { + $m->out('

    '. loc( 'Image displayed inline above' ) .'

    '); + return; + } + elsif ( $disposition ne 'inline' ) { + $m->out('

    '. loc( 'Image not shown because sender requested not to inline it.' ) .'

    '); + return; + } + + my $filename = length $name ? $name : loc('(untitled)'); + my $efilename = $m->interp->apply_escapes( $filename, 'h' ); + $m->out( + qq{$efilename' + ); + } + elsif ( $message->ContentLength && $message->ContentLength > 0 ) { + $m->out( '

    ' . + loc( 'Message body not shown because it is not plain text.' ) . + '

    ' + ); + } +}; + + 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', +); <%ARGS> $User => undef $Address => undef -$NoEscape => 0 -$style => RT->Config->Get('UsernameFormat', $session{'CurrentUser'}) +$style => undef +$Link => 1 +id ? 'data-user-id="'.$User->id.'"' : "" |n %>>\ +% if ($Link and $User and $User->id and not $system_user{$User->id} and $session{CurrentUser}->Privileged) { +/User/Summary.html?id=<% $User->id %>">\ +<% $display %>\ +\ +% } else { +<% $display %>\ +% } +\ diff --git a/rt/share/html/Elements/ShowUserConcise b/rt/share/html/Elements/ShowUserConcise deleted file mode 100644 index 1fa970e8d..000000000 --- a/rt/share/html/Elements/ShowUserConcise +++ /dev/null @@ -1,67 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC -%# -%# -%# (Except where explicitly superseded by other copyright notices) -%# -%# -%# LICENSE: -%# -%# This work is made available to you under the terms of Version 2 of -%# the GNU General Public License. A copy of that license should have -%# been provided with this software, but in any event can be snarfed -%# from www.gnu.org. -%# -%# This work is distributed in the hope that it will be useful, but -%# WITHOUT ANY WARRANTY; without even the implied warranty of -%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%# General Public License for more details. -%# -%# You should have received a copy of the GNU General Public License -%# along with this program; if not, write to the Free Software -%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -%# 02110-1301 or visit their web page on the internet at -%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -%# -%# -%# CONTRIBUTION SUBMISSION POLICY: -%# -%# (The following paragraph is not intended to limit the rights granted -%# to you to modify and distribute this software under the terms of -%# the GNU General Public License and is only of importance to you if -%# you choose to contribute your changes and enhancements to the -%# community by submitting them to Best Practical Solutions, LLC.) -%# -%# By intentionally submitting any modifications, corrections or -%# derivatives to this work, or any other work intended for use with -%# Request Tracker, to Best Practical Solutions, LLC, you confirm that -%# you are the copyright holder for those contributions and you grant -%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -%# royalty-free, perpetual, license to use, copy, create derivative -%# works based on those contributions, and sublicense and distribute -%# those contributions and any derivatives thereof. -%# -%# END BPS TAGGED BLOCK }}} -%# Released under the terms of version 2 of the GNU Public License -<% $display |n %>\ -<%ARGS> -$User => undef -$Address => undef - -<%INIT> -if ( !$User && $Address ) { - $User = RT::User->new( $session{'CurrentUser'} ); - $User->LoadByEmail( $Address->address ); - if ( $User->Id ) { - $Address = ''; - } else { - $Address = $Address->address; - } -} -my $display = $Address || $User->RealName || $User->Name; - $display = $m->interp->apply_escapes( $display, 'h' ) - unless $ARGS{'NoEscape'}; - diff --git a/rt/share/html/Elements/ShowUserVerbose b/rt/share/html/Elements/ShowUserVerbose deleted file mode 100644 index fd26007e6..000000000 --- a/rt/share/html/Elements/ShowUserVerbose +++ /dev/null @@ -1,72 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC -%# -%# -%# (Except where explicitly superseded by other copyright notices) -%# -%# -%# LICENSE: -%# -%# This work is made available to you under the terms of Version 2 of -%# the GNU General Public License. A copy of that license should have -%# been provided with this software, but in any event can be snarfed -%# from www.gnu.org. -%# -%# This work is distributed in the hope that it will be useful, but -%# WITHOUT ANY WARRANTY; without even the implied warranty of -%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%# General Public License for more details. -%# -%# You should have received a copy of the GNU General Public License -%# along with this program; if not, write to the Free Software -%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -%# 02110-1301 or visit their web page on the internet at -%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -%# -%# -%# CONTRIBUTION SUBMISSION POLICY: -%# -%# (The following paragraph is not intended to limit the rights granted -%# to you to modify and distribute this software under the terms of -%# the GNU General Public License and is only of importance to you if -%# you choose to contribute your changes and enhancements to the -%# community by submitting them to Best Practical Solutions, LLC.) -%# -%# By intentionally submitting any modifications, corrections or -%# derivatives to this work, or any other work intended for use with -%# Request Tracker, to Best Practical Solutions, LLC, you confirm that -%# you are the copyright holder for those contributions and you grant -%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -%# royalty-free, perpetual, license to use, copy, create derivative -%# works based on those contributions, and sublicense and distribute -%# those contributions and any derivatives thereof. -%# -%# END BPS TAGGED BLOCK }}} -%# Released under the terms of version 2 of the GNU Public License -<% $display |n %>\ -<%INIT> -my $phrase = ''; -my $address = ''; -my $comment = ''; - -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; -} else { - $address = $Address; -} - -my $display = ($phrase || $comment || '' ) . ($address ? ' <'.$address.'>' : ''); - -$display = $m->interp->apply_escapes( $display, 'h' ) - unless $ARGS{'NoEscape'}; - -<%ARGS> -$User => undef -$Address => undef - 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 }}} +<%init> +my $value = defined $DECODED_ARGS->{q} ? $DECODED_ARGS->{q} : ''; + <%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%>" >
    % if ($CheckAll) { - + % } % if ($ClearAll) { - + % } % if ($Reset) { 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 +%# +%# +%# (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 + +<%ONCE> +my $no_html = HTML::Scrubber->new( deny => '*' ); + +<%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(); + + 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 = '' @@ -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' + ); <%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 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 ); 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 ); 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; <%ARGS> +$Object => RT::Ticket->new( $session{'CurrentUser'}) $CustomFields $ARGSRef -$NamePrefix => "Object-RT::Ticket--CustomField-" +$ValidateUnsubmitted => 0 -- cgit v1.2.1