diff options
Diffstat (limited to 'rt/share')
163 files changed, 9906 insertions, 134 deletions
diff --git a/rt/share/html/Admin/CustomFields/Modify.html b/rt/share/html/Admin/CustomFields/Modify.html index c94c560ec..5ef32127b 100644 --- a/rt/share/html/Admin/CustomFields/Modify.html +++ b/rt/share/html/Admin/CustomFields/Modify.html @@ -82,6 +82,14 @@ Default => $CustomFieldObj->LookupType, &> </td></tr> +% if ( $CustomFieldObj->Id +% and $CustomFieldObj->LookupType =~ /RT::Transaction/ ) { +<tr><td class="label"><&|/l&>Display with</&></td> +<td><& /Admin/Elements/EditCustomFieldUILocation, + CustomField => $CustomFieldObj &> +</td></tr> +% } + <tr><td class="label"><&|/l&>Validation</&></td> <td><& /Widgets/ComboBox, Name => 'Pattern', @@ -119,6 +127,11 @@ % } <tr><td class="label"> </td><td> +<input type="checkbox" class="checkbox" name="Required" value="1" <% $RequiredChecked |n%> /> +<&|/l&>Required for ticket resolution</&> +</td></tr> + +<tr><td class="label"> </td><td> <input type="hidden" class="hidden" name="SetEnabled" value="1" /> <input type="checkbox" class="checkbox" name="Enabled" value="1" <% $EnabledChecked |n%> /> <&|/l&>Enabled (Unchecking this box disables this custom field)</&> @@ -171,11 +184,12 @@ else { } if ( $ARGS{'Update'} && $id ne 'new' ) { - #we're asking about enabled on the web page but really care about disabled. $ARGS{'Disabled'} = $Disabled = $Enabled? 0 : 1; + + $ARGS{'Required'} ||= 0; - my @attribs = qw(Disabled Pattern Name TypeComposite LookupType Description LinkValueTo IncludeContentForValue); + my @attribs = qw(Disabled Required Pattern Name TypeComposite LookupType Description LinkValueTo IncludeContentForValue); push @results, UpdateRecordObject( AttributesRef => \@attribs, Object => $CustomFieldObj, @@ -185,6 +199,8 @@ if ( $ARGS{'Update'} && $id ne 'new' ) { $CustomFieldObj->SetBasedOn( $BasedOn ); + $CustomFieldObj->SetUILocation( $UILocation ); + my $paramtag = "CustomField-". $CustomFieldObj->Id ."-Value"; # Delete any fields that want to be deleted foreach my $key ( keys %ARGS ) { @@ -193,6 +209,15 @@ if ( $ARGS{'Update'} && $id ne 'new' ) { push (@results, $msg); } + # Clean up values + foreach my $param (grep /^$paramtag-/, keys(%ARGS)) { + for ($ARGS{$param}) { + s/\r+\n/\n/g; + s/^\s+//; + s/\s+$//; + } + } + # Update any existing values my $values = $CustomFieldObj->ValuesObj; while ( my $value = $values->Next ) { @@ -202,7 +227,6 @@ if ( $ARGS{'Update'} && $id ne 'new' ) { $ARGS{$param} =~ s/^\s+//; $ARGS{$param} =~ s/\s+$//; next if ($value->$attr()||'') eq ($ARGS{$param}||''); - my $mutator = "Set$attr"; my ($id, $msg) = $value->$mutator( $ARGS{$param} ); push (@results, $msg); @@ -226,6 +250,9 @@ $id = $CustomFieldObj->id if $CustomFieldObj->id; my $EnabledChecked = qq[checked="checked"]; $EnabledChecked = '' if $CustomFieldObj->Disabled; +my $RequiredChecked = ''; +$RequiredChecked = qq[checked="checked"] if $CustomFieldObj->Required; + my @CFvalidations = ( '(?#Mandatory).', '(?#Digits)^[\d.]+$', @@ -250,4 +277,5 @@ $ValuesClass => 'RT::CustomFieldValues' $LinkValueTo => undef $IncludeContentForValue => undef $BasedOn => undef +$UILocation => undef </%ARGS> diff --git a/rt/share/html/Admin/Elements/EditCustomFieldUILocation b/rt/share/html/Admin/Elements/EditCustomFieldUILocation new file mode 100644 index 000000000..9cffafabb --- /dev/null +++ b/rt/share/html/Admin/Elements/EditCustomFieldUILocation @@ -0,0 +1,66 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<div id="ui-location-class-block"> +<select name="UILocation"> +% foreach my $tag (@tags) { +<option value="<% $tag %>" <% $tag eq $CustomField->UILocation && 'selected="selected"' %>><% $description{$tag} %></option> +% } +</select> +</div> + +<%INIT> +my @sources; +my @tags = ( '', 'TimeWorked' ); +my %description = ( + '' => 'Custom fields', + 'TimeWorked' => 'Time Worked', +); +</%INIT> +<%ARGS> +$CustomField => undef +</%ARGS> diff --git a/rt/share/html/Admin/Elements/EditScrip b/rt/share/html/Admin/Elements/EditScrip index eed23ff31..c2e9de19f 100755 --- a/rt/share/html/Admin/Elements/EditScrip +++ b/rt/share/html/Admin/Elements/EditScrip @@ -64,14 +64,32 @@ <& /Admin/Elements/SelectScripCondition, Name => "Scrip-$id-ScripCondition", Default => $ARGS{"Scrip-$id-ScripCondition"} || $scrip->ConditionObj->Id, + ScripObj => $scrip, + Queue => $Queue, &></td></tr> +<& /Admin/Elements/EditScripOptions, + Name => "Condition", + Default => $ARGS{"Scrip-$id-ConditionRules"} || $scrip->ConditionRules, + Queue => $Queue, + ScripX => $ARGS{"Scrip-$id-ScripCondition"} || $scrip->ConditionObj->Id, +&> + <tr><td align="right"><&|/l&>Action</&>:</td><td> <& /Admin/Elements/SelectScripAction, Name => "Scrip-$id-ScripAction", Default => $ARGS{"Scrip-$id-ScripAction"} || $scrip->ActionObj->Id, + ScripObj => $scrip, + Queue => $Queue, &></td></tr> +<& /Admin/Elements/EditScripOptions, + Name => "Action", + Default => $ARGS{"Scrip-$id-ActionRules"} || $scrip->ActionRules, + Queue => $Queue, + ScripX => $ARGS{"Scrip-$id-ScripAction"} || $scrip->ActionObj->Id, +&> + <tr><td align="right"><&|/l&>Template</&>:</td><td> <& /Admin/Elements/SelectTemplate, Name => "Scrip-$id-Template", @@ -165,6 +183,18 @@ $Queue => undef <%INIT> return ($id) unless $id; +my @rules = ('ConditionRules', 'ActionRules'); +if ( exists($ARGS{"Scrip-$id-ScripCondition"}) ) { + foreach my $rules (@rules) { + my $prefix = join('-', 'Scrip', $id, $rules); + $ARGS{$prefix} = join("\n", map { + $_ =~ /^$rules-(.*)$/ ? + ($1, $ARGS{$_}) : () + } keys(%ARGS) + ); + } +} + my $scrip = RT::Scrip->new( $session{'CurrentUser'} ); if ( $id eq 'new' ) { return $scrip->Create( @@ -177,6 +207,8 @@ if ( $id eq 'new' ) { CustomCommitCode => $ARGS{"Scrip-new-CustomCommitCode"}, CustomIsApplicableCode => $ARGS{"Scrip-new-CustomIsApplicableCode"}, Stage => $ARGS{"Scrip-new-Stage"}, + ConditionRules => $ARGS{"Scrip-new-ConditionRules"}, + ActionRules => $ARGS{"Scrip-new-ActionRules"}, ); } else { @@ -185,7 +217,8 @@ else { unless $scrip->id; my @attribs = qw(Queue ScripAction ScripCondition Template Stage - Description CustomPrepareCode CustomCommitCode CustomIsApplicableCode); + Description CustomPrepareCode CustomCommitCode CustomIsApplicableCode + ConditionRules ActionRules); my @results = UpdateRecordObject( AttributesRef => \@attribs, AttributePrefix => 'Scrip-'.$scrip->Id, diff --git a/rt/share/html/Admin/Elements/EditScripOptions b/rt/share/html/Admin/Elements/EditScripOptions new file mode 100644 index 000000000..264ca3a20 --- /dev/null +++ b/rt/share/html/Admin/Elements/EditScripOptions @@ -0,0 +1,48 @@ +% return if !@options; +<tr><td></td><td><table> +% my $prefix = $Name.'Rules-'; +% foreach my $o (@options) { +% my $curr_value = $rules{ $o->{'name'} }; + <tr><td align="right"><% $o->{'label'} %>:</td> + <td> +% if ( $o->{'type'} eq 'text' ) { + <input type="text" name="<% $prefix.$o->{'name'} %>" value="<% $curr_value %>"> +% } +% elsif ( $o->{'type'} eq 'checkbox' ) { + <input type="checkbox" name="<% $prefix.$o->{'name'} %>" value="1" <% $curr_value ? 'CHECKED' : '' %>> +% } +% elsif ( $o->{'type'} eq 'select' and ref $o->{'options'} ) { + <select name="<% $prefix.$o->{'name'} %>"> +% my @choices = @{ $o->{'options'} }; +% while (@choices) { +% my $v = shift @choices; +% my $l = shift @choices; + <option value="<% $v %>"<% ($curr_value eq $v) ? ' SELECTED' : ''%>> + <% $l %></option> +% } + </select> +% } # else $o->{'type'} +</td></tr> +% } #foreach $o +</table></td></tr> + +<%INIT> +my (@options, %rules); +if ( $ScripX ) { + my $ScripXObj = "RT::Scrip$Name"->new($session{'CurrentUser'}); + $ScripXObj->Load($ScripX); + my $QueueObj = RT::Queue->new($session{'CurrentUser'}); + $QueueObj->Load($Queue); + my $method = "Load$Name"; + my $XObj = $ScripXObj->$method(); + @options = $XObj->Options('QueueObj' => $QueueObj); + %rules = split("\n", $Default); +} +</%INIT> + +<%ARGS> +$Name => undef +$Default => undef +$Queue => 0 +$ScripX => undef +</%ARGS> diff --git a/rt/share/html/Admin/Elements/SelectScripAction b/rt/share/html/Admin/Elements/SelectScripAction index 425028dba..4dd39f55b 100755 --- a/rt/share/html/Admin/Elements/SelectScripAction +++ b/rt/share/html/Admin/Elements/SelectScripAction @@ -45,7 +45,10 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<select name="<%$Name%>"> +<select name="<%$Name%>" +onchange="var idobj = document.getElementsByName('id')[0]; +if (idobj.value=='new') idobj.value = ''; +form.submit()"> <option value="" <% ! defined $Default && qq[ selected="selected"] |n %> >-</option> diff --git a/rt/share/html/Admin/Elements/SelectScripCondition b/rt/share/html/Admin/Elements/SelectScripCondition index b1dc5b0dd..67438a72a 100755 --- a/rt/share/html/Admin/Elements/SelectScripCondition +++ b/rt/share/html/Admin/Elements/SelectScripCondition @@ -45,7 +45,10 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<select name="<%$Name%>"> +<select name="<%$Name%>" +onchange="var idobj = document.getElementsByName('id')[0]; +if (idobj.value=='new') idobj.value = ''; +form.submit()"> <option value="" <% ! defined $Default && qq[ selected="selected"] %> >-</option> diff --git a/rt/share/html/Admin/Users/Modify.html b/rt/share/html/Admin/Users/Modify.html index 09c0c6c97..28d594377 100755 --- a/rt/share/html/Admin/Users/Modify.html +++ b/rt/share/html/Admin/Users/Modify.html @@ -105,6 +105,12 @@ </table> </&> <br /> + +<&| /Widgets/TitleBox, title => loc('Customers') &> +<& /Elements/EditCustomers, Object => $UserObj, CustomerString=> $CustomerString, ServiceString => $ServiceString &> +</&> +<br /> + <&| /Widgets/TitleBox, title => loc('Access control') &> <input type="hidden" class="hidden" name="SetEnabled" value="1" /> <input type="checkbox" class="checkbox" name="Enabled" value="1" <%$EnabledChecked%> /> @@ -330,6 +336,8 @@ if ($UserObj->Id && $id ne 'new') { push (@results,@fieldresults); push @results, ProcessObjectCustomFieldUpdates( ARGSRef => \%ARGS, Object => $UserObj ); + #deal with freeside customer links + push @results, ProcessObjectCustomers( ARGSRef => \%ARGS, Object => $UserObj ); # {{{ Deal with special fields: Privileged, Enabled if ( $SetPrivileged and $Privileged != $UserObj->Privileged ) { @@ -418,4 +426,8 @@ $CurrentPass => undef $Pass1 => undef $Pass2 => undef $Create=> undef +$OnlySearchForCustomers => undef +$OnlySearchForServices => undef +$CustomerString => undef +$ServiceString => undef </%ARGS> diff --git a/rt/share/html/Callbacks/RT-Extension-MobileUI/Elements/Login/Header b/rt/share/html/Callbacks/RT-Extension-MobileUI/Elements/Login/Header new file mode 100644 index 000000000..9e6ac0a35 --- /dev/null +++ b/rt/share/html/Callbacks/RT-Extension-MobileUI/Elements/Login/Header @@ -0,0 +1,12 @@ +<%init> +if ( defined($RT::Extension::MobileUI::VERSION) + and ( RT::Extension::MobileUI->MobileClient() || + ($m->request_comp->path() =~ m{^/m(?:\/|$)})) { + + $m->comp('/m/login',%ARGS); + $m->abort; +} else { +return; +} + +</%init> diff --git a/rt/share/html/Callbacks/RT-Extension-MobileUI/Ticket/Create.html/Init b/rt/share/html/Callbacks/RT-Extension-MobileUI/Ticket/Create.html/Init new file mode 100644 index 000000000..f9c418f35 --- /dev/null +++ b/rt/share/html/Callbacks/RT-Extension-MobileUI/Ticket/Create.html/Init @@ -0,0 +1,13 @@ +<%INIT> +if ( defined($RT::Extension::MobileUI::VERSION) + and RT::Extension::MobileUI::MobileClient() ) { + RT::Interface::Web::Redirect( + RT->Config->Get('WebURL').'m/ticket/create?'. + $m->comp('/Elements/QueryString', %$ARGSRef), + ); + $m->abort; +} +</%INIT> +<%ARGS> +$ARGSRef => {} +</%ARGS> diff --git a/rt/share/html/Callbacks/RT-Extension-MobileUI/Ticket/Display.html/Initial b/rt/share/html/Callbacks/RT-Extension-MobileUI/Ticket/Display.html/Initial new file mode 100644 index 000000000..6b6edbeea --- /dev/null +++ b/rt/share/html/Callbacks/RT-Extension-MobileUI/Ticket/Display.html/Initial @@ -0,0 +1,14 @@ +<%INIT> +return if $ARGSRef->{'NoRedirect'}; +if ( defined($RT::Extension::MobileUI::VERSION) + and RT::Extension::MobileUI::MobileClient()) { + my $id = $ARGSRef->{'id'} || ($TicketObj ? $TicketObj->id : undef); + RT::Interface::Web::Redirect(RT->Config->Get('WebURL').'m/ticket/show?id='.$id); + $m->abort; +} +</%INIT> + +<%ARGS> +$TicketObj => undef +$ARGSRef => {} +</%ARGS> diff --git a/rt/share/html/Callbacks/RT-Extension-MobileUI/index.html/Initial b/rt/share/html/Callbacks/RT-Extension-MobileUI/index.html/Initial new file mode 100644 index 000000000..d63445459 --- /dev/null +++ b/rt/share/html/Callbacks/RT-Extension-MobileUI/index.html/Initial @@ -0,0 +1,16 @@ +<%init> +# avoid fatal errors if the extension isn't loaded +if ( defined( $RT::Extension::MobileUI::VERSION ) + and RT::Extension::MobileUI->MobileClient()) { + my $path = 'm'; + if ( $ARGSRef->{'q'} ) { + $path = "m/tickets/search?q=". $m->interp->apply_escapes($ARGSRef->{'q'}); + } + RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . $path); +} else { +return +} +</%init> +<%ARGS> +$ARGSRef => {} +</%ARGS> diff --git a/rt/share/html/Callbacks/RTx-Calendar/Elements/Header/Head b/rt/share/html/Callbacks/RTx-Calendar/Elements/Header/Head new file mode 100644 index 000000000..c1f24c2b4 --- /dev/null +++ b/rt/share/html/Callbacks/RTx-Calendar/Elements/Header/Head @@ -0,0 +1,2 @@ +<link rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/css/calendar.css" type="text/css" media="all" /> + diff --git a/rt/share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default b/rt/share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default new file mode 100644 index 000000000..cb46fdaf6 --- /dev/null +++ b/rt/share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default @@ -0,0 +1,19 @@ +<%init> +my $args; +$args= "?" . $m->comp( + '/Elements/QueryString', + Query => $ARGS{'Query'} || $session{'CurrentSearchHash'}->{'Query'}, + Format => $ARGS{'Format'} || $session{'CurrentSearchHash'}->{'Format'}, + OrderBy => $ARGS{'OrderBy'} || $session{'CurrentSearchHash'}->{'OrderBy'}, + Order => $ARGS{'Order'} || $session{'CurrentSearchHash'}->{'Order'}, + Page => $ARGS{'Page'} || $session{'CurrentSearchHash'}->{'Page'}, + Rows => $ARGS{'Rows'}, + ) if ($ARGS{'Query'} or $session{'CurrentSearchHash'}->{'Query'}); +$args ||= ''; + +$tabs->{'zz'} = { title =>loc("Calendar"), + path => "Search/Calendar.html$args" }; +</%init> +<%args> +$tabs +</%args> diff --git a/rt/share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default b/rt/share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default new file mode 100644 index 000000000..06413e278 --- /dev/null +++ b/rt/share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default @@ -0,0 +1,9 @@ +<%init> + $tabs->{'z'} = { title =>loc("Calendar"), + path => "Prefs/Calendar.html" }; +</%init> +<%args> +$tabs +$current_subtab => undef +$Searches => undef +</%args> diff --git a/rt/share/html/Callbacks/RTx-Statistics/Elements/Tabs/Default b/rt/share/html/Callbacks/RTx-Statistics/Elements/Tabs/Default new file mode 100644 index 000000000..d4ca2b95e --- /dev/null +++ b/rt/share/html/Callbacks/RTx-Statistics/Elements/Tabs/Default @@ -0,0 +1,11 @@ +<%init> +use RTx::Statistics; +if (($Statistics::RestrictAccess == 0) || ($session{'CurrentUser'}->HasRight( Right => 'ShowConfigTab', + Object => $RT::System ))) { + $toptabs->{'ZZ-RTx-STATS'} = { title => 'RTx-Statistics', + path => "RTx/Statistics/index.html" }; +} +</%init> +<%args> + $toptabs =>undef +</%args> diff --git a/rt/share/html/Callbacks/SearchCustomerFields/Search/Elements/PickBasics/Default b/rt/share/html/Callbacks/SearchCustomerFields/Search/Elements/PickBasics/Default new file mode 100644 index 000000000..abbafbcf1 --- /dev/null +++ b/rt/share/html/Callbacks/SearchCustomerFields/Search/Elements/PickBasics/Default @@ -0,0 +1,46 @@ +<%init> +push @$Conditions, + { + Name => 'Agentnum', + Field => 'Agent', + Op => { + Type => 'component', + Path => '/Elements/SelectBoolean', + Arguments => { TrueVal=> '=', FalseVal => '!=' }, + }, + Value => { + Type => 'component', + Path => '/Elements/SelectCustomerAgent', + }, + }, + { + Name => 'Classnum', + Field => 'Customer Class', + Op => { + Type => 'component', + Path => '/Elements/SelectBoolean', + Arguments => { TrueVal=> '=', FalseVal => '!=' }, + }, + Value => { + Type => 'component', + Path => '/Elements/SelectCustomerClass', + }, + }, + { + Name => 'Tagnum', + Field => 'Tag', + Op => { + Type => 'component', + Path => '/Elements/SelectBoolean', + Arguments => { TrueVal=> '=', FalseVal => '!=' }, + }, + Value => { + Type => 'component', + Path => '/Elements/SelectCustomerTag', + }, + }, +; +</%init> +<%ARGS> +$Conditions => [] +</%ARGS> diff --git a/rt/share/html/Callbacks/TimeToResolve/Elements/RT__Ticket/ColumnMap/Once b/rt/share/html/Callbacks/TimeToResolve/Elements/RT__Ticket/ColumnMap/Once new file mode 100644 index 000000000..df5d29e14 --- /dev/null +++ b/rt/share/html/Callbacks/TimeToResolve/Elements/RT__Ticket/ColumnMap/Once @@ -0,0 +1,13 @@ +<%init> +$COLUMN_MAP->{'TimeToResolve'} = { + title => 'Time to Resolve', + attribute => 'Resolved', + value => sub { + my $r = $_[0]->ResolvedObj or return ''; + return $r->DiffAsString($_[0]->CreatedObj); + } +}; +</%init> +<%ARGS> +$COLUMN_MAP => {} +</%ARGS> diff --git a/rt/share/html/Callbacks/TimeToResolve/Search/Elements/BuildFormatString/SetFieldsOnce b/rt/share/html/Callbacks/TimeToResolve/Search/Elements/BuildFormatString/SetFieldsOnce new file mode 100644 index 000000000..54dcae04c --- /dev/null +++ b/rt/share/html/Callbacks/TimeToResolve/Search/Elements/BuildFormatString/SetFieldsOnce @@ -0,0 +1,8 @@ +<%init> +my $i = 1; +$i++ until ($i == scalar(@$Fields) or $Fields->[$i-1] =~ /^Resolved/); +splice @$Fields, $i, 0, 'TimeToResolve'; +</%init> +<%ARGS> +$Fields => [] +</%ARGS> diff --git a/rt/share/html/Elements/AddCustomers b/rt/share/html/Elements/AddCustomers new file mode 100644 index 000000000..9828d7d53 --- /dev/null +++ b/rt/share/html/Elements/AddCustomers @@ -0,0 +1,62 @@ +%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am> +%# Copyright (c) 2008 Freeside Internet Services, Inc. +%# +%# 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. +<BR> +<%$msg%><br> + +% if (@Customers) { + +<br><i>(Check box to link)<i> +<table> +% foreach my $customer (@Customers) { +<tr> + <td> + <input type="checkbox" name="Object-AddCustomer-<% $customer->{'custnum'} %>" VALUE="1" <% scalar(@Customers) == 1 ? 'CHECKED' : '' %>> + <A HREF="<%$freeside_url%>/view/cust_main.cgi?<% $customer->{'custnum'} %>"><% &RT::URI::freeside::small_custview($customer->{'custnum'}, &RT::URI::freeside::FreesideGetConfig('countrydefault'), 1) |n %> + </td> +</tr> +% } +</table> + +% } + +<%INIT> +my ($msg); + +my $freeside_url = &RT::URI::freeside::FreesideURL(); + +warn "/Elements/AddCustomers called with CustomerString $CustomerString\n" + if $Debug; + +my @Customers = (); +if ( $CustomerString ) { + @Customers = &RT::URI::freeside::smart_search( + 'search' => $CustomerString, + 'no_fuzzy_on_exact' => 1, #pref? + ); +} + +my @Services = (); +if ($ServiceString) { + @Services = (); #service_search(); +} + +warn "/Elements/AddCustomers displaying ". scalar(@Customers). " customers\n" + if $Debug; + +</%INIT> + +<%ARGS> +$CustomerString => undef +$ServiceString => undef +$Debug => 0 +</%ARGS> diff --git a/rt/share/html/Elements/CalendarEvent b/rt/share/html/Elements/CalendarEvent new file mode 100644 index 000000000..3a6b00bb8 --- /dev/null +++ b/rt/share/html/Elements/CalendarEvent @@ -0,0 +1,129 @@ +<%args> +$Date => undef +$Object => undef +$DateTypes => undef +</%args> +<div class="tooltip"> +<small> + +% if ($IsReminder and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today) { + <img src="<%$RT::WebImagesURL%>/reminder.png" /> + +% } elsif ($DateTypes->{Resolved} +% and RTx::Calendar::LocalDate($Object->ResolvedObj->Unix) eq $today) { + <img src="<%$RT::WebImagesURL%>/resolved.png" /> + +% } elsif ($DateTypes->{Starts} and $DateTypes->{Due} +% and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $today and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today ) { + <img src="<%$RT::WebImagesURL%>/starts_due.png" /> + +% } elsif ($DateTypes->{Due} and $DateTypes->{Created} +% and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $today ) { + <img src="<%$RT::WebImagesURL%>/created_due.png" /> + +% } elsif ($DateTypes->{Starts} +% and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $today) { + <img src="<%$RT::WebImagesURL%>/starts.png" /> + +% } elsif ($DateTypes->{Due} +% and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today) { + <img src="<%$RT::WebImagesURL%>/due.png" /> + +% } elsif ($DateTypes->{Created} +% and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $today) { + <img src="<%$RT::WebImagesURL%>/created.png" /> + +% } elsif ($DateTypes->{Started} +% and RTx::Calendar::LocalDate($Object->StartedObj->Unix) eq $today) { + <img src="<%$RT::WebImagesURL%>/started.png" /> + +% } elsif ($DateTypes->{LastUpdated} +% and RTx::Calendar::LocalDate($Object->LastUpdatedObj->Unix) eq $today) { + <img src="<%$RT::WebImagesURL%>/updated.png" /> + +% } + + <a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>"> + <% $Object->QueueObj->Name %> #<% $TicketId %> + <% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %> + <% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %></a></small><br /> + <span class="tip"> + <a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>"> + <% $Object->QueueObj->Name %> #<% $TicketId %> + </a> + :</strong> <% $subject%><br /> + <br /> + +%# logic taken from Ticket/Search/Results.tsv +% foreach my $attr (@display_fields) { +% my $value; +% +% if ($attr =~ /(.*)->ISO$/ and $Object->$1->Unix <= 0) { +% $value = '-'; +% } else { +% my $method = '$Object->'.$attr.'()'; +% $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/; +% $value = eval $method; +% if ($@) {die "<b>Check your CalendarPopupFields config in etc/RT_SiteConfig.pm</b>.<br /><br />Failed to find \"$attr\" - ". $@}; +% } + <strong><&|/l&><% $label_of{$attr} %></&>:</strong> <% $value %><br /> +% } + +<br /> + </span> +</div> + +<%init> +use RTx::Calendar; + +my $today = $Date->strftime("%F"); + +my $TicketId; + +my $ticket; +my $subject; +my $IsReminder; + +if ($Object->Type eq 'reminder') { + $IsReminder = 1; + if ($Object->RefersTo->First) { + $ticket = $Object->RefersTo->First->TargetObj; + $TicketId = $ticket->Id; + $subject = $Object->Subject . " (" . $ticket->Subject . ")"; + } +} else { + $TicketId = $Object->Id; + $subject = $Object->Subject; +} + +my $display_owner = $RT::CalendarDisplayOwner; +$display_owner ||= RT->Config->Get('CalendarDisplayOwner') + if RT->can('Config'); + + +# 3.6 config +my @display_fields = @RT::CalendarPopupFields; + +# 3.8 config +# the if condition is weird but it doesn't work with 3.8.0 without the last part +@display_fields = RT->Config->Get('CalendarPopupFields') + if 0 == @display_fields and RT->can('Config') and RT->Config->Get('CalendarPopupFields'); + +# default +if (0 == @display_fields) { + @display_fields = qw(OwnerObj->Name CreatedObj->ISO StartsObj->ISO + StartedObj->ISO LastUpdatedObj->ISO DueObj->ISO + ResolvedObj->ISO Status Priority + Requestors->MemberEmailAddressesAsString); +} + + +my %label_of; +for my $field (@display_fields) { + my $label = $field; + $label =~ s'Obj-.(?:AsString|Name|ISO)''g; + $label =~ s'-\>MemberEmailAddressesAsString''g; + $label_of{$field} = $label; +} + +</%init> diff --git a/rt/share/html/Elements/CollectionList b/rt/share/html/Elements/CollectionList index a57006e5e..522db5811 100644 --- a/rt/share/html/Elements/CollectionList +++ b/rt/share/html/Elements/CollectionList @@ -133,7 +133,9 @@ if ( $ShowHeader ) { my ($i, $column_map) = (0, {}); while ( my $record = $Collection->Next ) { # Every ten rows, flush the buffer and put something on the page. - $m->flush_buffer unless ++$i % 10; + #broken w/FS, causes rows to be output prematurely + #$m->flush_buffer unless ++$i % 10; + ++$i; my $warning = 0; my $Classes = ''; diff --git a/rt/share/html/Elements/ColumnMap b/rt/share/html/Elements/ColumnMap index 8afe4a163..5e5354ade 100644 --- a/rt/share/html/Elements/ColumnMap +++ b/rt/share/html/Elements/ColumnMap @@ -64,6 +64,7 @@ my $COLUMN_MAP = { Created => { attribute => 'Created', title => 'Created', # loc + date => sub { return $_[0]->CreatedObj }, value => sub { return $_[0]->CreatedObj->AsString } }, CreatedRelative => { @@ -79,6 +80,7 @@ my $COLUMN_MAP = { LastUpdated => { attribute => 'LastUpdated', title => 'Last Updated', # loc + date => sub { return $_[0]->LastUpdatedObj }, value => sub { return $_[0]->LastUpdatedObj->AsString } }, LastUpdatedRelative => { diff --git a/rt/share/html/Elements/EditCustomFieldDate b/rt/share/html/Elements/EditCustomFieldDate new file mode 100644 index 000000000..b6359d7e0 --- /dev/null +++ b/rt/share/html/Elements/EditCustomFieldDate @@ -0,0 +1,62 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/copyleft/gpl.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 $name = $NamePrefix.$CustomField->Id.'-Values'; +<& /Elements/SelectDate, Name => "$name", ShowTime => 0, current => 0 &> (<%$DateObj->AsString%>) + +<%INIT> +my $DateObj = new RT::Date ( $session{'CurrentUser'} ); +$DateObj->Set( Format => 'ISO', Value => $Default ); +</%INIT> +<%ARGS> +$Object => undef +$CustomField => undef +$NamePrefix => undef +$Default => undef +$Values => undef +$MaxValues => 1 +</%ARGS> diff --git a/rt/share/html/Elements/EditCustomFieldTimeValue b/rt/share/html/Elements/EditCustomFieldTimeValue new file mode 100644 index 000000000..064554594 --- /dev/null +++ b/rt/share/html/Elements/EditCustomFieldTimeValue @@ -0,0 +1,16 @@ +% my $name = $NamePrefix . $CustomField->Id . '-Value'; +% if ($Multiple) { +% $RT::Logger->error("TimeValue Multiple custom field not supported"); +% return; +% } +<& /Elements/EditTimeValue, + Name => $name, + Default => $Default, + InUnits => $ARGS{"$name-TimeUnits"} || 'minutes', +&> +<%ARGS> +$CustomField => undef +$NamePrefix => undef +$Default => undef +$Multiple => undef +</%ARGS> diff --git a/rt/share/html/Elements/EditCustomers b/rt/share/html/Elements/EditCustomers new file mode 100644 index 000000000..68efb5f40 --- /dev/null +++ b/rt/share/html/Elements/EditCustomers @@ -0,0 +1,63 @@ +%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am> +%# Copyright (c) 2008 Freeside Internet Services, Inc. +%# +%# 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. +<TABLE width=100%> + <TR> + <TD VALIGN=TOP WIDTH=50%> + <h3><&|/l&>Current Customers</&></h3> + +<table> + <tr> + <td><i><&|/l&>(Check box to disassociate)</&></i></td> + </tr> + <tr> + <td class="value"> +% foreach my $link ( @{ $Object->Customers->ItemsArrayRef } ) { + + <INPUT TYPE=CHECKBOX NAME="DeleteLink--<%$link->Type%>-<%$link->Target%>"> +%# <& ShowLink, URI => $link->TargetURI &><br> + <A HREF="<% $link->TargetURI->Resolver->HREF %>"><% $link->TargetURI->Resolver->AsStringLong |n %></A> + <BR> +% } + </td> + </tr> +</table> + +</TD> + +<TD VALIGN=TOP> +<h3><&|/l&>New Customer Links</&></h3> +<&|/l&>Find customer</&><BR> +<input name="CustomerString"> +<input type=submit name="OnlySearchForCustomers" value="<&|/l&>Go!</&>"> +<br><i>cust #, name, company or phone</i> +<BR> +%#<BR> +%#<&|/l&>Find service</&><BR> +%#<input name="ServiceString"> +%#<input type=submit name="OnlySearchForServices" value="<&|/l&>Go!</&>"> +%#<br><i>username, username@domain, domain, or IP address</i> +%#<BR> + +<& AddCustomers, Object => $Object, + CustomerString => $CustomerString, + ServiceString => $ServiceString, &> + +</TD> +</TR> +</TABLE> + +<%ARGS> +$CustomerString => undef +$ServiceString => undef +$Object => undef +</%ARGS> diff --git a/rt/share/html/Elements/EditLinks b/rt/share/html/Elements/EditLinks index 469b84a9c..d6d1ee955 100755 --- a/rt/share/html/Elements/EditLinks +++ b/rt/share/html/Elements/EditLinks @@ -73,6 +73,7 @@ <td class="labeltop"><& ShowRelationLabel, id => $id, Label => loc('Parents'), Relation => 'Parents' &>:</td> <td class="value"> % while (my $link = $Object->MemberOf->Next) { +% next if $link->Target and $link->Target =~ m(^freeside://); <input type="checkbox" class="checkbox" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" /> <& ShowLink, URI => $link->TargetURI &><br /> % } diff --git a/rt/share/html/Elements/Footer b/rt/share/html/Elements/Footer index 27962136a..e339a05e0 100755 --- a/rt/share/html/Elements/Footer +++ b/rt/share/html/Elements/Footer @@ -48,25 +48,6 @@ %# End of div#body from /Elements/PageLayout </div> % $m->callback( %ARGS ); -<div id="footer"> -% if ($m->{'rt_base_time'}) { - <p id="time"> - <span><&|/l&>Time to display</&>: <%Time::HiRes::tv_interval( $m->{'rt_base_time'} )%></span> - </p> -%} - <p id="bpscredits"> - <span> -<&|/l, '»|«', $RT::VERSION, '2010', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>', &>[_1] RT [_2] Copyright 1996-[_3] [_4].</&> -</span> -</p> -% if (!$Menu) { - <p id="legal"> -<&|/l&>Distributed under version 2 <a href="http://www.gnu.org/copyleft/gpl.html"> of the GNU GPL.</a></&><br /> -<&|/l, '<a href="mailto:sales@bestpractical.com">sales@bestpractical.com</a>' &>To inquire about support, training, custom development or licensing, please contact [_1].</&><br /> - </p> -% } - -</div> % if ($Debug >= 2 ) { % require Data::Dumper; % my $d = Data::Dumper->new([\%ARGS], [qw(%ARGS)]); diff --git a/rt/share/html/Elements/Header b/rt/share/html/Elements/Header index f9bd27fbf..64d548dc3 100755 --- a/rt/share/html/Elements/Header +++ b/rt/share/html/Elements/Header @@ -45,62 +45,67 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<!DOCTYPE html - PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<& /elements/header.html, { + 'title' => $Title, + 'head' => $head, + 'etc' => $etc, + 'nobr' => 1, + 'nocss' => 1, + } +&> +<%INIT> +$r->headers_out->{'Pragma'} = 'no-cache'; +$r->headers_out->{'Cache-control'} = 'no-cache'; -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head> -<title><%$Title%></title> +my $id = $m->request_comp->path; +$id =~ s|^/||g; +$id =~ s|/|-|g; +$id =~ s|\.html$||g; +$id =~ s|index$||g + if $id ne 'index'; +$id =~ s|-$||g; +my $head = ''; -% if ($Refresh && $Refresh =~ /^(\d+)/ && $1 > 0) { -% my $URL = $m->notes->{LogoutURL}; $URL = $URL ? ";URL=$URL" : ""; - <meta http-equiv="refresh" content="<% "$1$URL" %>" /> -% } +if ($Refresh && $Refresh =~ /^(\d+)/ && $1 > 0) { + $head .= qq( <meta http-equiv="refresh" content="$Refresh" /> ); +} -<link rel="shortcut icon" href="<%RT->Config->Get('WebImagesURL')%>/favicon.png" type="image/png" /> -<link rel="stylesheet" href="<%RT->Config->Get('WebPath')%>/NoAuth/css/<% RT->Config->Get( 'WebDefaultStylesheet', $session{'CurrentUser'} ) %>/main<% RT->Config->Get('DevelMode')? '' : '-squished' %>.css" type="text/css" media="all" /> -<link rel="stylesheet" href="<%RT->Config->Get('WebPath')%>/NoAuth/css/print.css" type="text/css" media="print" /> +my $WebPath = RT->Config->Get('WebPath'); +my $WebImagesURL = RT->Config->Get('WebImagesURL'); +my $WebDefaultStylesheet = + RT->Config->Get('WebDefaultStylesheet', $session{'CurrentUser'}); +my $squished = RT->Config->Get('DevelMode') ? '' : '-squished'; -% for (keys %{$LinkRel || {}}) { - <link rel="<% $_ %>" href="<% RT->Config->Get('WebPath') . $LinkRel->{$_} %>" /> -% } +$head .= <<END; +<link rel="shortcut icon" href="$WebImagesURL/favicon.png" type="image/png" /> +<link rel="stylesheet" href="$WebPath/NoAuth/css/$WebDefaultStylesheet/main$squished.css" type="text/css" media="all" /> +<link rel="stylesheet" href="$WebPath/NoAuth/css/print.css" type="text/css" media="print" /> +END -% if ( $RSSAutoDiscovery ) { - <link rel="alternate" href="<%$RSSAutoDiscovery%>" type="application/rss+xml" title="RSS RT Search" /> -% } +for (keys %{$LinkRel || {}}) { + $head .= qq(<link rel="$_" href="$WebPath) . $LinkRel->{$_} . '" />'; +} -% if ($JavaScript) { -<& HeaderJavascript, focus => $Focus, onload => $onload &> -% } +if ( $RSSAutoDiscovery ) { + $head .= qq(<link rel="alternate" href="$RSSAutoDiscovery" type="application/rss+xml" title="RSS RT Search" />); +} -% my $stylesheet_plugin = "/NoAuth/css/". RT->Config->Get( 'WebDefaultStylesheet', $session{'CurrentUser'} )."/InHeader"; -% if ($m->comp_exists($stylesheet_plugin) ) { -<& $stylesheet_plugin &> -% } -% $m->callback( %ARGS, CallbackName => 'Head' ); +if ($JavaScript) { + $head .= $m->scomp('HeaderJavascript', focus => $Focus, onload => $onload); +} -</head> - <body<% $id && qq[ id="comp-$id"] |n %>> +my $stylesheet_plugin = "/NoAuth/css/$WebDefaultStylesheet/InHeader"; +if ($m->comp_exists($stylesheet_plugin) ) { + $head .= $m->scomp($stylesheet_plugin); +} -% if ($ShowBar) { -<& /Elements/Logo, %ARGS &> +# $m->callback( %ARGS, CallbackName => 'Head' ); +$head .= $m->scomp( '/Elements/Callback', _CallbackName => 'Head', %ARGS ); -<div id="quickbar"> - <& /Elements/PersonalQuickbar, %ARGS &> -% } +my $etc = ''; +$etc .= qq[ id="comp-$id"] if $id; -<%INIT> -$r->headers_out->{'Pragma'} = 'no-cache'; -$r->headers_out->{'Cache-control'} = 'no-cache'; - -my $id = $m->request_comp->path; -$id =~ s|^/||g; -$id =~ s|/|-|g; -$id =~ s|\.html$||g; -$id =~ s|index$||g - if $id ne 'index'; -$id =~ s|-$||g; </%INIT> <%ARGS> diff --git a/rt/share/html/Elements/MyCalendar b/rt/share/html/Elements/MyCalendar new file mode 100644 index 000000000..a54ab39d6 --- /dev/null +++ b/rt/share/html/Elements/MyCalendar @@ -0,0 +1,78 @@ +<&|/Widgets/TitleBox, + title => loc("Calendar"), + title_href => "Search/Calendar.html" &> + +<table class="rtxcalendar"> +<thead> +<tr> +% my $date = $begin->clone; +% while ( $date <= $end ) { +<th width="14%"><%$rtdate->GetWeekday($date->day_of_week % 7)%></th> +% $date = $set->next($date); +% } +</tr> +</thead> +<tbody> +<tr> +% $date = $begin->clone; +% while ($date <= $end) { +<td> +<p class="date"><%$date->day%></p> +% for my $t (@{ $Tickets{$date->strftime("%F")} }) { +<& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &> +% } +</td> +% $date = $set->next($date); +% } +</tr> +</tbody> +</table> + + </&> + +<%INIT> + +use RTx::Calendar; + +my $title = loc("Calendar"); + +my $rtdate = RT::Date->new($session{'CurrentUser'}); + +my @DateTypes = qw/Created Starts Started Due LastUpdated Resolved/; + +my $today = DateTime->today; + +# this line is used to debug MyCalendar +# $today = DateTime->new(year => 2007, month => 4, day => 11); + +my $begin = $today->clone->subtract( days => 3); +my $end = $today->clone->add( days => 3); + +# use this to loop over days until $end +my $set = DateTime::Set->from_recurrence( + next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) } +); + +my $Query = "( Status = 'new' OR Status = 'open' OR Status = 'stalled') + AND ( Owner = '" . $session{CurrentUser}->Id ."' OR Owner = 'Nobody' ) + AND ( Type = 'reminder' OR 'Type' = 'ticket' )"; +my $Format = "__Starts__ __Due__"; + +if ( my $Search = RTx::Calendar::SearchDefaultCalendar($session{CurrentUser}) ) { + $Format = $Search->SubValue('Format'); + $Query = $Search->SubValue('Query'); +} + +# we search all date types in Format string +my @Dates = grep { $Format =~ m/__${_}(Relative)?__/ } @DateTypes; + +# used to display or not a date in Element/CalendarEvent +my %DateTypes = map { $_ => 1 } @Dates; + +$Query .= RTx::Calendar::DatesClauses(\@Dates, $begin->strftime("%F"), $end->strftime("%F")); + +# print STDERR $Query, "\n"; + +my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates); + +</%INIT> diff --git a/rt/share/html/Elements/PageLayout b/rt/share/html/Elements/PageLayout index 7ef203ade..cc0bb06dc 100755 --- a/rt/share/html/Elements/PageLayout +++ b/rt/share/html/Elements/PageLayout @@ -45,23 +45,27 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} + +<% include('/elements/init_calendar.html') |n %> + +% if (0) { ## new ticket via customer, and we already have a ticket search box <div id="topactions"> % foreach my $action (reverse sort keys %{$topactions}) { <span class="topaction" id="topaction-<%$action%>"><% $topactions->{"$action"}->{'html'} |n %></span> % } </div> +% } -%# End of div#quickbar from /Elements/Header -</div> - +% if (0) { ##FREESIDE MENUS INSTEAD## if ( $show_menu ) { % if ( $show_menu ) { <div id="nav"> <& /Elements/Menu, toptabs => $toptabs, current_toptab => $current_toptab &> </div> % } +% } <div id="header"> -<h1><% $title %></h1> +%#already shown <h1><% $title %></h1> <div id="page-navigation"> % my $sep = 0; % my $postsep = 0; diff --git a/rt/share/html/Elements/RT__SavedSearch/ColumnMap b/rt/share/html/Elements/RT__SavedSearch/ColumnMap new file mode 100644 index 000000000..780ee838a --- /dev/null +++ b/rt/share/html/Elements/RT__SavedSearch/ColumnMap @@ -0,0 +1,85 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2010 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<%ARGS> +$Name +$Attr => undef +</%ARGS> +<%ONCE> +my $COLUMN_MAP = { + id => { + title => '#', # loc + attribute => 'id', + align => 'right', + value => sub { return $_[0]->Id }, + }, + Name => { + title => sub { return "foo" }, #'Name', # loc + attribute => 'Name', + value => sub { return $_[0]->Name()||loc("Unnamed search") }, + }, + Query => { + title => 'Query', # loc + attribute => 'Query', + value => sub { return $_[0]->GetParameter('Query') }, + }, + ResultsURL => { + title => '', + attribute => 'ResultsURL', + value => sub { my $search = shift; + return $m->comp('/Elements/QueryString', + map { $_ => $search->GetParameter($_) } + qw(Query Format Rows Order OrderBy PrimaryGroupBy SecondaryGroupBy ChartStyle)); + }, + } +}; + +</%ONCE> +<%INIT> +$m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap', CallbackOnce => 1 ); +return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr ); +</%INIT> diff --git a/rt/share/html/Elements/RT__Ticket/ColumnMap b/rt/share/html/Elements/RT__Ticket/ColumnMap index e347032ff..cd38dcb7b 100644 --- a/rt/share/html/Elements/RT__Ticket/ColumnMap +++ b/rt/share/html/Elements/RT__Ticket/ColumnMap @@ -220,26 +220,31 @@ $COLUMN_MAP = { Starts => { title => 'Starts', # loc attribute => 'Starts', + date => sub { return $_[0]->StartsObj }, value => sub { return $_[0]->StartsObj->AsString } }, Started => { title => 'Started', # loc attribute => 'Started', + date => sub { return $_[0]->StartedObj }, value => sub { return $_[0]->StartedObj->AsString } }, Told => { title => 'Told', # loc attribute => 'Told', + date => sub { return $_[0]->ToldObj }, value => sub { return $_[0]->ToldObj->AsString } }, Due => { title => 'Due', # loc attribute => 'Due', + date => sub { return $_[0]->DueObj }, value => sub { return $_[0]->DueObj->AsString } }, Resolved => { title => 'Resolved', # loc attribute => 'Resolved', + date => sub { return $_[0]->ResolvedObj }, value => sub { return $_[0]->ResolvedObj->AsString } }, UpdateStatus => { @@ -313,8 +318,87 @@ $COLUMN_MAP = { return \$bookmark; }, }, + + Customer => { + title => 'Customer', #loc + attribute => 'Customer.Number', #title/attribute/name... what does it all mean? + value => sub { + my $Ticket = shift; + my @return = (); + foreach my $c (ticket_cust_resolvers($Ticket)) { + push @return, \'<A HREF="', $c->HREF, \'">', + $c->AsString, + \'</A>', + \'<BR>'; + } + pop @return; + @return; + }, + }, + # For future reference: + # hash key = name of the column in the format string + # (see /Search/Elements/BuildFormatString) + # title = displayed name in the table header + # attribute = the field to ORDER BY when sorting on this column + Agent => { + title => 'Agent', + attribute => 'Customer.Agent', + value => sub { + my $Ticket = shift; + my @return = (); + foreach my $c (ticket_cust_resolvers($Ticket)) { + push @return, $c->AgentName, \'<BR>'; + } + pop @return; + @return; + }, + }, + CustomerClass => { + title => 'Class', + attribute => 'Customer.Class', + value => sub { + my $Ticket = shift; + my @return = (); + foreach my $c (ticket_cust_resolvers($Ticket)) { + push @return, $c->CustomerClass, \'<BR>'; + } + pop @return; + @return; + }, + }, + CustomerTags => { + title => '', + attribute => '', + value => sub { + my $Ticket = shift; + my @return = (); + foreach my $c (ticket_cust_resolvers($Ticket)) { + my @tags = sort { $a->{'name'} cmp $b->{'name'} } + $c->CustomerTags; + foreach my $t (@tags) { + push @return, \'<SPAN style="background-color:#', + $t->{'color'}, + \';"> ', + $t->{'name'}, + \' </SPAN>', + \' ' + ; + } + pop @return; + push @return, \'<BR>'; + } + pop @return; + @return; + }, + }, }; +sub ticket_cust_resolvers { + my $Ticket = shift; + my @Customers = @{ $Ticket->Customers->ItemsArrayRef }; + return map $_->TargetURI->Resolver, @Customers; +} + # if no GPG support, then KeyOwnerName and KeyRequestors fall back to the regular # versions if (RT->Config->Get('GnuPG')->{'Enable'}) { diff --git a/rt/share/html/Elements/SavedSearches b/rt/share/html/Elements/SavedSearches new file mode 100644 index 000000000..96d589fc1 --- /dev/null +++ b/rt/share/html/Elements/SavedSearches @@ -0,0 +1,70 @@ +<& /Elements/ListActions, actions => \@results &> +<table width="100%"><tr> +% foreach my $type ('Ticket', 'Chart') { +<td width="50%"> +<&|/Widgets/TitleBox, title => loc('Saved '.$titles{$type}) &> +% foreach my $Object (@Objects) { +% $SavedSearches = RT::SavedSearches->new($session{CurrentUser}); +% $SavedSearches->LimitToPrivacy(join('-',ref($Object),$Object->Id), $type); +% my $title = $titles{$type}; +% if (ref $Object eq 'RT::User' && $Object->Id == $session{CurrentUser}->Id) { +% $title = loc("My saved ".lc($title)); +% } else { +% $title = loc("[_1]'s saved ".lc($title),$Object->Name); +% } +% $title = $m->interp->apply_escapes($title, 'h'); +% +% my $oid = join('-', ref($Object), $Object->Id, 'SavedSearch', '__id__'); +% my $resultpath = $paths{$type}; +% my @cols = ( +% qq{<a href="__WebPath__/$resultpath?__ResultsURL__">__Name__</a>/TITLE:$title}, +% '__Query__', +% qq{<a href="__WebPath__/Search/Build.html?SavedSearchLoad=$oid">[Edit]</a> } . +% qq{<a href="$uri?Delete=$oid">[Delete]</a>/TITLE:}, +% ); +% my $format = join(',', map { "'$_'" } @cols); +<& /Elements/CollectionList, + %ARGS, + Class => 'RT::SavedSearch', + Format => $format, + Collection => $SavedSearches, + PassArguments => [qw(Format Name id)], +&> +% } #foreach $Object +</&> +</td> +% } #foreach $type +</tr></table> +<%init> +my @Objects; +my $SavedSearches = RT::SavedSearches->new($session{'CurrentUser'}); +push @Objects, $SavedSearches->_PrivacyObjects; +push @Objects, RT::System->new( $session{'CurrentUser'} ) + if $session{'CurrentUser'}->HasRight( Object=> $RT::System, + Right => 'SuperUser' ); + +my $uri = '__WebPath__'.$m->request_path; + +my @results; +if ( $Delete =~ /(.*)-SavedSearch-(\d+)/) { + my ($privacy, $id) = ($1, $2); + my $record = RT::SavedSearch->new($session{'CurrentUser'}); + $record->Load($privacy, $id); + if ( $record->Id ) { + my ($status, $msg) = $record->Delete; + push @results, $msg; + } + else { + push @results, "Saved search #$Delete not found"; + } +} + +my %titles = ( 'Ticket' => 'Searches', 'Chart' => 'Charts' ); +my %paths = ( 'Ticket' => 'Search/Results.html', + 'Chart' => 'Search/Chart.html', +); +</%init> +<%ARGS> +$user_attrs => undef +$Delete => undef +</%ARGS> diff --git a/rt/share/html/Elements/SelectCustomerAgent b/rt/share/html/Elements/SelectCustomerAgent new file mode 100644 index 000000000..75a1fba63 --- /dev/null +++ b/rt/share/html/Elements/SelectCustomerAgent @@ -0,0 +1,17 @@ +% return if ($RT::URI::freeside::IntegrationType ne 'Internal'); +<select name="<%$Name%>"> +% if ($ShowNullOption) { + <option value="">-</option> +% } +% for my $agent (qsearch('agent', {'disabled' => ''})) { + <option value="<%$agent->agentnum%>" <% + $agent->agentnum == $Default||'' ? 'selected' : ''%> + ><%$agent->agent%></option> +% } +</select> +<%init></%init> +<%args> +$ShowNullOption => 1 +$Name => undef +$Default => 0 +</%args> diff --git a/rt/share/html/Elements/SelectCustomerClass b/rt/share/html/Elements/SelectCustomerClass new file mode 100644 index 000000000..1a03cba8f --- /dev/null +++ b/rt/share/html/Elements/SelectCustomerClass @@ -0,0 +1,17 @@ +% return if ($RT::URI::freeside::IntegrationType ne 'Internal'); +<select name="<%$Name%>"> +% if ($ShowNullOption) { + <option value="">-</option> +% } +% for my $class (qsearch('cust_class', {'disabled' => ''})) { + <option value="<%$class->classnum%>" <% + $class->classnum == $Default||'' ? 'selected' : ''%> + ><%$class->classname%></option> +% } +</select> +<%init></%init> +<%args> +$ShowNullOption => 1 +$Name => undef +$Default => 0 +</%args> diff --git a/rt/share/html/Elements/SelectCustomerTag b/rt/share/html/Elements/SelectCustomerTag new file mode 100644 index 000000000..862766966 --- /dev/null +++ b/rt/share/html/Elements/SelectCustomerTag @@ -0,0 +1,17 @@ +% return if ($RT::URI::freeside::IntegrationType ne 'Internal'); +<select name="<%$Name%>"> +% if ($ShowNullOption) { + <option value="">-</option> +% } +% for my $tag (qsearch('part_tag', {'disabled' => ''})) { + <option value="<%$tag->tagnum%>" <% + $tag->tagnum == $Default||'' ? 'selected' : ''%> + ><%$tag->tagname%></option> +% } +</select> +<%init></%init> +<%args> +$ShowNullOption => 1 +$Name => undef +$Default => 0 +</%args> diff --git a/rt/share/html/Elements/SelectDate b/rt/share/html/Elements/SelectDate index df4dc2b08..5bdbceeeb 100755 --- a/rt/share/html/Elements/SelectDate +++ b/rt/share/html/Elements/SelectDate @@ -45,10 +45,21 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<script type="text/javascript"><!-- - onLoadHook('createCalendarLink("<% $Name %>");'); ---></script> +%# in PageLayout instead, once <% include('/elements/init_calendar.html') |n %> <input type="text" id="<% $Name %>" name="<% $Name %>" value="<% $Value %>" size="<% $Size %>" /> +<IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $Name %>_date_button" STYLE="cursor: pointer" TITLE="Select date"> +<script type="text/javascript"> +Calendar.setup({ + inputField: <% $Name |n,js_string %>, +% if ( defined($ShowTime) && $ShowTime ) { + ifFormat: "%Y-%m-%d %H:%M", + showsTime: true, +% } else { + ifFormat: "%Y-%m-%d", +% } + button: <% $Name.'_date_button' |n,js_string %>, +}); +</script> <%init> unless ((defined $Default) or ($current <= 0)) { diff --git a/rt/share/html/Elements/SelectQueue b/rt/share/html/Elements/SelectQueue index acd73ae56..0cfdd915d 100755 --- a/rt/share/html/Elements/SelectQueue +++ b/rt/share/html/Elements/SelectQueue @@ -55,7 +55,7 @@ % if ($ShowNullOption) { <option value="">-</option> % } -% for my $queue (@{$session{$cache_key}}) { +% for my $queue (@{$session{$cache_key}{queues}}) { <option value="<% ($NamedValues ? $queue->{Name} : $queue->{Id}) %>" % if ($queue->{Id} eq ($Default||'') || $queue->{Name} eq ($Default||'')) { @@ -90,18 +90,27 @@ my $cache_key = "SelectQueue---" . $session{'CurrentUser'}->Id . "---$CheckQueueRight---$ShowAllQueues"; -if (not defined $session{$cache_key} and not $Lite) { +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 = new RT::Queues($session{'CurrentUser'}); $q->UnLimit; - + while (my $queue = $q->Next) { if ($ShowAllQueues || $queue->CurrentUserHasRight($CheckQueueRight)) { - push @{$session{$cache_key}}, { + push @{$session{$cache_key}{queues}}, { Id => $queue->Id, Name => $queue->Name, Description => $queue->Description, }; } } + $session{$cache_key}{lastupdated} = time(); } </%init> diff --git a/rt/share/html/Elements/ShowCustomFieldDate b/rt/share/html/Elements/ShowCustomFieldDate new file mode 100644 index 000000000..4e8ad676c --- /dev/null +++ b/rt/share/html/Elements/ShowCustomFieldDate @@ -0,0 +1,57 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<%INIT> + my $content = $Object->Content; + my $DateObj = new RT::Date ( $session{'CurrentUser'} ); + $DateObj->Set( Format => 'ISO', Value => $content ); + $content = $DateObj->AsString; +</%INIT> +<%$content|n%> +<%ARGS> +$Object +</%ARGS> diff --git a/rt/share/html/Elements/ShowCustomFieldTimeValue b/rt/share/html/Elements/ShowCustomFieldTimeValue new file mode 100644 index 000000000..16d26df60 --- /dev/null +++ b/rt/share/html/Elements/ShowCustomFieldTimeValue @@ -0,0 +1,4 @@ +<%$Object->Content|n%> min +<%ARGS> +$Object +</%ARGS> diff --git a/rt/share/html/Elements/ShowLink_Checklist b/rt/share/html/Elements/ShowLink_Checklist new file mode 100644 index 000000000..945305fb6 --- /dev/null +++ b/rt/share/html/Elements/ShowLink_Checklist @@ -0,0 +1,36 @@ +<a href="<%$URI->Resolver->HREF%>"> +% if ($URI->IsLocal) { +% my $member = $URI->Object; +% if (UNIVERSAL::isa($member, "RT::Ticket")) { +% my $inactive = 0; #$member->QueueObj->IsInactiveStatus($member->Status); + +<span class="<% $inactive ? 'ticket-inactive' : '' %>"> +<IMG SRC="<%$fsurl%>images/<% $status2image{$member->Status} %>.png" BORDER=0> +<%$member->Id%>: (<%$member->OwnerObj->Name%>) <%$member->Subject%> +%# [<% loc($member->Status) %>] +</span> + +% } elsif ( UNIVERSAL::can($member, 'Name')) { +<%$URI->Resolver->AsString%>: <%$member->Name%> +% } else { +<%$URI->Resolver->AsString%> +% } +% } else { +<%$URI->Resolver->AsString%> +% } +</a> +<%ARGS> +$URI => undef +</%ARGS> +<%once> + +my %status2image = ( + 'new' => 'square_add', #'bullet_add', + 'open' => 'square', #'bullet_black', + 'stalled' => 'error', + 'resolved' => 'tick', + 'rejected' => 'cross', + #'deleted' => 'delete', +); + +</%once> diff --git a/rt/share/html/Elements/ShowUserVerbose b/rt/share/html/Elements/ShowUserVerbose index b88aaf369..6a85a5b6f 100644 --- a/rt/share/html/Elements/ShowUserVerbose +++ b/rt/share/html/Elements/ShowUserVerbose @@ -46,7 +46,11 @@ %# %# END BPS TAGGED BLOCK }}} %# Released under the terms of version 2 of the GNU Public License -<%$Address->format%>\ +% if ( $Address->phrase || $Address->comment ) { +<% sprintf q{%s <%s> %s}, map $Address->$_, qw( phrase address comment ) %> +% } else { +<% $Address->address %> +% } <%INIT> my ($phrase, $address, $comment); diff --git a/rt/share/html/NoAuth/Calendar/dhandler b/rt/share/html/NoAuth/Calendar/dhandler new file mode 100644 index 000000000..4b4aa631e --- /dev/null +++ b/rt/share/html/NoAuth/Calendar/dhandler @@ -0,0 +1,159 @@ +<%init> + +use Data::ICal; +use Data::ICal::Entry::Todo; +use Data::ICal::Entry::Event; +use Date::ICal; + +$RT::ICalTicketType ||= "Data::ICal::Entry::Todo"; +$RT::ICalReminderType ||= "Data::ICal::Entry::Event"; + +my ($UserId, $SearchId, $MagicNumber); +my $arg = $m->dhandler_arg; + +if ($arg =~ m{^(\d+)@(\d+)/(.*)$}) { + $UserId = $1; + $SearchId = $2; + $MagicNumber = $3; +} elsif ($arg =~ m{^(\d+)/(.*)}) { + $UserId = $1; + $MagicNumber = $2; +} else { + Abort("Corrupted URL."); +} + +my $CurrentUser = new RT::CurrentUser(); +$CurrentUser->LoadById($UserId); +my $user = $CurrentUser->Name; + +# if no user, abort +unless ($CurrentUser->Id) { + $RT::Logger->error("No such user id $UserId from $ENV{'REMOTE_ADDR'}"); + $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n"); + $m->abort; +} + +# verify user has LoadSavedSearch right +if ($SearchId and not $CurrentUser->HasRight( Right => 'LoadSavedSearch', + Object=> $RT::System )) { + $RT::Logger->error("not enough rights for user $user from $ENV{'REMOTE_ADDR'}"); + $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n"); + $m->abort; +} + + +# if MagicNumber doesn't match the one stored in database, abort +my $Search; +my $ICalAttribute; +if ($SearchId) { + $Search = $CurrentUser->Attributes->WithId($SearchId); + $ICalAttribute = $Search->FirstAttribute('ICalURL'); +} else { + $ICalAttribute = $CurrentUser->UserObj->FirstAttribute('ICalURL'); +} + +unless ($ICalAttribute) { + $RT::Logger->error("No such ICal feed for $user from $ENV{'REMOTE_ADDR'}"); + $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n"); + $m->abort; +} + + +if ($MagicNumber ne $ICalAttribute->Content) { + $RT::Logger->error("FAILED LOGIN for $user from $ENV{'REMOTE_ADDR'}"); + $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n"); + $m->abort; +} + +my $Tickets = RT::Tickets->new($CurrentUser); + +my $Query = "( Status = 'new' OR Status = 'open' OR Status = 'stalled') + AND ( Owner = '" . $CurrentUser->Id ."' OR Owner = 'Nobody' ) + AND ( Type = 'reminder' OR 'Type' = 'ticket' )"; + +$Query = $Search->SubValue('Query') + if $Search; + +$Query .= " AND ( Due > '1970-01-01' OR Starts > '1970-01-01' )"; + +$Tickets->FromSQL($Query); + +$Tickets->OrderBy(FIELD => 'Due', ORDER => 'ASC'); + +my $calendar = Data::ICal->new(); + +my ($uid) = $RT::WebURL =~ m{https?://([^:]+)}; + +while (my $Ticket = $Tickets->Next ) { + + my $event; + if ($Ticket->Type eq 'ticket') { + $event = add_todo($Ticket, $uid); + } else { + $event = add_event($Ticket, $uid); + } + next unless $event; + $calendar->add_entry($event); +} + +my $cal = $calendar->as_string; + +$r->content_type('text/calendar;charset=utf-8'); +$m->clear_buffer(); +$m->out($cal); +$m->abort; + +sub add_event { + my ($Reminder, $uid) = @_; + + return unless defined $Reminder->RefersTo->First; + my $Ticket = $Reminder->RefersTo->First->TargetObj; + + my %event = ( + summary => $Reminder->Subject ? $Reminder->Subject : '', + url => "${RT::WebURL}/Ticket/Display.html?id=" . $Ticket->id, + uid => Date::ICal->new( epoch => time() )->ical() . "-" . $Reminder->Id . "@" . $uid, + categories => $Ticket->QueueObj->Name, + dtstart => Date::ICal->new( epoch => $Reminder->DueObj->Unix )->ical, + ); + + my $event = $RT::ICalReminderType->new(); + $event->add_properties(%event); + + return $event; +} + +sub add_todo { + my ($Ticket, $uid) = @_; + + my %vtodo = ( + summary => $Ticket->Subject ? $Ticket->Subject : '', + dtstart => Date::ICal->new( epoch => $Ticket->CreatedObj->Unix )->ical, + url => "${RT::WebURL}/Ticket/Display.html?id=" . $Ticket->id, + uid => Date::ICal->new( epoch => time() )->ical() . "-" . $Ticket->Id . "@" . $uid, + categories => $Ticket->QueueObj->Name, + ); + + $vtodo{due} = Date::ICal->new( epoch => $Ticket->DueObj->Unix )->ical, + if $Ticket->DueObj; + + if ($Ticket->OwnerObj->Id != $RT::Nobody->Id and $Ticket->OwnerObj->EmailAddress) { + $vtodo{organizer} = "MAILTO:" . $Ticket->OwnerObj->EmailAddress; + $vtodo{attendee} = "MAILTO:" . $Ticket->OwnerObj->EmailAddress; + } elsif ($Ticket->QueueObj->CommentAddress) { + $vtodo{organizer} = "MAILTO:" . $Ticket->QueueObj->CommentAddress; + $vtodo{attendee} = "MAILTO:" . $Ticket->QueueObj->CommentAddress; + } + + $vtodo{priority} = $Ticket->Priority + if $Ticket->Priority; + + my $vtodo = $RT::ICalTicketType->new(); + $vtodo->add_properties(%vtodo); + + return $vtodo; +} + + + +</%init> diff --git a/rt/share/html/NoAuth/css/calendar.css b/rt/share/html/NoAuth/css/calendar.css new file mode 100644 index 000000000..c6b584e96 --- /dev/null +++ b/rt/share/html/NoAuth/css/calendar.css @@ -0,0 +1,75 @@ +.tooltip{position:relative;z-index:1;} +.tooltip:hover{z-index:5;color:#000;} +.tooltip span.tip{display: none; text-align:left;} + +div.tooltip:hover span.tip{ +display:block; +position:absolute; +top:12px; left:24px; width:350px; +border:1px solid #555; +background-color:#fff; +padding: 4px; +font-size: 0.8em; +color:#505050; +} + +.calendardate { + text-align: right; + background-color: #f8f8ff; + width:100%; +} + +.offmonthcalendardate { + text-align: right; + background-color: #f8f8f8; + width:100%; +} + +.todayscalendardate { + text-align: right; + background-color: #fc6; /*#fad163*/ + width:100%; +} + +table.rtxcalendar { + width:100%; + border-collapse: collapse; + border: 1px solid #d0d0d0; + margin-bottom: 6px; +} + +table.rtxcalendar td { + border: 1px solid #d7d7d7; + background: #fff; + vertical-align: top; + width: 14%; +} + +table.rtxcalendar th { + border: 1px solid #d7d7d7; + background: #eef; +} +table.rtxcalendar tbody th { + border: 1px solid #d7d7d7; + background: #eee; + font-weight: normal; +} + +table.rtxcalendar td.offmonth { + background: #f8f8f8; + color: #aaa; +} + +table.rtxcalendar td.today { + background: #ffe; /*#fed;*/ + border: 1px solid #fc6; +} + +table.rtxcalendar td.yesterday { + border-right: none; +} + +table.rtxcalendar td.aweekago { + border-bottom: none; +} + diff --git a/rt/share/html/NoAuth/css/freeside2.1/InHeader b/rt/share/html/NoAuth/css/freeside2.1/InHeader new file mode 100644 index 000000000..904535fbd --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/InHeader @@ -0,0 +1,54 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<!--[if lt IE 8]> +<link rel="stylesheet" href="<%RT->Config->Get('WebPath')%>/NoAuth/css/freeside2.1/msie.css" type="text/css" media="all" /> + +<![endif]--> +<!--[if lt IE 7]> +<link rel="stylesheet" href="<%RT->Config->Get('WebPath')%>/NoAuth/css/freeside2.1/msie6.css" type="text/css" media="all" /> +<![endif]--> diff --git a/rt/share/html/NoAuth/css/freeside2.1/admin.css b/rt/share/html/NoAuth/css/freeside2.1/admin.css new file mode 100644 index 000000000..63385bffa --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/admin.css @@ -0,0 +1,60 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +ul.list-menu .menu-item { + font-size: 1.25em; +} +ul.list-menu { + list-style: none; + +} +ul.list-menu .description { + display: block; + padding: 0.5em; + font-style: italic; + padding-left: 1em; +} diff --git a/rt/share/html/NoAuth/css/freeside2.1/base.css b/rt/share/html/NoAuth/css/freeside2.1/base.css new file mode 100644 index 000000000..a38854fb5 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/base.css @@ -0,0 +1,63 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +a { + color: #000; + text-decoration: none; +} + + +div#body a:visited { + color: #666; + +} + +a:hover { + text-decoration: underline; +} + +textarea:focus, input:focus { background-color: #ffd; } diff --git a/rt/share/html/NoAuth/css/freeside2.1/boxes.css b/rt/share/html/NoAuth/css/freeside2.1/boxes.css new file mode 100644 index 000000000..fbd9af108 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/boxes.css @@ -0,0 +1,192 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +.titlebox { + border-left: 1px solid #ccc; + border-top: 1px solid #ccc; + background-color: #efefef; + padding-top: 1em; + margin-top: 1em; + margin-left: 1em; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; + margin-bottom: 2em; + border-bottom: 2px solid #aaa; + border-right: 2px solid #aaa; + padding-right: 1em; +} + +* html .titlebox { + border-top: none; + border-left: none; +} + +.titlebox .titlebox { + + background-color: #ffffff; + margin-top: 1em; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; + margin-right: 0.25em; + +} + + +.titlebox { + margin-left: 0em; + margin-right: 0em; + min-height: 1.25em; + +} + + + +.titlebox .titlebox-title { + position: relative; + margin-top: -1.5em; + padding-bottom: 0.25em; + padding-left: 1em; + margin-right: -1em; + +} + +.titlebox .titlebox-title a { + text-decoration: none; + color: black; + +} + +.titlebox .titlebox-title a:hover { + text-decoration: underline; + +} + +.titlebox .titlebox-title a:visited { + color: #fff; +} + +.titlebox .titlebox-title .left { + font-weight: bold; + background: #ccc; + margin-left: 0.75em; + padding:0.5em; + padding-left: 0.75em; + padding-right: 0.75em; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; + border-bottom: 2px solid #aaa; + border-right: 2px solid #aaa; + + +} + +.titlebox .titlebox-title .right-empty { + display:none; +} + +.titlebox .titlebox-title .right { + position: absolute; + right: 0; + top: 0.5em; + font-size: 0.9em; + background: #dedede; + border-left: 1px solid #ccc; + border-bottom: 1px solid #ccc; + padding-right: 0.4em; + padding-left: 0.4em; + padding-bottom: 0.2em; + padding-top: 0.5em; + -moz-border-radius-bottomleft: 0.25em; + -webkit-border-bottom-left-radius: 0.25em; + + + -moz-border-radius-topright: 0.25em; + -webkit-border-top-right-radius: 0.25em; + +} + +.titlebox .titlebox-title .right a { + color: #000; +} + +.titlebox .titlebox-content { + padding-top: 0.5em; + padding-left: 1em; + padding-bottom: 1em; + +} + +.titlebox .titlebox-title .widget a { + display: block; + margin: 0; + margin-top: 0.5em; + width: 20px; + + background: url(<%RT->Config->Get('WebPath')%>/NoAuth/images/css/rollup-arrow.gif) no-repeat center center; + + position: absolute; + top: -1em; + left: 0.15em; + float: left; + + padding: 11px 0 0 0; + overflow: hidden; +} + +* html .titlebox .titlebox-title .widget a { + background-position: center 0.3em; + top: 0em; + left: -1.5em; +} + +.titlebox.rolled-up .titlebox-title .widget a { + background-image: url(<%RT->Config->Get('WebPath')%>/NoAuth/images/css/rolldown-arrow.gif); +} + +.titlebox hr.clear { + display: none; +} diff --git a/rt/share/html/NoAuth/css/freeside2.1/collection.css b/rt/share/html/NoAuth/css/freeside2.1/collection.css new file mode 100644 index 000000000..cbc8cefd7 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/collection.css @@ -0,0 +1,52 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} + +table.collection td:first-child, table.collection th:first-child { + padding-left: 1em; +} + diff --git a/rt/share/html/NoAuth/css/freeside2.1/forms.css b/rt/share/html/NoAuth/css/freeside2.1/forms.css new file mode 100755 index 000000000..8afedcb03 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/forms.css @@ -0,0 +1,242 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +form { + + background: none; + border: none; + margin: 0; +} + + +.input-row .label { + position: relative; + text-align: right; + width: 4em; +} + +.input-row .input { + position: relative; + left: 1em; + width: 10em; + text-align: right; +} + +.value { + font-size: 0.85em; + +} + + + +div.button-row { + text-align: right; + padding-right: 0.5em; +} + + +input[type=reset], input[type=submit], input[class=button] { + color: #fff; + background: #3858a3; + padding: 0.25em; + padding-left: 0.5em; + padding-right: 0.5em; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; +} + +input.button:hover, button:hover, input[type=reset]:hover, input[type=submit]:hover, input[class=button]:hover { + background: #1D3B7D; +} + +input.button:focus, button:focus, input[type=reset]:focus, input[type=submit]:focus, input[class=button]:focus { + background: #1D3B7D; +} + +div.error div.error { + border: 2px solid #aa0000; + border-top: 1px solid #bb0000; + border-left: 1px solid #bb0000; + background-color: #fcc; +} + +div.error .titlebox-title span.left { + background-color: #f00; + color: #fff; + border: 1px solid #cc0000; + border-right: 2px solid #bb0000; + border-bottom: 2px solid #bb0000; + +} + + +div.results .titlebox-title .left, div.results .titlebox { + border: 1px solid #aa9; + border-bottom: 2px solid #990; + border-right: 2px solid #990; +} + +div.results .titlebox-title .left { + background: #ff9; + +} + +div.results .titlebox { + background: #ffc; + +} + +div.results .titlebox-content { + padding: 0; +} + + +.label, .labeltop { + text-align: right; + font-size: 0.8em; + padding-right: .5em; + +} + +.cflabel { + text-align: right; + font-size: 0.8em; + padding-right: .5em; + width: 25%; +} + +.labeltop, .label, .value { + padding-top: 0.25em; +} + +div.ticket-info-basics div.titlebox-content .labeltop{ + width: 10em; +} + +div.submit { + text-align: right; +} + +div.submit .extra-buttons { + text-align: left; +} + + +div.widget { + padding-bottom: 0.5em; +} + +div.widget .label { + text-align: right; + display: block; + width: 15em; + float: left; + clear: both; + font-size: 0.9em; + padding-right: 0.5em; +} + +div.widget .hints { + + display: block; + padding-left: 14em; + font-style: italic; +} + + +%# ComboBox styles... some properties like height and width must be dynamically +%# set in the JS (at least for now). +.combobox { + position: relative; + width: 11.5em; +} + +.combobox .combo-button { + right: 0; + padding: 0; + margin-top: 0; + cursor: default; + color: ButtonFace; + background: ButtonFace; + border: 2px outset ButtonHighlight; +} + +/* this style replaces the default down-triangle with one that looks more like + * native widget sets. It does not work in IE as it's an :after pseudo element + * with a "content" value. but that's ok because IE can't display unicode 25be + * anyway */ + +.combobox .combo-button:after { + color: ButtonText; + margin: 0; + padding: 0; + margin-top: -0.5em; + margin-left: -0.8em; + content: "\25be"; +} + +.combobox .combo-text { + border: 1px inset ButtonHighlight; + margin: 0; + padding: 0; +} + +.combobox .combo-list { + border: 1px outset; + z-index: 150; +} + +.value .TimeUnits{ + margin-left: .5em; + width: 7em; +} + +.cfinvalidfield { + font-style: italic; + color: red; +} + diff --git a/rt/share/html/NoAuth/css/freeside2.1/freeside.css b/rt/share/html/NoAuth/css/freeside2.1/freeside.css new file mode 100644 index 000000000..6e5f3b576 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/freeside.css @@ -0,0 +1,9 @@ + +%# div.titlebox { +%# background: #d4d4d4; +%# } +%# +%# div.titlebox-title { +%# background: #e8e8e8; +%# } + diff --git a/rt/share/html/NoAuth/css/freeside2.1/images/dhandler b/rt/share/html/NoAuth/css/freeside2.1/images/dhandler new file mode 100644 index 000000000..6ec9dea05 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/images/dhandler @@ -0,0 +1,8 @@ +<%INIT> +use File::Basename; +my $arg = $m->dhandler_arg; +my $file = dirname($m->current_comp->source_file) . '/source/'. $arg; +RT::Interface::Web->SendStaticFile( File => $file ); + +$m->abort; +</%INIT> diff --git a/rt/share/html/NoAuth/css/freeside2.1/images/source/background-gradient.png b/rt/share/html/NoAuth/css/freeside2.1/images/source/background-gradient.png Binary files differnew file mode 100644 index 000000000..9c126c7e3 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/images/source/background-gradient.png diff --git a/rt/share/html/NoAuth/css/freeside2.1/layout.css b/rt/share/html/NoAuth/css/freeside2.1/layout.css new file mode 100644 index 000000000..0e7912d98 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/layout.css @@ -0,0 +1,237 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +/* body */ + +body { + + + padding:0; + margin:0; + + /*background: #547CCC url(<%RT->Config->Get('WebPath')%>/NoAuth/css/freeside2.1/images/background-gradient.png) top left repeat-x ; */ + background: #f8f8f8; + font-family: arial, helvetica, sans-serif; + + color: #000000; +} + +div#body { + position: relative; + padding: 1em; + padding-top: 1.8em; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; + /* margin-left: 10.5em; */ + /* margin-top: 5.2em; */ + margin-left: .5em; + margin-top: 3.0em; + margin-right: 1em; + margin-bottom: 0em; + min-height: 10%; + background: #fff; + border-top: 2px solid #ccc; + border-left: 2px solid #ccc; + z-index:1; + + +} + +#topactions { + position: absolute; + background: transparent; + top: 3.8em; + right: 1em; + width: auto; + min-width: 42em; + font-size: 0.9em; + z-index: 99; +} + +#topactions form * { + vertical-align: top; +} + +#topactions button, #topactions select, #topactions input{ + padding-top: 0em; + padding-bottom: 0em; + width: 8em; + +} + +#topactions form { + display: block; + +} + +#topactions #CreateTicketInQueue { + text-align: right; + +} +#topactions #simple-search { + float: right; +} + +#topactions #simple-search .field{ + margin-left: 1em; + color: #787; + } + +#topactions #simple-search .field:focus { + color: #000; + } + +#topactions #GotoTicket { + text-align: right; + +} + +div#footer { + position: absolute; + right: 0; + text-align: right; + font-size: 0.9em; + margin-top: 2em; + background: #fff; + margin-bottom: 0; + padding-left: 3em; + padding-right: 1em; + + + + + + border-top: 2px solid #aaa; + border-left: 2px solid #aaa; + + + + -moz-border-radius-topleft: 0.5em; + -webkit-border-top-left-radius: 0.5em; + -moz-border-radius-bottomleft: 0.5em; + -webkit-border-bottom-left-radius: 0.5em; +} + +div#footer #time { +display: none ; +} + +div#footer #bpscredits { + text-align: right; + background: url(<%RT->Config->Get('WebPath')%>/NoAuth/images//bplogo.gif) no-repeat top right; + padding-top: 4em; +} + + +/* logo stuff */ + +div#logo { +} + + +div#logo a { + display: none; + position: absolute; + left: 0; + bottom: 0; +} +div#logo a img { + border: 0; +} +div#logo .rtname { + position: absolute; + font-weight: bold; + top: 1em; + left: 1em; +} + + +div#quickbar, div#logo { + font-size: 0.9em; +} +div#quickbar a, div#logo a { + color: #000; +} + + +div#quickbar { + background: #eaeaea; + padding-top: 1em; + padding-left: 1em; + padding-bottom: 0.5em; + height: 1em; + border-bottom: 1px solid #ccc; + +} +div#quick-personal { + float: right; + margin-right: 1em; +} + + +div#header h1 { + position: absolute; + left: 7.25em; + right: 20em; + overflow: hidden; + height: 1em; + font-size: 1.4em; + margin-top: 0.4em; + padding: 0.25em; + color: #fff; +} + +/* in multi-column layouts, make sure we have an internal gutter */ + +tr .boxcontainer { + padding-right: 1em; +} + +tr .boxcontainer:last-child { + padding-right: 0; +} + diff --git a/rt/share/html/NoAuth/css/freeside2.1/login.css b/rt/share/html/NoAuth/css/freeside2.1/login.css new file mode 100644 index 000000000..2eb423876 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/login.css @@ -0,0 +1,82 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +#login-box hr { + display: none; +} + +#login-box { + + width: 30em; + +margin-right:auto;margin-left:auto; + padding-top: 2em; + padding-bottom: 2em; + + +} + + +#login-box .input-row { + position: relative; + height: 1.5em; + padding-top: 1em; +} + +#login-box .input-row .label { + + float: left; + width: 8em; + text-align: right; + font-weight: bold; + + +} + +#login-box .button-row { + margin-top: 0.5em; +} diff --git a/rt/share/html/NoAuth/css/freeside2.1/main.css b/rt/share/html/NoAuth/css/freeside2.1/main.css new file mode 100644 index 000000000..69e7f44e2 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/main.css @@ -0,0 +1,71 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +% $m->callback(CallbackName => 'Begin'); + +@import "yui-fonts.css"; +@import "base.css"; + +@import "layout.css"; +@import "nav.css"; +@import "forms.css"; +@import "boxes.css"; + +@import "login.css"; +@import "ticket-lists.css"; +@import "ticket-search.css"; +@import "portlets.css"; +@import "ticket.css"; +@import "tools.css"; +@import "admin.css"; +@import "collection.css"; +@import "misc.css"; + +@import "freeside.css"; + +% $m->callback(CallbackName => 'End'); + diff --git a/rt/share/html/NoAuth/css/freeside2.1/misc.css b/rt/share/html/NoAuth/css/freeside2.1/misc.css new file mode 100644 index 000000000..80d7ce0b4 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/misc.css @@ -0,0 +1,87 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} + +@import "../base/misc.css"; + +#body.calpopup { + margin-left: 1em; + margin-top: 1em; +} + +#body.calpopup a.today { + font-size: 1em; + font-weight: bold; +} + +#body.calpopup a { + font-size: 0.8em; +} + +.calendar { + text-align: center; + margin: 0 0 0 0; +} + +.calendar td, .calendar th { padding: 0.1em 0.1em 0.1em 0.1em; } + +.calendar caption .month { + padding: 0 0.25em 0 0.25em; + font-size: 1.5em; +} + +.comment { + padding-left: 0.5em; + color: #999; + +} + +#comp-Ticket-ShowEmailRecord #body { + margin-left: 1em; + margin-top: 1em; + overflow: auto; +} diff --git a/rt/share/html/NoAuth/css/freeside2.1/msie.css b/rt/share/html/NoAuth/css/freeside2.1/msie.css new file mode 100644 index 000000000..2297c304a --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/msie.css @@ -0,0 +1,246 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +div#body { + left: 0.25em; + height: 100%; + top: 1em; + +} + + +#footer { + padding: 1em; +} + + +div#header h1 { + position: absolute; + left: 7.25em; + overflow: hidden; + height: 1em; + font-size: 1.4em; + margin-top: 0.4em; + right: 23.5em; + padding: 0.25em; +} + + +#topactions { + top: 4.1em; + width: auto; +} +.topaction form * { + vertical-align: top; +} + +.topaction form button, .topaction form input { + height: 2em; +} + +.topaction form input.field { + height: 1.6em; +} + +.topaction .select-queue { + margin-top: 0.2em; +} + +div#page-navigation ul#actions-menu { + margin-top: -2.9em; + margin-right: -0.2em; + border-top: 1px solid #ccc; + border-right: none; +} + + +div#page-navigation { + position: absolute; + top: 6.2em; + height: 1.8em; + background: #fff; + border-top: 2px solid #ccc; +} + + + +div#page-navigation ul#page-menu { + margin-top: -2.5em; + margin-left: 4em; + background: none; + border: none; +} + + +div#quickbar { height: 1.2em; + + +} + +#pick-criteria td.label select { + width: 10em; +} + + +#editquery { + margin-top: 0.2em; + width: 39%; + left: 60%; +} + +div#nav li.first { + margin-top: 0.75em; + border-top: none; +} +div#nav ul ul li.first { + border-top: 1px solid #cccccc; + margin-top: 0.25em; +} + +div#nav li.last { + border-bottom: none; + padding-bottom: 0; + margin-bottom: 0; +} + + +.ticket-transaction .type a { font-weight: normal; text-decoration: none; color: #fff; } + + +.titlebox { + border-top: none; + border-left: none; +} + +.titlebox .titlebox-title .left { + padding: 0.25em; + padding-left: 0.5em; +} + +.titlebox { +} + +.titlebox .titlebox-title .right { + border-right: 2px solid #aaa; + +} + + +.titlebox .titlebox-content { + padding-top: 2.2em; +} + +.titlebox table.ticket-list, .titlebox table.queue-summary { + width: 95%; + padding: 0.5em; + margin-left: auto; + margin-right: auto; +} + +th.collection-as-table { + padding: 0.25em; +} + +table.queue-summary td, td.collection-as-table { + padding: 0.25em; +} + + .titlebox-title { + position: relative; +} + +.titlebox-title .widget { + position: absolute; + top: -0.25em; + left: -0.25em; + +} +.titlebox-title .left { + position: absolute; + top: -0.75em; + left: 0.5em; +} + + +.titlebox .titlebox-title .right{ + top: 0.2em; + right: -0.2em; +} + +/* nested things. like the ticket dates tab */ +.titlebox .titlebox .titlebox-title .right{ + top: 0.25em; +} + +.combobox { + float: left; +} + +.combobox .combo-button { + color: ButtonText; + padding: 0; +} + +.combobox .combo-list { + margin-top:0.5em; + margin-left: -0.2em; +} + +#pick-criteria td.label { + width: auto; +} + +#pick-criteria td.operator { + width: 7.5em; +} + +.plain-text-white-space { + word-wrap: break-word; /* Internet Explorer 5.5+ */ + white-space: pre; /* IE only hack to re-specify in addition to + word-wrap */ +} + diff --git a/rt/share/html/NoAuth/css/freeside2.1/msie6.css b/rt/share/html/NoAuth/css/freeside2.1/msie6.css new file mode 100644 index 000000000..bf6b1ed6d --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/msie6.css @@ -0,0 +1,88 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +.topaction .select-queue { + margin-top: 0; +} + +div#page-navigation ul#page-menu { + margin-top: -3.2em; +} + +.titlebox-title .widget { + top: -1em; + left: 0.5em; + +} +.titlebox .titlebox-title .right{ + position: absolute; + top: 0.25em; + right: 1em; +} + +/* nested things. like the ticket dates tab */ +.titlebox .titlebox .titlebox-title .right{ + right: 1.3em; +} + +#login-box .titlebox .titlebox-title .right { + margin-top: -0.1em; + right: 0em; +} + +.titlebox +{ + height: auto !important; + height: 1.25em; +} + + +.ticket-transaction .messagebody img { + /* ie6 does not support max-width */ + width: expression(this.width > 401 ? 400 : true); +} + diff --git a/rt/share/html/NoAuth/css/freeside2.1/nav.css b/rt/share/html/NoAuth/css/freeside2.1/nav.css new file mode 100644 index 000000000..8a52e62c4 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/nav.css @@ -0,0 +1,206 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +div#nav { + position: absolute; + left: 0; + font-size: 0.9em; + top: 3.2em; + width: 10.5em; + background: #fff; + -moz-border-radius-bottomright: 0.5em; + -webkit-border-bottom-right-radius: 0.5em; + border-left: 1px solid #999; +border-top: 1px solid #999; + + -moz-border-radius-topright: 0.5em; + -webkit-border-top-right-radius: 0.5em; + z-index: 99; + + +} + +div#nav ul { + padding-left: 0.75em; + margin-left: 0; + padding-right: 0.75em; + list-style-type: none; +} + +div#nav li:first-child { + border-top: 1px solid #ccc; + padding-top: 0.25em; + +} + +div#nav li { + padding: 0.125em; + padding-bottom: 0.25em; + margin-bottom: 0.25em; + border-bottom: 1px solid #ccc; + padding-left: 0.5em; + margin-right: 0.25em; + margin-left: 0em; +} + +div#nav li li:first-child { + margin-top: 0.25em; +} +div#nav li li { + margin-left: -0.5em; + padding-left: 0.25em; + margin-right: -0.5em; +} + +div#nav li li:last-child { + margin-bottom: 0; + padding-bottom: 0; + border: none; +} + +div#nav .bullet { + display: none; +} + +div#nav .separator { +display: none; +} + + +div#nav a, div#page-navigation a{ + text-decoration: none; + font-weight: normal; + color: #000; +} + +div#nav a:hover, div#page-navigation a:hover { + text-decoration: underline; +} + + + +div#nav a.selected, div#page-navigation a.selected { + font-weight: bold; +} + + +div#nav a.selected:after { +/* content: " > " */ +} + +div#page-navigation { + background: white; + position: relative; + /* width:100%; */ + z-index: 10; + +} + + +div#page-navigation ul { + +} + +div#page-navigation ul#page-menu { + display: block; + /* position: absolute; */ + float: left; + left: 8em; + font-size: 0.9em; + top: 2.3em; + min-height: 1em; + background-color: white; + right: 0em; + padding-top:0.3em; + padding-bottom:0.5em; + padding-right: 4em; + border-top: 1px solid #aaa; + +} + +/* ie hack */ +* html div#page-navigation ul#page-menu { + left: 6.5em; + top: 3.2em; + padding-left: 2em; +} + + +div#page-navigation ul#actions-menu { + /* position: absolute; */ + float: right; + right: 1em; + top: 5.2em; + margin-top: 0em; + padding: 0.25em; + padding-left: 0.5em; + padding-right: 0.5em; + + background: #dedede; + border-left: 1px solid #aaa; + border-bottom: 2px solid #aaa; + -moz-border-radius-bottomleft: 0.5em; + -webkit-border-bottom-left-radius: 0.5em; + -moz-border-radius-topright: 0.25em; + -webkit-border-top-right-radius: 0.25em; + + + +} + + + +div#page-navigation ul li{ + display: inline; + +} + + +ul.page-navigation ul.page-menu { + float: right; +} + diff --git a/rt/share/html/NoAuth/css/freeside2.1/portlets.css b/rt/share/html/NoAuth/css/freeside2.1/portlets.css new file mode 100644 index 000000000..d96d5a9f0 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/portlets.css @@ -0,0 +1,71 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +table.myrt { + width: 100%; +} + +table.dashboard { + width: 100%; + border: 0; +} + +.quick-create .select-queue { + width: 12em; +} + +.quick-create input[type="text"], .quick-create textarea { + width: 100%; + +} + +.reminders blockquote { + margin-top: 0.5em; + margin-bottom: 0.5em; + margin-left: 1em; + margin-right: 1em; +} diff --git a/rt/share/html/NoAuth/css/freeside2.1/ticket-lists.css b/rt/share/html/NoAuth/css/freeside2.1/ticket-lists.css new file mode 100644 index 000000000..799a391e5 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/ticket-lists.css @@ -0,0 +1,172 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +table.ticket-list, table.queue-summary, table.collection { + margin-top: 0.75em; + font-size: 0.9em; + border: 1px solid #aaa; + border-bottom: 2px solid #999; + border-right: 2px solid #999; + + +} + +table.queue-summary tr>*:first-child { + padding-left: 1em; + +} + + +table.queue-summary tr>*:last-child { + padding-right: 1em; + +} + +table.ticket-list a, table.queue-summary a, table.collection a { + font-weight: bold; +} + + +table.ticket-list th.collection-as-table, table.collection th.collection-as-table { + background: #ddd; + font-size: 0.9em; + margin-bottom: 0.5em; + text-align: left; + +} + + +table.queue-summary th.collection-as-table { + font-size: 0.9em; + margin-bottom: 0.5em; + text-align: right; + +} + +table.queue-summary th.collection-as-table:first-child { + text-align: left; + +} + + +tr.collection-as-table+tr.collection-as-table th { + border-bottom: 2px solid grey; + +} + + + + +table.queue-summary td { + background: #efefef; + border-bottom: 1px solid #ccc; +} + + + +tr.evenline td { + background: #eee; +} + +tr.oddline td { + background: #fff; + +} + +tr.evenline td, tr.oddline td { + padding-top: 0.5em; +} + + + +tr.evenline+tr.evenline td, tr.oddline+tr.oddline td{ + padding-top: 0; + border: none; +} + + + +table.ticket-list td:first-child, table.ticket-list th:first-child { + padding-left: 1em; +} + +table.ticket-list td:last-child, table.ticket-list th:last-child { + padding-right: 1em; +} + +th.collection-as-table , td.collection-as-table { + padding-right: 0.5em; +} + +.pagenum.a:hover, .paging a.nav:hover{ +text-decoration: underline; +} + + +.pagenum *, .paging a.nav{ +padding: .5em; +} + +.currentpage{ +text-decoration: none; +font-weight: bold; +background: #eee; +} + +div.paging{ +text-align: center; +padding-bottom: 1em; +} + + +/* full-page ticket lists */ +#body>table.ticket-list { + margin-bottom: 2em; + +} + + diff --git a/rt/share/html/NoAuth/css/freeside2.1/ticket-search.css b/rt/share/html/NoAuth/css/freeside2.1/ticket-search.css new file mode 100644 index 000000000..7a31d3e82 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/ticket-search.css @@ -0,0 +1,199 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +#comp-Search-Build #body { + position: relative; +} + +#pick-criteria select { + width: 8em; +} + +#pick-criteria tr { + height: 1.5em; +} + +#pick-criteria td.label { + font: message-box; + padding-right: 0.5em; + width: 11em; +} + +#pick-criteria td.label * { + width: 8.5em; +} + +#pick-criteria td.label select { + text-align: right; +} + +#pick-criteria td.operator { + padding-right: 0.5em; + text-align: left; + vertical-align: bottom; + width: 7em; +} + +#pick-criteria td.operator select { + text-align: right; +} + +#pick-criteria td.value input, +#pick-criteria td.value select { + width: 10em; +} + +#pick-criteria td.value #ValueOfDate { + width: 6em; +} + + +#pick-criteria td.value #ValueOfTime { + width: 4em; + +} + +#pick-criteria td.value #ValueOfTime-TimeUnits{ + width: 5.5em; +} + +#pick-criteria td.value { + padding-right: 0.5em; + text-align: left; + font: message-box; +} + +#editquery, #editsearches{ + position: absolute; + margin-top: 0.2em; + right: 1em; + left: 60%; + top: 1em; +/* margin-top: -1em; */ +} + +#editquery { + top: 1.3em; +} + + +#editsearches { + top: 24em; +} + + +#pick-criteria { + width: 58%; + padding-top: 0em; + margin-top: 0em; +} + +#pick-criteria .titlebox-content { + overflow-x: auto; +} + +#comp-Search-Build .submit { + width: 58%; +} + + +#sorting.titlebox { + width: 55%; + padding-right: 1em; +} + +#comp-Search-Build #columns { +} + +#display-options .submit { + width: 100%; +} + + + +.search-result-views { + position: absolute; + top: 0; + right: 0; + margin-top: -2px; + margin-right: 0em; + padding: 0.25em; + padding-left: 0.5em; + padding-right: 0.5em; + background-color: #ccc; + border-left: 1px solid #999; + border-bottom: 1px solid #999; + -moz-border-radius-bottomleft: 0.5em; + -webkit-border-bottom-left-radius: 0.5em; +} + + + +.search-result-views li { + + display: inline; +} + +.search-result-views li:after { + content: " \00b7 "; +} + +.search-result-views li:last-child:after { + content: ""; + +} + + +.refresh { + float: left; +} + +/* Force some widget to fit at max parent box */ +#HomeRefreshInterval, #SavedSearchLoad, #SavedSearchOwner { + max-width: 100%; +} + diff --git a/rt/share/html/NoAuth/css/freeside2.1/ticket.css b/rt/share/html/NoAuth/css/freeside2.1/ticket.css new file mode 100644 index 000000000..78477e0d4 --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/ticket.css @@ -0,0 +1,230 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +div#ticket-history div.ticket-transaction { + border-top: 1px solid #ccc; + padding-bottom: 0.25em; + +} + +div#ticket-history div.odd { + background-color: #fff; +} + +div#ticket-history { + + margin-top: 0.75em; + border-left: 1px solid #ccc; + + border-right: 2px solid #999; + border-bottom: 2px solid #999; + +} + +.ticket-transaction div.metadata span.actions { + position: absolute; + right: 2.3em; + padding: 0em; + background: #ccc; + text-align: right; + border-left: 1px solid #999; + border-bottom: 1px solid #999; + color: #ccc; + -moz-border-radius-bottomleft: 0.5em; + -webkit-border-bottom-left-radius: 0.5em; + white-space: nowrap; +} + +.ticket-transaction div.metadata span.type { + text-align: center; + float: left; + margin: 0.25em 0.70em 0.25em 0.25em; + width: 1em; + height: 1.25em; + padding: 0.75em 0 0 0; + border-right: 1px solid #999; + border-bottom: 1px solid #999; + -moz-border-radius: 0.25em; + -webkit-border-bottom-right-radius: 0.25em; +} + +div#ticket-history span.type a { + color: #fff; +} + + +div#ticket-history span.date { + width: 10em; +} + + +div#ticket-history span.description { + margin-left: 1em; + font-weight: bold; +} + +div#ticket-history span.time-taken { + margin-left: 1em; +} + +div#ticket-history div.content { + padding-right: 1em; + padding-bottom: 0.7em; + font-size: 1.1em; + margin-left: 1.5em; +} + +.plain-text-white-space { + white-space: pre-wrap; + font-family: monospace; +} + +.ticket-transaction .messagebody { + font-size: 1em; + padding-left: 1em; + margin-top: 0.5em; + padding-top: 0.5em; + border-top: 1px solid #ccc; + /*overflow: auto; */ + min-height: 2.5em; + /* To avoid overlapping of "downloadattachment" by messagebody */ + clear: left; +} + +.ticket-transaction .messagebody img { + max-width: 100%; +} + +div#ticket-history div.downloadattachment { +float: right; +clear: both; +font-size: 0.9em; +text-align: right; +background: #ddd; +padding: 0.5em; +margin-left: 1em; + +border: 1px solid #ccc; +border-right: 2px solid #aaa; +border-bottom: 2px solid #aaa; +margin-top: 0.5em; +-moz-border-radius: 0.5em; +-webkit-border-radius: 0.5em; + +} + +div#ticket-history div.downloadattachment .downloadcontenttype{ +color: #666; +padding-right:0.25em; +} + + +div#ticket-history .message-header-key { + width: 7em; + font-weight: bold; + color: #666; +} + + +div#ticket-history .messagebody .messagebody{ + font-size: 1em; + padding: 0; + border: 0; + margin: 0; +} + + + +.ticket-transaction.basics .type { background: #b32; } +.ticket-transaction.cfs .type { background: #b32; } +.ticket-transaction.people .type { background: #48c; } +.ticket-transaction.links .type { background: #316531; } +.ticket-transaction.dates .type { background: #633063; } +.ticket-transaction.message .type { background: #069; } +.ticket-transaction.reminders .type { background: #369; } +.ticket-transaction.other .type { background: #abc; } + + + + +.ticket-info-cfs .titlebox-title .left { background-color: #b32; color: #fff;} +.ticket-info-basics .titlebox-title .left { background-color: #b32; color: #fff;} +.ticket-info-people .titlebox-title .left { background-color: #48c; color: #fff;} +.ticket-info-requestor .titlebox-title .left { white-space: nowrap; background-color: #48c; color: #fff;} +.ticket-info-links .titlebox-title .left { background-color: #316531; color: #fff;} +.ticket-info-reminders .titlebox-title .left { background-color: #369; color: #fff;} +.ticket-info-dates .titlebox-title .left { background-color: #633063; color: #fff;} +.ticket-info-attachments .titlebox-title .left { background-color: #993366; color: #fff;} + + +.ticket-summary .titlebox-title a, div#body .ticket-summary .titlebox-title a:visited { color: #fff;} + +.unread-messages .titlebox , .unread-messages .titlebox-title .left { + border: 1px solid #99a; + border-right: 2px solid #aab; + border-bottom: 2px solid #aab; + +} + + +.unread-messages .titlebox { + background-color: #dde; +} + +.unread-messages .titlebox-title .left { + background-color: #cce; +} + +.ticket-inactive { + text-decoration: line-through; + color: #666 +} + +table.ticket-summary td.boxcontainer:first-child { + width: 50%; +} + diff --git a/rt/share/html/NoAuth/css/freeside2.1/tools.css b/rt/share/html/NoAuth/css/freeside2.1/tools.css new file mode 100644 index 000000000..843feb27f --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/tools.css @@ -0,0 +1,56 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +table.myday td { + padding: 1em; +} + +ol.dashboard-queries { + padding-left: 1.5em; +} + + diff --git a/rt/share/html/NoAuth/css/freeside2.1/yui-fonts.css b/rt/share/html/NoAuth/css/freeside2.1/yui-fonts.css new file mode 100644 index 000000000..fdae8d98f --- /dev/null +++ b/rt/share/html/NoAuth/css/freeside2.1/yui-fonts.css @@ -0,0 +1,7 @@ +/* +Copyright (c) 2008, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 2.5.1 +*/ +body {font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}table {font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;} diff --git a/rt/share/html/NoAuth/images/created.png b/rt/share/html/NoAuth/images/created.png Binary files differnew file mode 100644 index 000000000..4d5eeb9ea --- /dev/null +++ b/rt/share/html/NoAuth/images/created.png diff --git a/rt/share/html/NoAuth/images/created_due.png b/rt/share/html/NoAuth/images/created_due.png Binary files differnew file mode 100644 index 000000000..52dfc96f0 --- /dev/null +++ b/rt/share/html/NoAuth/images/created_due.png diff --git a/rt/share/html/NoAuth/images/due.png b/rt/share/html/NoAuth/images/due.png Binary files differnew file mode 100644 index 000000000..30a3aff8a --- /dev/null +++ b/rt/share/html/NoAuth/images/due.png diff --git a/rt/share/html/NoAuth/images/reminder.png b/rt/share/html/NoAuth/images/reminder.png Binary files differnew file mode 100644 index 000000000..4370b6902 --- /dev/null +++ b/rt/share/html/NoAuth/images/reminder.png diff --git a/rt/share/html/NoAuth/images/resolved.png b/rt/share/html/NoAuth/images/resolved.png Binary files differnew file mode 100644 index 000000000..09db36d5a --- /dev/null +++ b/rt/share/html/NoAuth/images/resolved.png diff --git a/rt/share/html/NoAuth/images/started.png b/rt/share/html/NoAuth/images/started.png Binary files differnew file mode 100644 index 000000000..e177addea --- /dev/null +++ b/rt/share/html/NoAuth/images/started.png diff --git a/rt/share/html/NoAuth/images/starts.png b/rt/share/html/NoAuth/images/starts.png Binary files differnew file mode 100644 index 000000000..88064ba50 --- /dev/null +++ b/rt/share/html/NoAuth/images/starts.png diff --git a/rt/share/html/NoAuth/images/starts_due.png b/rt/share/html/NoAuth/images/starts_due.png Binary files differnew file mode 100644 index 000000000..16a4de46f --- /dev/null +++ b/rt/share/html/NoAuth/images/starts_due.png diff --git a/rt/share/html/NoAuth/images/updated.png b/rt/share/html/NoAuth/images/updated.png Binary files differnew file mode 100644 index 000000000..680e79a54 --- /dev/null +++ b/rt/share/html/NoAuth/images/updated.png diff --git a/rt/share/html/Prefs/Calendar.html b/rt/share/html/Prefs/Calendar.html new file mode 100644 index 000000000..5fbdd2717 --- /dev/null +++ b/rt/share/html/Prefs/Calendar.html @@ -0,0 +1,123 @@ +<%args> +$ChangeURL => undef +$ResetURL => undef +$SearchType => 'Ticket' +$HiddenField => undef +</%args> + +<& /Elements/Header, Title => $title &> +<& /User/Elements/Tabs, + current_tab => 'Prefs/Calendar.html', + Title => $title +&> + +<&| /Widgets/TitleBox, title => loc('ICal Feeds (ics)') &> + +<&| /Widgets/TitleBox, title => 'Help' &> + +<h3>displaying reminders :</h3> +<p>If you want to have reminders in a search you need to go in the <a +href="<%$RT::WebPath%>/Search/Edit.html"><%loc("Edit Query")%></a> tab +of the <%loc("query builder")%> and add something like that : + + <pre> + AND ( Type = 'ticket' OR Type = 'reminder' ) +</pre> +</p> + +<h3>displaying other kind of dates :</h3> +<p>By default RTx::Calendar display Due and Starts dates. You can +select other kind of events you want with the <%loc("Display +Columns")%> section in the <a +href="<%$RT::WebPath%>/Search/Build.html"><%loc("Query +Builder")%></a>. The following one will display the two latter and +LastUpdated dates : + +<pre> + '<small>__Due__</small>', + '<small>__Starts__</small>', + '<small>__LastUpdated__</small>' +</pre> +</p> + +<h3>changing the default query :</h3> +<p>You can change the default Query of Calendar.html and MyCalendar +portlet by saving a query with the name <code>calendar</code> in the +<a href="<%$RT::WebPath%>/Search/Build.html"><%loc("Query +Builder")%></a>.</p> + +</&> + +<& /Prefs/Elements/CalendarFeed &> + +% # only allow this part if +% if ($AllowSearch) { + +% my $search_count; + +% # I'm quite sure the loop isn't usefull but... +% my @Objects = $session{CurrentUser}->UserObj; +% for my $object (@Objects) { +% next unless ref($object) eq 'RT::User' && $object->id == $session{'CurrentUser'}->Id; +% my @searches = $object->Attributes->Named('SavedSearch'); +% for my $search (@searches) { +% next if ($search->SubValue('SearchType') +% && $search->SubValue('SearchType') ne $SearchType); +% $search_count++; +<& /Prefs/Elements/CalendarFeed, Object => $object, Search => $search &> + +% } +% } +% unless ($search_count) { + +<&| /Widgets/TitleBox, title => loc('Private Search ICal feeds') + , title_class=> 'inverse' + , color => "#993333" &> + +You can add private ICal feeds by saving new queries in <a +href="<%$RT::WebPath . '/Search/Build.html'%>">the Query Builder</a> + +</&> + +% } +% } else { +%#<&| /Widgets/TitleBox, title => loc('Private Search ICal feeds') +%# , title_class=> 'inverse' +%# , color => "#993333" &> +%# +%#<%loc('Private search ICal feeds disabled. To enable them, ask your admin for "[_1]" and "[_2]" rights', +%# loc('CreateSavedSearch'), +%# loc('LoadSavedSearch') )%> +%# +%#</&> +% } + +</&> + +<%INIT> +use Digest::SHA1; +use RT::SavedSearches; + +my $title = loc("Calendar Prefs"); +my $AllowSearch; + +$AllowSearch = 1 + if $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch', + Object=> $RT::System ); + +my $object; + +if ($HiddenField eq 'Private') { + $object = $session{CurrentUser}->UserObj; +} elsif ($AllowSearch and my ($SearchId) = $HiddenField =~ m/SavedSearch\-(\d+)/) { + $object = $session{CurrentUser}->Attributes->WithId($SearchId); +} + +if (defined $ChangeURL) { + my @args = $object->SetAttribute(Name => 'ICalURL', Content => Digest::SHA1::sha1_base64(time)); +} elsif (defined $ResetURL) { + my @args = $object->DeleteAttribute('ICalURL'); +} + + +</%INIT> diff --git a/rt/share/html/Prefs/Elements/CalendarFeed b/rt/share/html/Prefs/Elements/CalendarFeed new file mode 100644 index 000000000..46893435e --- /dev/null +++ b/rt/share/html/Prefs/Elements/CalendarFeed @@ -0,0 +1,68 @@ +<%args> +$Search => undef +$Object => undef +$HiddenField => undef +</%args> + +<&| /Widgets/TitleBox, title => $title &> + +% if ($FeedText) { +<p><%$FeedText%></p> +% } else { +This feed will show tickets with due date find with query:<br /> +"<%$Search->SubValue('Query')%>". +% } + +% if ($ICalURL) { +<p>Your can paste this url in your calendar : <b><a href="<%$link%>"><%$link%></a></b><p> +<table> +<tr> +<td> +<form action="<%$RT::WebPath%>/Prefs/Calendar.html" method="post"> +<input type="hidden" name="HiddenField" value="<%$HiddenField%>" /> +<input type="submit" class="button" name="ResetURL" value="<%loc('Disable Feed')%>" /> +</form> +</td> +<td> +<form action="<%$RT::WebPath%>/Prefs/Calendar.html" method="post"> +<input type="hidden" name="HiddenField" value="<%$HiddenField%>" /> +<input type="submit" class="button" name="ChangeURL" value="<%loc('Change Feed URL')%>" /> +</form> +</td> +</tr> +</table> +% } else { + +<form action="<%$RT::WebPath%>/Prefs/Calendar.html" method="post"> +<input type="hidden" name="HiddenField" value="<%$HiddenField%>" /> +<input type="submit" class="button" name="ChangeURL" value="<%loc('Enable Feed')%>" /> +</form> +% } + +</&> + +<%init> +my $title; +my $ICalURL; +my $Id; +my $FeedText; +my $link; + +if ($Object) { + $title = loc('Feed for "') . ($Search->Description || loc('Unnamed search')) . '" search'; + $HiddenField = "SavedSearch-" . $Search->Id; + $ICalURL = $Search->FirstAttribute('ICalURL'); + $Id = $session{CurrentUser}->Id . "@" . $Search->Id; + $title .= " (disabled)" unless $ICalURL; +} else { + $title = loc('Feed for default calendar'); + $HiddenField = "Private"; + $ICalURL = $session{CurrentUser}->UserObj->FirstAttribute('ICalURL'); + $Id = $session{CurrentUser}->Id; + $FeedText = "This feed will show yours and Nobody's tasks with due date."; +} + +$link = $RT::WebURL . "NoAuth/Calendar/" . $Id . "/" . $ICalURL->Content + if $ICalURL; + +</%init>
\ No newline at end of file diff --git a/rt/share/html/Prefs/SavedSearches.html b/rt/share/html/Prefs/SavedSearches.html new file mode 100644 index 000000000..fe9859ca4 --- /dev/null +++ b/rt/share/html/Prefs/SavedSearches.html @@ -0,0 +1,10 @@ +<& /Elements/Header, Title => $title, &> +<& /Ticket/Elements/Tabs, + current_tab => "Prefs/SavedSearches.html", + Title => $title, +&> +<& /Elements/SavedSearches, %ARGS &> + +<%INIT> +my $title = "Saved Searches"; +</%INIT> diff --git a/rt/share/html/Prefs/SearchOptions.html b/rt/share/html/Prefs/SearchOptions.html index 613a0f351..aeb27863f 100644 --- a/rt/share/html/Prefs/SearchOptions.html +++ b/rt/share/html/Prefs/SearchOptions.html @@ -45,7 +45,7 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<& /Elements/Header, Title => loc("Search Preferences") &> +<& /Elements/Header, Title => loc("Ticketing Search Preferences") &> <& /User/Elements/Tabs, current_tab => "Prefs/SearchOptions.html", Title => loc("Search Preferences") diff --git a/rt/share/html/RTx/Statistics/CallsMultiQueue/Elements/Chart b/rt/share/html/RTx/Statistics/CallsMultiQueue/Elements/Chart new file mode 100755 index 000000000..6a7279bdc --- /dev/null +++ b/rt/share/html/RTx/Statistics/CallsMultiQueue/Elements/Chart @@ -0,0 +1,39 @@ +<%perl> +$r->content_type("image/$format"); +print $graph->plot(\@data)->$format(); +$m->abort(); +</%perl> +<em><&|/l, $#data+1&>[_1] Plot Elements</&></em><p> +% foreach my $value (@data) { +<% $value %><p> +% } +<em><&|/l&>x_labels</&>:</em><p> +<% $ARGS{x_labels} %> +<p> +<em><&|/l&>legend</&>:</em><p> +<% $ARGS{set_legend} %> +<p> +<em><&|/l, (keys %ARGS) - 2&>[_1] data sets</&>:</em><p> + +% for (1..(scalar keys %ARGS)-2) { +<% $_ %> <% $ARGS{"data$_"} %><p> +% } + +<%INIT> +use GD::Graph::lines; + +my @data; +my $graph = GD::Graph::lines->new($Statistics::GraphWidth||500,$Statistics::GraphHeight||400); +$graph->set(export_format => "png", + x_label => 'Day of Week', + y_label => 'Tickets per day'); +$graph->set_legend(split /,/ , $ARGS{set_legend}); +my $format = $graph->export_format; +push @data, [split /,/ , $ARGS{x_labels}]; +for (1..((scalar keys %ARGS)-2)) { + push @data, [split /,/ , $ARGS{"data".$_}]; +} + +</%INIT> +<%ARGS> +</%ARGS> diff --git a/rt/share/html/RTx/Statistics/CallsMultiQueue/index.html b/rt/share/html/RTx/Statistics/CallsMultiQueue/index.html new file mode 100755 index 000000000..1b7a6592c --- /dev/null +++ b/rt/share/html/RTx/Statistics/CallsMultiQueue/index.html @@ -0,0 +1,325 @@ +<& /Elements/Header, Title => loc('Tickets per day in Multiple queues') &> +<& /RTx/Statistics/Elements/Tabs, Title => loc('Tickets per day in Multiple Queues by status') &> + +<h3>Description</h3> +<p>This chart shows details of tickets per day by their status. You can select multiple queues to display at the same time, but only one status. You can chose any of the defined status values. +There is also the option to display all available queues at the same time. +The default display shows tickets resolved in your default queue (General unless altered locally). +The line chart below shows the same information in a graphical form. + +<br /> + +<form method="POST" action="index.html"> + +%# Build Legend +% my @legend; +% for (sort keys %queues_to_show) { +% push @legend, $_; +% } + +%my $title = "Tickets with Status $status in " . join(', ', @queues) . ", per day from " . +% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[0]) . " through " . +% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$#dates-1]); + +<& /Elements/TitleBoxStart, title => $title, title_href => "/RTx/Statistics/OpenStalled/index.html?$QueryString"&> +<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH="100%"> +% if ($ShowHeader) { +<& /RTx/Statistics/Elements/CollectionAsTable/Header, + Format => \@RowFormat, + FormatString => $RowFormat, + AllowSorting => $AllowSorting, + Order => $Order, + Query => undef, + Rows => $Rows, + Page => $Page, + OrderBy => $OrderBy , + BaseURL => $BaseURL, + maxitems => $maxitems &> +% } +% my $line = 0; +% LINE: for my $d (0..$#dates) { +% if ($d == $#dates ){ +% next LINE; +% } +% $line++; +% my $x = 1; +% $values{Statistics_Date} = Statistics::FormatDate($dateformat, $dates[$d]); +% my $row_total=0; +% foreach my $q (sort keys %queues_to_show) { +% my $tix = new RT::Tickets($session{'CurrentUser'}); +% if ($status eq "resolved") { +% $tix->LimitStatus(VALUE => $status); +% $tix->LimitResolved(VALUE => $dates[$d]->ISO, OPERATOR => ">="); +% if ($dates[$d+1]) { +% $tix->LimitResolved(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); +% } +% } +% elsif ($status eq "new") { +% $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">="); +% if ($dates[$d+1]) { +% $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); +% } +% } +% elsif ($status eq "deleted") { +% $tix->LimitStatus(VALUE => $status); +% $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">="); +% if ($dates[$d+1]) { +% $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); +% } +% } +% elsif ($status eq "stalled") { +% $tix->LimitStatus(VALUE => $status); +% $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">="); +% if ($dates[$d+1]) { +% $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); +% } +% } +% elsif ($status eq "open") { +% $tix->LimitStatus(VALUE => $status); +% $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">="); +% if ($dates[$d+1]) { +% $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); +% } +% } +% elsif ($status eq "rejected") { +% $tix->LimitStatus(VALUE => $status); +% $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">="); +% if ($dates[$d+1]) { +% $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); +% } +% } +% $tix->LimitQueue (VALUE => $q); +% $values{$q} = $tix->Count; +% $row_total += $tix->Count; +% $data[$x++][$d] = $tix->Count; +% } +% $values{Statistics_Totals} = $row_total; +<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@RowFormat, i => $line, record => $record, maxitems => $maxitems &> +% } +</table> +<& /Elements/TitleBoxEnd&> + +<!-- <td>Show:</td> + <td COLSPAN=2><SELECT NAME="status"> +% for (qw(resolved new deleted stalled rejected open)) { + <OPTION VALUE="<% $_ %>" <% $_ eq $status && "SELECTED" %>> + <% loc($_) %></OPTION> +% } +--!> + +<%perl> +# Create the graph URL +my $url = 'Elements/Chart?x_labels='; +#$url .= join ",", @{ shift @data } . "&"; +for (0..$max) { + $url .= $m->interp->apply_escapes($data[0][$_],'u') . ","; +} +chop $url; +$url .= "&"; +shift @data; +$url .= 'set_legend='.(join ",", @legend)."&"; +for (0..$#data) { + $url .= "data".(1+$_)."=". (join ",", @{$data[$_]})."&"; +} +chop $url; +</%perl> + +<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &> + +<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, + Title => "Change Status, Queues or Dates", + ShowDates => 1, sMonth => \$sMonth, sDay => \$sDay, sYear => \$sYear, + eMonth => \$eMonth, eDay => \$eDay, eYear => \$eYear, + weekends => $weekends, + ShowMultiQueues => 1, queues_ref => \@queues, + ShowStatus => 1, Status => $status + &> + +</form> + +<a href="<%$RT::WebPath%>/RTx/Statistics/CallsMultiQueue/index.html?<% $QueryString %>"><&|/l&>Bookmarkable link</&></a> +%# | <a href="<%$RT::WebPath%>/RTx/Statistics/CallsMultiQueue/Results.tsv?<%$QueryString%>"><&|/l&>spreadsheet</&></a> +<BR> +<BR> + +<%ARGS> +$status => $Statistics::MultiQueueStatus +$max => $Statistics::MultiQueueMaxRows +@queues => @Statistics::MultiQueueQueueList +$weekends => $Statistics::PerDayWeekends; +$sMonth=>undef +$sDay=>undef +$sYear=>undef +$eMonth=>undef +$eDay=>undef +$eYear=>undef +$days=>undef +$dateformat => $Statistics::MultiQueueDateFormat +$currentMonth=>undef + +$AllowSorting => undef +$Order => undef +$OrderBy => undef +$ShowNavigation => 1 +$ShowHeader => 1 +$Rows => 50 +$Page => 1 +$BaseURL => undef +$AddAllCheck => undef +</%ARGS> + +<%INIT> + +use RTx::Statistics; +use Time::Local; +my $n = 0; +my @data = ([]); +my @dates; +my @msgs; +my $selected; +my $diff; +my %queues_to_show; +my $secsPerDay=86400; +my $sEpoch; +my $eEpoch; +my $QueryString; +my $maxitems; +my $RowFormat; +my $BoldRowFormat; +my %record; +my %values; +my $record = \%record; + +$record{values} = \%values; + +Statistics::DebugClear(); +Statistics::DebugLog("CallsQueueDay/index.html ARGS:\n"); +for my $key (keys %ARGS) { + Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n"); +} + + + # Handle the Add All Checkbox + if($AddAllCheck eq "on") { + $AddAllCheck = undef; + undef (@queues); + my $q=new RT::Queues($session{'CurrentUser'}); + $q->UnLimit; + while (my $queue=$q->Next) { + next if !$queue->CurrentUserHasRight('SeeQueue'); + push @queues, $queue->Name; + } + } + + # If the user has the right to see the queue, put it into the map + for my $q (@queues) { + my $Queueobj = new RT::Queue($session{'CurrentUser'}); + $Queueobj->Load($q); + next if !$Queueobj->CurrentUserHasRight('SeeQueue'); + $queues_to_show{$q} = 1; + } + + $maxitems = (scalar @queues) + 2; + + # Build the format strings + $RowFormat = "'__Statistics_Date__'"; + $BoldRowFormat = "'<B>__Statistics_Date__</B>'"; + for my $q (@queues) { + $RowFormat .= ",'__Statistics_Dynamic__/KEY:$q/TITLE:$q/STYLE:text-align:right;'"; + $BoldRowFormat .= ",'<B>__Statistics_Dynamic__</B>/KEY:$q/TITLE:$q/STYLE:text-align:right;'"; + } + $RowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'"; + $BoldRowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'"; + # Parse the formats into structures. + my (@RowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $RowFormat); + my (@BoldRowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldRowFormat); + +if ($sDay > $Statistics::monthsMaxDay{$sMonth}) { + $sDay = $Statistics::monthsMaxDay{$sMonth}; +} + +if ($eDay > $Statistics::monthsMaxDay{$eMonth}) { + $eDay = $Statistics::monthsMaxDay{$eMonth}; +} + +if ($sYear){ + $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900); +} +if ($eYear){ +Statistics::DebugLog("eMonth = " . $eMonth . "\n"); + $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900); +} else { + # This case happens when the page is first loaded + my @local = localtime(time); + ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]); + $eYear += 1900; + $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]); +Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n"); +} + +if (($eEpoch < $sEpoch) || ($sEpoch == 0)) { + # We have an end, but not a start, or, overlapping. + + # if $currentMonth is set, just set the day to 1 + if($currentMonth) { + # set start vars from end, but with day set to 1 + (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch); + $sDay=1; + $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear); + } else { + # If the user has specified how many days back to go, use that, + # If not, set start to configured default period before end + if(defined $days) { + $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay); + } else { + $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay); + } + (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch); + } + $sYear += 1900; +} + +# Compute days to chart. +# The +1 is because we need to generate one more date. If the user +# selected a 10 day range, we need to generate 11 days. +$diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1; +Statistics::DebugLog("Setting diff=$diff\n"); + +Statistics::DebugLog("sEpoch=$sEpoch, components=" . join(',', localtime($sEpoch)) . "\n"); +Statistics::DebugLog("eEpoch=$eEpoch, components=" . join(',', localtime($eEpoch)) . "\n"); + +# Build the new query string +$QueryString = "queues=" . join("&queues=", @queues); +$QueryString .= "&sDay=$sDay&sMonth=$sMonth&sYear=$sYear&eDay=$eDay&eMonth=$eMonth&eYear=$eYear&weekends=$weekends"; + + + + +# Set up the end date to be midnight(morning) of the date after the one the user wanted. +my $endRange = $eEpoch + $Statistics::secsPerDay; +$n = 0; +until ($#dates == $diff) { + my $date = new RT::Date($session{CurrentUser}); + $date->Set(Value=>$endRange - $n, Format => 'unix'); + # Note: we used to adjust the time to local midnight, but + # none of the other date entry fields in RT seem to adjust, so we've stopped. + #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n"); + $n+= $Statistics::secsPerDay; + # If we aren't showing weekends and this is one, decrement the number + # of days to show and skip to the next date. + if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;} + unshift @dates, $date; +Statistics::DebugLog("pushing date " . Statistics::FormatDate("%c", $date) . "\n"); + unshift @{ $data[0] }, Statistics::FormatDate($Statistics::PerDayLabelDateFormat, $date); +} + +# We put an extra day into the lists to cover up till midnight of the next day, +# But we don't want that to appear in the labels, so pop it off. +pop( @{ $data[0] } ); + +my $queue = new RT::Queues($session{CurrentUser}); +$queue->UnLimit; + +my $QueueObj = new RT::Queue($session{'CurrentUser'}); +$QueueObj->Load($queue); +</%INIT> diff --git a/rt/share/html/RTx/Statistics/CallsQueueDay/Elements/Chart b/rt/share/html/RTx/Statistics/CallsQueueDay/Elements/Chart new file mode 100755 index 000000000..2d6b43c17 --- /dev/null +++ b/rt/share/html/RTx/Statistics/CallsQueueDay/Elements/Chart @@ -0,0 +1,29 @@ +<%perl> +$r->content_type("image/$format"); +print $graph->plot(\@data)->$format(); +$m->abort(); +print $#data+1 . " Elements:<p>"; +for (0..$#data) { +print $data[$_]; +print "<p>"; +} +</%perl> +<%INIT> +use GD::Graph::lines; + +my @data; +my $graph = GD::Graph::lines->new($Statistics::GraphWidth||500,$Statistics::GraphHeight||400); +$graph->set(export_format => "png", + x_label => 'Day of Week', + y_label => 'Tickets per Day', + x_labels_vertical => 1, + ); +my $format = $graph->export_format; +$graph->set_legend(split /,/ , $ARGS{set_legend}); +push @data, [split /,/ , $ARGS{x_labels}]; +push @data, [split /,/ , $ARGS{data1}]; +push @data, [split /,/ , $ARGS{data2}]; +push @data, [split /,/ , $ARGS{data3}]; +</%INIT> +<%ARGS> +</%ARGS> diff --git a/rt/share/html/RTx/Statistics/CallsQueueDay/Results.tsv b/rt/share/html/RTx/Statistics/CallsQueueDay/Results.tsv new file mode 100644 index 000000000..23f0c699c --- /dev/null +++ b/rt/share/html/RTx/Statistics/CallsQueueDay/Results.tsv @@ -0,0 +1,191 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +%# +%# +%# 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> +$Queue => undef +$weekends => $Statistics::PerDayWeekends; +$sMonth=>undef +$sDay=>undef +$sYear=>undef +$eMonth=>undef +$eDay=>undef +$eYear=>undef +$days=>undef +$currentMonth=>undef +</%ARGS> + +<%INIT> +use RTx::Statistics; +use Time::Local; +my @dates; +my $n = 0; +my %Totals; +my $now = new RT::Date($session{CurrentUser}); +my $sEpoch; +my $eEpoch; + +if (!defined $Queue) { + $Queue = $Statistics::PerDayQueue; +} + +if ($sDay > $Statistics::monthsMaxDay{$sMonth}) { + $sDay = $Statistics::monthsMaxDay{$sMonth}; +} + +if ($eDay > $Statistics::monthsMaxDay{$eMonth}) { + $eDay = $Statistics::monthsMaxDay{$eMonth}; +} + +if ($sYear){ + $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900); +} +if ($eYear){ +Statistics::DebugLog("eMonth = " . $eMonth . "\n"); + $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900); +} else { + # This case happens when the page is first loaded + my @local = localtime(time); + ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]); + $eYear += 1900; + $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]); +Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n"); +} + +if (($eEpoch < $sEpoch) || ($sEpoch == 0)) { + # We have an end, but not a start, or, overlapping. + + # if $currentMonth is set, just set the day to 1 + if($currentMonth) { + # set start vars from end, but with day set to 1 + (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch); + $sDay=1; + $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear); + } else { + # If the user has specified how many days back to go, use that, + # If not, set start to configured default period before end + if(defined $days) { + $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay); + } else { + $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay); + } + (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch); + } + $sYear += 1900; +} + +# set content type +$r->content_type('application/vnd.ms-excel'); + +# Put out some data about the generation of this file +$m->out("Tickets per day for Queue:\t" . $Queue . "\tGenerated at:\t" . Statistics::FormatDate("%x %X", $now). "\n\n"); + + +# Compute days to chart. +# The +1 is because we need to generate one more date. If the user +# selected a 10 day range, we need to generate 11 days. +my $diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1; + +# Build array of dates +my $endRange = $eEpoch + $Statistics::secsPerDay; +my $QueueObj = new RT::Queue($session{'CurrentUser'}); +$QueueObj->Load($Queue); +until ($#dates == $diff) { + my $date = new RT::Date($session{CurrentUser}); + $date->Set(Value=>$endRange - $n, Format => 'unix'); + # Note: we used to adjust the time to local midnight, but + # none of the other date entry fields in RT seem to adjust, so we've stopped. + #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n"); + $n+= $Statistics::secsPerDay; + # If we aren't showing weekends and this is one, decrement the number + # of days to show and skip to the next date. + if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;} + unshift @dates, $date; +} + +# Output header row +$m->out("Date\tcreate\tresolved\tdeleted\n"); + + +LINE: for my $d (0..$#dates) { + if ($d == $#dates){ + next LINE; + } + my $x = 1; + # Output the date for this row + $m->out(Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$d])); + + # output the 3 columns for this row + for my $status (qw(created resolved deleted)) { + my $tix = new RT::Tickets($session{'CurrentUser'}); + if ($status eq "created") { + $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">="); + if ($dates[$d+1]) { + $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); + } + } elsif ($status eq "resolved") { + $tix->LimitStatus(VALUE => $status); + $tix->LimitResolved(VALUE => $dates[$d]->ISO, OPERATOR => ">="); + if ($dates[$d+1]) { + $tix->LimitResolved(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); + } + } elsif ($status eq "deleted") { + $tix->LimitStatus(VALUE => $status); + $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">="); + if ($dates[$d+1]) { + $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); + } + } + $tix->LimitQueue (VALUE => $Queue); + $m->out( "\t" . $tix->Count ); + $Totals{$status} += $tix->Count; + } + $m->out("\n"); +} + +# Output the totals +$m->out("Totals\t$Totals{created}\t$Totals{resolved}\t$Totals{deleted}\n"); + +$m->abort(); +</%INIT> diff --git a/rt/share/html/RTx/Statistics/CallsQueueDay/index.html b/rt/share/html/RTx/Statistics/CallsQueueDay/index.html new file mode 100755 index 000000000..5a4fe52f0 --- /dev/null +++ b/rt/share/html/RTx/Statistics/CallsQueueDay/index.html @@ -0,0 +1,270 @@ +<& /Elements/Header, Title => loc("Tickets per day in Queue:" . $QueueObj->Name()) &> +<& /RTx/Statistics/Elements/Tabs, Title => loc("Tickets by status per day in Queue:" . $QueueObj->Name()) &> + +<h3>Description</h3> +<p>This page displays details about tickets in the selected queue over the date range chosen. It shows how many tickets were created on +each day in the chosen range, and how many of those were either Resolved or Deleted.</p> +<p>To always show the current month to date, bookmark this <a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/index.html?currentMonth=1">link</a>, or +for a spreadsheet, use this <a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/Results.tsv?currentMonth=1">link</a>.</p> + +<form method="POST" action="index.html"> + +% Statistics::DebugLog("queue name=" . $QueueObj->Name() . "\n"); + +%my $title = "Ticket counts in " . $QueueObj->Name() . " by status per day from " . +% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[0]) . " through " . +% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$#dates-1]); +<&|/Elements/TitleBox, + title => $title, + title_href => "/RTx/Statistics/CallsQueueDay/index.html?$QueryString" &> +<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%> +% if ($ShowHeader) { +<& /RTx/Statistics/Elements/CollectionAsTable/Header, + Format => \@Format, + FormatString => $Format, + AllowSorting => $AllowSorting, + Order => $Order, + Query => undef, + Rows => $Rows, + Page => $Page, + OrderBy => $OrderBy , + BaseURL => $BaseURL, + maxitems => $maxitems &> +% } +% my $line = 1; +% LINE: for my $d (0..$#dates) { +% if ($d == $#dates){ +% next LINE; +% } +% my $x = 1; +% $values{Statistics_Date} = Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$d]); +%# NOTE need to handle all status values here.... +% for my $status (qw(created resolved deleted)) { +% my $tix = new RT::Tickets($session{'CurrentUser'}); +% $tix->LimitQueue (VALUE => $Queue); +% if ($status eq "created") { +% $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">="); +% if ($dates[$d+1]) { +% $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); +% } +% $values{Statistics_Created_Count} = $tix->Count; +% $Totals{Statistics_Created_Count} += $tix->Count; +% } +% elsif ($status eq "resolved") { +% $tix->LimitStatus(VALUE => $status); +% $tix->LimitResolved(VALUE => $dates[$d]->ISO, OPERATOR => ">="); +% if ($dates[$d+1]) { +% $tix->LimitResolved(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); +% } +% $values{Statistics_Resolved_Count} = $tix->Count; +% $Totals{Statistics_Resolved_Count} += $tix->Count; +% } +% elsif ($status eq "deleted") { +% $tix->LimitStatus(VALUE => $status); +% $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">="); +% if ($dates[$d+1]) { +% $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); +% } +% $values{Statistics_Deleted_Count} = $tix->Count; +% $Totals{Statistics_Deleted_Count} += $tix->Count; +% } +% $data[$x++][$d] = $tix->Count; +% } +<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@Format, i => $line, record => $record, maxitems => $maxitems &> +% $line++; +% } +% $values {Statistics_Date} = "Totals"; +% $values {Statistics_Created_Count} = $Totals{Statistics_Created_Count}; +% $values {Statistics_Resolved_Count} = $Totals{Statistics_Resolved_Count}; +% $values {Statistics_Deleted_Count} = $Totals{Statistics_Deleted_Count}; +<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@BoldFormat, i => $line, record => $record, maxitems => $maxitems &> +</table> +</&> + +<%perl> +# Create the graph URL +my $url= 'Elements/Chart?x_labels='; +for (1..$diff) { + $url .= $data[0][$_] . ","; +} +chop $url; +$url .= "&"; +shift @data; +for (0..$#data) { + $url .= "data".(1+$_)."=".(join ",", @{$data[$_]})."&"; +} +chop $url; +$url .= "&set_legend=Created,Resolved,Deleted"; +</%perl> + +<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &> + +<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, + Title => "Change Queue or Dates", + ShowDates => 1, sMonth => \$sMonth, sDay => \$sDay, sYear => \$sYear, + eMonth => \$eMonth, eDay => \$eDay, eYear => \$eYear, + weekends => $weekends, + ShowSingleQueue => 1, Queue => $Queue + &> + +</form> + +<a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/index.html?<% $QueryString %>"><&|/l&>Bookmarkable link</&></a> | +<a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/Results.tsv?<%$QueryString%>"><&|/l&>spreadsheet</&></a> +<BR> +<BR> + + +% Statistics::DebugLog("ref of eMonth is " . ref($eMonth) . "\n"); +% Statistics::DebugInit( $m ); + +<%ARGS> +$Queue => undef +$weekends => $Statistics::PerDayWeekends; +$sMonth=>undef +$sDay=>undef +$sYear=>undef +$eMonth=>undef +$eDay=>undef +$eYear=>undef +$days=>undef +$currentMonth=>undef + +$AllowSorting => undef +$Order => undef +$OrderBy => undef +$ShowNavigation => 1 +$ShowHeader => 1 +$Rows => 50 +$Page => 1 +$BaseURL => undef +</%ARGS> + +<%INIT> +use RTx::Statistics; +use Time::Local; +my $selected; +my $n = 0; +my @data = ([]); +my @dates; +my @msgs; +my $diff; +my $sEpoch=0; +my $eEpoch=0; +my %Totals; +my $QueryString; +my $maxitems = 4; +my %record; +my %values; +my $record = \%record; + +$record{values} = \%values; + + +# If debugging, set things up and display all the args +Statistics::DebugClear(); +Statistics::DebugLog("CallsQueueDay/index.html ARGS:\n"); +for my $key (keys %ARGS) { + Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n"); +} + +my $Format = qq{ Statistics_Date, + '__Statistics_Created_Count__/STYLE:text-align:right;', + '__Statistics_Resolved_Count__/STYLE:text-align:right;', + '__Statistics_Deleted_Count__/STYLE:text-align:right;' }; +my $BoldFormat = qq{ '<B>__Statistics_Date__</B>', + '<B>__Statistics_Created_Count__</B>/STYLE:text-align:right;', + '<B>__Statistics_Resolved_Count__</B>/STYLE:text-align:right;', + '<B>__Statistics_Deleted_Count__</B>/STYLE:text-align:right;' }; +my (@Format) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $Format); +my (@BoldFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldFormat); +Statistics::DebugLog("CallsQueueDay/index.html Format array=" . join(',', @Format) . "\n"); + +if (!defined $Queue) { + my $QueueObj = new RT::Queue($session{'CurrentUser'}); + $QueueObj->Load($Statistics::PerDayQueue); + $Queue = $QueueObj->Id(); +} + +if ($sDay > $Statistics::monthsMaxDay{$sMonth}) { + $sDay = $Statistics::monthsMaxDay{$sMonth}; +} + +if ($eDay > $Statistics::monthsMaxDay{$eMonth}) { + $eDay = $Statistics::monthsMaxDay{$eMonth}; +} + +if ($sYear){ + $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900); +} +if ($eYear){ +Statistics::DebugLog("eMonth = " . $eMonth . "\n"); + $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900); +} else { + # This case happens when the page is first loaded + my @local = localtime(time); + ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]); + $eYear += 1900; + $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]); +Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n"); +} + +if (($eEpoch < $sEpoch) || ($sEpoch == 0)) { + # We have an end, but not a start, or, overlapping. + + # if $currentMonth is set, just set the day to 1 + if($currentMonth) { + # set start vars from end, but with day set to 1 + (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch); + $sDay=1; + $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear); + } else { + # If the user has specified how many days back to go, use that, + # If not, set start to configured default period before end + if(defined $days) { + $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay); + } else { + $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay); + } + (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch); + } + $sYear += 1900; +} + +# Compute days to chart. +# The +1 is because we need to generate one more date. If the user +# selected a 10 day range, we need to generate 11 days. +$diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1; +Statistics::DebugLog("Setting diff=$diff\n"); + +Statistics::DebugLog("sEpoch=$sEpoch, components=" . join(',', localtime($sEpoch)) . "\n"); +Statistics::DebugLog("eEpoch=$eEpoch, components=" . join(',', localtime($eEpoch)) . "\n"); + +# Set up the string for the current query for bookmarkable link +$QueryString = "sDay=$sDay&sMonth=$sMonth&sYear=$sYear&eDay=$eDay&eMonth=$eMonth&eYear=$eYear&weekends=$weekends&Queue=$Queue"; + +# Set up the end date to be midnight(morning) of the date after the one the user wanted. +my $endRange = $eEpoch + $Statistics::secsPerDay; +my $QueueObj = new RT::Queue($session{'CurrentUser'}); +$QueueObj->Load($Queue); +$n = 0; +until ($#dates == $diff) { + my $date = new RT::Date($session{CurrentUser}); + $date->Set(Value=>$endRange - $n, Format => 'unix'); + # Note: we used to adjust the time to local midnight, but + # none of the other date entry fields in RT seem to adjust, so we've stopped. + #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n"); + $n+= $Statistics::secsPerDay; + # If we aren't showing weekends and this is one, decrement the number + # of days to show and skip to the next date. + if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;} + unshift @dates, $date; +Statistics::DebugLog("pushing date " . Statistics::FormatDate("%c", $date) . "\n"); + unshift @{ $data[0] }, Statistics::FormatDate($Statistics::PerDayLabelDateFormat, $date); +} + +# We put an extra day into the lists to cover up till midnight of the next day, +# But we don't want that to appear in the labels, so pop it off. +pop( @{ $data[0] } ); + +</%INIT> diff --git a/rt/share/html/RTx/Statistics/DayOfWeek/Elements/Chart b/rt/share/html/RTx/Statistics/DayOfWeek/Elements/Chart new file mode 100755 index 000000000..7bdf56b80 --- /dev/null +++ b/rt/share/html/RTx/Statistics/DayOfWeek/Elements/Chart @@ -0,0 +1,26 @@ +% $r->content_type("image/$format"); +% $m->print($graph->plot(\@data)->$format()); +% $m->abort(); +<&|/l, $#data+1&>[_1] Elements</&>:<p> +% for (0..$#data) { +<% $data[$_] %><p> +% } +<%INIT> +use GD::Graph::bars; + +my @data; +my $graph = GD::Graph::bars->new($Statistics::GraphWidth||500,$Statistics::GraphHeight||400); +$graph->set(export_format => "png", + x_label => 'Day of Week', + y_label => 'Ticket actions per Day by type'); +$graph->set_legend(split /,/ , $ARGS{set_legend}); +push @data, [split /,/ , $ARGS{x_labels}]; +push @data, [split /,/ , $ARGS{data1}]; +push @data, [split /,/ , $ARGS{data2}]; +push @data, [split /,/ , $ARGS{data3}]; + +my $format = $graph->export_format; +$r->content_type("image/$format"); +</%INIT> +<%ARGS> +</%ARGS> diff --git a/rt/share/html/RTx/Statistics/DayOfWeek/index.html b/rt/share/html/RTx/Statistics/DayOfWeek/index.html new file mode 100755 index 000000000..6353abbdb --- /dev/null +++ b/rt/share/html/RTx/Statistics/DayOfWeek/index.html @@ -0,0 +1,150 @@ +<& /Elements/Header, Title =>loc('Tickets by Day Of Week in Queue:' . $QueueObj->Name()) &> +<& /RTx/Statistics/Elements/Tabs, Title =>loc('Trends in ticket status by Day Of Week in Queue:' . $QueueObj->Name()) &> + +<h3>Description</h3> +<p>The purpose of this page is to show historical trends for each day of the week. +It displays details of number of tickets created in your +selected queue for each day. It also hows how many of those created tickets were Resolved or Deleted</p> + +<form method="POST" action="index.html"> + + +%my $title = "Ticket counts by day of week in " . $QueueObj->Name(); +<&|/Elements/TitleBox, + title => $title, + title_href => "/RTx/Statistics/DayOfWeek/index.html?$QueryString" &> +<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%> +% if ($ShowHeader) { +<& /RTx/Statistics/Elements/CollectionAsTable/Header, + Format => \@Format, + FormatString => $Format, + AllowSorting => $AllowSorting, + Order => $Order, + Query => undef, + Rows => $Rows, + Page => $Page, + OrderBy => $OrderBy , + BaseURL => $BaseURL, + maxitems => $maxitems &> +% } +% my $line = 1; +% for my $d (0..$#days) { +% my $x = 1; +% $values{Statistics_Date} = $days[$d]; +%# NOTE Show all status values??? +% $values{Statistics_Created_Count} = $counts[$d]{new}; +% $values{Statistics_Resolved_Count} = $counts[$d]{resolved}; +% $values{Statistics_Deleted_Count} = $counts[$d]{deleted}; +<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@Format, i => $line, record => $record, maxitems => $maxitems &> +% $line++; +% } +% $values {Statistics_Date} = "Totals"; +% $values {Statistics_Created_Count} = $Totals{new}; +% $values {Statistics_Resolved_Count} = $Totals{resolved}; +% $values {Statistics_Deleted_Count} = $Totals{deleted}; +<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@BoldFormat, i => $line, record => $record, maxitems => $maxitems &> +</table> +</&> + +<%perl> +my $url = 'Elements/Chart?&x_labels='; +for (0..$#days) { + $url .= $days[$_] . "," ; +} +chop $url; +$url .= "&"; + +my @things = qw(new resolved deleted); +for my $th (0..$#things) { + $url .= "data".(1+$th)."=".(join ",", map { $counts[$_]{$things[$th]} } (0..6))."&"; +} +chop $url; +$url .= '&set_legend=Created,Resolved,Deleted'; +</%perl> + +<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &> + +% Statistics::DebugLog("queue name=" . $QueueObj->Id() . "\n"); + +<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, + Title => "Change Queue", + ShowSingleQueue => 1, Queue => $QueueObj->Id() + &> + +</form> + +% Statistics::DebugInit( $m ); + +<%ARGS> +$Queue => $Statistics::DayOfWeekQueue + +$AllowSorting => undef +$Order => undef +$OrderBy => undef +$ShowNavigation => 1 +$ShowHeader => 1 +$Rows => 50 +$Page => 1 +$BaseURL => undef +</%ARGS> + +<%INIT> +use GD::Graph; +use RTx::Statistics; +my @days = qw(Sun Mon Tue Wed Thu Fri Sat); +my $n = 0; +my @data = ([]); +my @msgs; +my @counts; +my %Totals = ( + resolved => 0, + deleted => 0, + new => 0 +); +my $QueryString = "Queue=$Queue"; +my $maxitems = 4; +my %record; +my %values; +my $record = \%record; + +$record{values} = \%values; + +my $Format = qq{ Statistics_Date, + '__Statistics_Created_Count__/STYLE:text-align:right;', + '__Statistics_Resolved_Count__/STYLE:text-align:right;', + '__Statistics_Deleted_Count__/STYLE:text-align:right;' }; +my $BoldFormat = qq{ '<B>__Statistics_Date__</B>', + '<B>__Statistics_Created_Count__</B>/STYLE:text-align:right;', + '<B>__Statistics_Resolved_Count__</B>/STYLE:text-align:right;', + '<B>__Statistics_Deleted_Count__</B>/STYLE:text-align:right;' }; +my (@Format) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $Format); +my (@BoldFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldFormat); + +my $QueueObj = new RT::Queue($session{'CurrentUser'}); +$QueueObj->Load($Queue); +$RT::Logger->warning("Loaded queue $Queue, name=". $QueueObj->Name()); + +my $tix = new RT::Tickets($session{'CurrentUser'}); +$tix->LimitQueue (VALUE => $Queue); +$tix->UnLimit; +if ($tix->Count) { + # Initialize the counters to zero, so that all the cells show up + foreach my $day (0..@days) { + $counts[$day]{resolved} = 0; + $counts[$day]{deleted} = 0; + $counts[$day]{new} = 0; + } + while (my $t = $tix->RT::SearchBuilder::Next) { # BLOODY HACK + if($t->Status eq "resolved") { + $counts[(localtime($t->ResolvedObj->Unix))[6]]{resolved}++; + $Totals{resolved}++; + } + if($t->Status eq "deleted") { + $counts[(localtime($t->LastUpdatedObj->Unix))[6]]{deleted}++; + $Totals{deleted}++; + } + $counts[(localtime($t->CreatedObj->Unix))[6]]{new}++; + $Totals{new}++; + } +} +</%INIT> diff --git a/rt/share/html/RTx/Statistics/DurationAsString b/rt/share/html/RTx/Statistics/DurationAsString new file mode 100755 index 000000000..c0b4d9af4 --- /dev/null +++ b/rt/share/html/RTx/Statistics/DurationAsString @@ -0,0 +1,18 @@ +<%$days|'00'%> days <%$hours|'00'%>:<%$minutes|'00'%> +<%INIT> + +my $MINUTE = 60; +my $HOUR = $MINUTE*60; +my $DAY = $HOUR * 24; +my $WEEK = $DAY * 7; +my $days = int($Duration / $DAY); +$Duration = $Duration % $DAY; +my $hours = int($Duration / $HOUR); +$hours = sprintf("%02d", $hours); +$Duration = $Duration % $HOUR; +my $minutes = int($Duration/$MINUTE); +$minutes = sprintf("%02d", $minutes); +</%INIT> +<%ARGS> +$Duration => undef +</%ARGS> diff --git a/rt/share/html/RTx/Statistics/Elements/CollectionAsTable/Header b/rt/share/html/RTx/Statistics/Elements/CollectionAsTable/Header new file mode 100644 index 000000000..cecb02eee --- /dev/null +++ b/rt/share/html/RTx/Statistics/Elements/CollectionAsTable/Header @@ -0,0 +1,126 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +%# +%# +%# 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> +@Format => undef +$FormatString => undef +$AllowSorting => undef +$Order=>undef +$BaseURL => undef +$Query => undef +$Rows => undef +$Page => undef +$maxitems => undef +</%ARGS> +<TR class="collection-as-table"> +<%perl> + +my %generic_query_args = ( Query => $Query, Rows => $Rows, Page => $Page, Format => $FormatString ); + +my $item = 0; +foreach my $col (@Format) { + $item++; + if ( $col->{title} eq 'NEWLINE' ) { + while ( $item < $maxitems ) { + $m->out(qq{<th class="collection-as-table"> </th>\n}); + $item++; + } + + $item = 0; + $m->out(qq{</TR>\n<TR class="collection-as-table">}); + } + else { + $m->out('<TH class="collection-as-table" '); + $m->out( 'align="' . $col->{align} . '"' ) if ( $col->{align} ); + $m->out( 'style="' . $col->{style} . '"' ) if ( $col->{style} ); + $m->out('>'); + my $title = $col->{title}; + $title =~ s/^__(.*)__$/$1/o; + $title = ( + $m->comp( + '/RTx/Statistics/Elements/StatColumnMap', + Name => $title, + Attr => 'title' + ) + || $title + ); + if ( + $AllowSorting + && $col->{'attribute'} + && $m->comp( + '/RTx/Statistics/Elements/StatColumnMap', + Name => $col->{'attribute'}, + Attr => 'attribute' + ) + ) + { + + $m->out( + '<a href="' . $BaseURL + . $m->comp( + '/Elements/QueryString', + %generic_query_args, + OrderBy => ( + $m->comp( + '/RTx/Statistics/Elements/StatColumnMap', + Name => $col->{'attribute'}, + Attr => 'attribute' + ) + || $col->{'attribute'} + ), + Order => ( $ARGS{'Order'} eq 'ASC' ? 'DESC' : 'ASC' ) + ) + . '">' + . loc($title) . '</a>' + ); + } + else { + $m->out( loc($title) ); + } + $m->out('</TH>'); + } +} +</%perl> +</TR> diff --git a/rt/share/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat b/rt/share/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat new file mode 100644 index 000000000..a482f817e --- /dev/null +++ b/rt/share/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat @@ -0,0 +1,109 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +%# +%# +%# 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> +$Format +</%ARGS> + +<%init> +use Regexp::Common; +my @Columns; + +while ($Format =~ /($RE{delimited}{-delim=>qq{\'"}}|[{}\w.]+)/go) { + my $col = $1; + + if ($col =~ /^$RE{quoted}$/o) { + substr($col,0,1) = ""; + substr($col,-1,1) = ""; + } + + my $colref; + + # kfh at mqsoftware.com added this to be able + # to create columns where the actual heading and value + # aren't know ahead of time. For instance queue names. + # it will work with subcols, but all subcols will have the same KEY + if ( $col =~ s!/KEY:([^/]+)!!io ) { + $colref->{'keyname'} = $1; + } + if ( $col =~ s!/STYLE:([^/]+)!!io ) { + $colref->{'style'} = $1; + } + if ( $col =~ s!/CLASS:([^/]+)!!io ) { + $colref->{'class'} = $1; + } + if ( $col =~ s!/TITLE:([^/]+)!!io ) { + $colref->{'title'} = $1; + } + if ( $col =~ s!/ALIGN:([^\/]+)!!io ) { + $colref->{'align'} = $1; + } + if ( $col =~ /__(.*?)__/gio ) { + my @subcols; + while ( $col =~ s/^(.*?)__(.*?)__//o ) { + push ( @subcols, $1 ) if ($1); + push ( @subcols, "__$2__" ); + $colref->{'attribute'} = $2; + } + push ( @subcols, $col ); + @{ $colref->{'output'} } = @subcols; + } + else { + @{ $colref->{'output'} } = ( "__" . $col . "__" ); + $colref->{'attribute'} = $col; + } + + if ( !$colref->{'title'} && grep { /^__(.*?)__$/io } + @{ $colref->{'output'} } ) + { + $colref->{'title'} = $1; + $colref->{'attribute'} = $1; + } + + + push @Columns, $colref; +} + return(@Columns); +</%init> diff --git a/rt/share/html/RTx/Statistics/Elements/CollectionAsTable/Row b/rt/share/html/RTx/Statistics/Elements/CollectionAsTable/Row new file mode 100644 index 000000000..bcfabe5c3 --- /dev/null +++ b/rt/share/html/RTx/Statistics/Elements/CollectionAsTable/Row @@ -0,0 +1,112 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +%# +%# +%# 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> +$i => undef +@Format => undef +$record => undef +$maxitems => undef +$Depth => undef +$Warning => undef +</%ARGS> + +<%PERL> +$m->out('<TR class="' . ( $Warning ? 'warnline' : $i % 2 ? 'oddline' : 'evenline' ) . '" >' ); +my $item; +foreach my $column (@Format) { + if ( $column->{title} eq 'NEWLINE' ) { + while ( $item < $maxitems ) { + $m->out(qq{<td class="collection-as-table"> </td>\n}); + $item++; + } + $item = 0; + $m->out('</TR>'); + $m->out('<TR class="' + . ( $Warning ? 'warnline' : $i % 2 ? 'oddline' : 'evenline' ) + . '" >' ); + next; + } + $item++; + $m->out('<td class="collection-as-table" '); + $m->out( 'align="' . $column->{align} . '"' ) if ( $column->{align} ); + $m->out( 'style="' . $column->{style} . '"' ) if ( $column->{style} ); + $m->out('>'); + foreach my $subcol ( @{ $column->{output} } ) { + if ( $subcol =~ /^__(.*?)__$/o ) { + my $col = $1; + my $value = $m->comp( + '/RTx/Statistics/Elements/StatColumnMap', + Name => $col, + Attr => 'value' + ); + my @out; + + if ( $value && ref($value) ) { + + # All HTML snippets are returned by the callback function + # as scalar references. Data fetched from the objects are + # plain scalars, and needs to be escaped properly. + @out = + map { + ref($_) ? $$_ : $m->interp->apply_escapes( $_ => 'h' ) + } &{$value}( $record, $i, $column->{keyname} ); + ; + } + else { + + # Simple value; just escape it. + @out = $m->interp->apply_escapes( $value => 'h' ); + } + s/\n/<br>/gs for @out; + $m->out( @out ); + } + else { + $m->out($subcol); + } + } + $m->out('</td>'); +} +$m->out('</TR>'); +</%PERL> diff --git a/rt/share/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox b/rt/share/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox new file mode 100644 index 000000000..e2b5c1430 --- /dev/null +++ b/rt/share/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox @@ -0,0 +1,89 @@ +<TD VALIGN="top"> + +<&/Widgets/TitleBoxStart, title => $Title, &> + + <table border="0" cellpadding="1" cellspacing="0"> +% if (defined $ShowStatus) { + <tr> + <td class="collection-as-table" style="text-align:left;">Show Status:</td> + <td COLSPAN=3 class="collection-as-table" style="text-align:left;"> + <& /Elements/SelectStatus, Name=>"status", Default => $Status, DefaultValue => undef &> + </td> + </tr> +% } +% if (defined $ShowSingleQueue) { + <tr> + <td class="collection-as-table" style="text-align:left;">Show Queue:</td> + <td COLSPAN=3 class="collection-as-table" style="text-align:left;"> + <& /Elements/SelectQueue, Name=>"Queue", Default=>$Queue ,ShowNullOption=>0, + CheckQueueRight=>'SeeQueue' &> + </td> + </tr> +% } +% if (defined $ShowDates) { + <tr> + <& /RTx/Statistics/Elements/DateSelectRow, Label => "Start Date:", + refMonth => $sMonth, nameMonth => "sMonth", + refDay => $sDay, nameDay => "sDay", + refYear => $sYear, nameYear => "sYear" &> + </tr> + <tr> + <& /RTx/Statistics/Elements/DateSelectRow, Label => "End Date:", + refMonth => $eMonth, nameMonth => "eMonth", + refDay => $eDay, nameDay => "eDay", + refYear => $eYear, nameYear => "eYear" &> + </tr> + <tr> + <td class="collection-as-table" style="text-align:left;">Show Weekends:</td> + <td class="collection-as-table" style="text-align:left;"> + <select name=weekends> + <option value=0 <% (!$weekends) && 'selected' %> >No</option> + <option value=1 <% $weekends && 'selected' %> >Yes</option> + </select> + </td> + </tr> +% } +% if (defined $ShowMultiQueues) { + <tr> +% if (defined $ShowDates) { +%# If we're showing the dates, we put these side by side. + <td COLSPAN=2 class="collection-as-table" style="text-align:left;">Select All Queues: <input type=checkbox name="AddAllCheck"></td> + <td COLSPAN=3 class="collection-as-table" > + <& /RTx/Statistics/Elements/SelectMultiQueue, Name=>"queues", Selected=>$queues_ref, + ShowNullOption=>0, CheckQueueRight=>'SeeQueue', Size => 10, NamedValues => 1 &> + </td> +% } else { + <td COLSPAN=3 class="collection-as-table" style="text-align:left;"> + <& /RTx/Statistics/Elements/SelectMultiQueue, Name=>"queues", Selected=>$queues_ref, + ShowNullOption=>0, CheckQueueRight=>'SeeQueue', Size => 10, NamedValues => 1 &> + </td> + </tr> + <tr> + <td class="collection-as-table" style="text-align:left;">Select All Queues: <input type=checkbox name="AddAllCheck"></td> +% } + </tr> +% } + <& /RTx/Statistics/Elements/ControlsAsTable/UpdatePage &> + </table> + +<&/Widgets/TitleBoxEnd&> + +</TD></TR></TABLE> +<%args> +$Title => undef +$ShowMultiQueues => undef +$queues_ref => undef +$ShowDates => undef +$sMonth => undef +$sDay => undef +$sYear => undef +$eMonth => undef +$eDay => undef +$eYear => undef +$weekends => undef +$ShowSingleQueue => undef +$Queue => undef +$ShowStatus => undef +$Status => undef +</%args> + diff --git a/rt/share/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage b/rt/share/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage new file mode 100644 index 000000000..dc173d444 --- /dev/null +++ b/rt/share/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage @@ -0,0 +1,5 @@ +<tr> + <td colspan="4" style="text-align:center;padding-top:3px;"> + <INPUT TYPE="submit" VALUE="<&|/l&>Update Page</&>"> + </td> +</tr> diff --git a/rt/share/html/RTx/Statistics/Elements/DateSelectRow b/rt/share/html/RTx/Statistics/Elements/DateSelectRow new file mode 100644 index 000000000..325e168c9 --- /dev/null +++ b/rt/share/html/RTx/Statistics/Elements/DateSelectRow @@ -0,0 +1,55 @@ + <td class="collection-as-table" style="text-align:left;"><% $Label %></td> + <td class="collection-as-table" style="text-align:left;"> + <select name=<% $nameMonth %> > +% for ($n=0;$n<=$#Statistics::months;$n++){ +% if ($$refMonth eq $n){ +% $selected ="selected"; +% }else { +% $selected =""; +% } + <option value=<% $n %> <% $selected %> ><% $Statistics::months[$n] %></option> +%} + </select> + </td> + <td class="collection-as-table" style="text-align:left;"> + <select name=<% $nameDay %> > +% for ($n=1;$n<=31;$n++){ +% if ($$refDay == $n ){ +% $selected ="selected"; +% }else { +% $selected =""; +% } + <option value=<% $n %> <% $selected %> ><% $n %></option> +% } + </select> + </td> + <td class="collection-as-table" style="text-align:left;"> + <select name=<% $nameYear %> > +% +% for ($n=0;$n <= scalar @Statistics::years-1;$n++){ +% if ($Statistics::years[$n] == $$refYear){ +% $selected ="selected"; +% }else{ +% $selected =""; +% } + <option value=<% $Statistics::years[$n] %> <% $selected %> ><% $Statistics::years[$n] %></option> +% } + </select> + </td> + + +<%args> +$Label => undef +$refMonth => undef +$nameMonth => undef +$refDay => undef +$nameDay => undef +$refYear => undef +$nameYear => undef +</%args> +<%init> +use RTx::Statistics; +my $n; +my $selected; + +</%init> diff --git a/rt/share/html/RTx/Statistics/Elements/DurationAsString b/rt/share/html/RTx/Statistics/Elements/DurationAsString new file mode 100755 index 000000000..c0b4d9af4 --- /dev/null +++ b/rt/share/html/RTx/Statistics/Elements/DurationAsString @@ -0,0 +1,18 @@ +<%$days|'00'%> days <%$hours|'00'%>:<%$minutes|'00'%> +<%INIT> + +my $MINUTE = 60; +my $HOUR = $MINUTE*60; +my $DAY = $HOUR * 24; +my $WEEK = $DAY * 7; +my $days = int($Duration / $DAY); +$Duration = $Duration % $DAY; +my $hours = int($Duration / $HOUR); +$hours = sprintf("%02d", $hours); +$Duration = $Duration % $HOUR; +my $minutes = int($Duration/$MINUTE); +$minutes = sprintf("%02d", $minutes); +</%INIT> +<%ARGS> +$Duration => undef +</%ARGS> diff --git a/rt/share/html/RTx/Statistics/Elements/GraphBox b/rt/share/html/RTx/Statistics/Elements/GraphBox new file mode 100644 index 000000000..14c502a9d --- /dev/null +++ b/rt/share/html/RTx/Statistics/Elements/GraphBox @@ -0,0 +1,13 @@ +<TABLE><TR><TD VALIGN="top"> + +<&/Widgets/TitleBoxStart, + title => 'Download Chart as Image', + title_href => $GraphURL +&> +<img src="<% $GraphURL %>" ALT="Result Graph" > +<&/Widgets/TitleBoxEnd&> + +</TD> +<%args> +$GraphURL => undef +</%args> diff --git a/rt/share/html/RTx/Statistics/Elements/SelectMultiQueue b/rt/share/html/RTx/Statistics/Elements/SelectMultiQueue new file mode 100755 index 000000000..637f6dc80 --- /dev/null +++ b/rt/share/html/RTx/Statistics/Elements/SelectMultiQueue @@ -0,0 +1,81 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<SELECT NAME ="<%$Name%>" multiple size="<% $Size %>"> +% if ($ShowNullOption) { +<OPTION VALUE="">-</OPTION> +% } +% while (my $queue=$q->Next) { +% if ($ShowAllQueues || $queue->CurrentUserHasRight($CheckQueueRight)) { +% my $targ="," . $queue->Name . ","; +<OPTION VALUE="<%($NamedValues ? $queue->Name : $queue->Id) %>" <%( ($sel_list =~ m/$targ/) ? 'SELECTED' : '')%>><%$queue->Name%> +% if (($Verbose) and ($queue->Description) ){ +(<%$queue->Description%>) +% } +</OPTION> +% } +% } +</SELECT> +<%ARGS> +$CheckQueueRight => 'CreateTicket' +$ShowNullOption => 1 +$ShowAllQueues => 1 +$Name => undef +$Verbose => undef +$NamedValues => 0 +$Selected => undef # ref to array containing selected queue names +$Lite => 0 +$Size => 5 +</%ARGS> + +<%INIT> + +# put list of queue names into string, starting and ending with commas +my $sel_list = "," . join(",", @$Selected) . ","; + +my $q=new RT::Queues($session{'CurrentUser'}); +$q->UnLimit; + +</%INIT> diff --git a/rt/share/html/RTx/Statistics/Elements/StatColumnMap b/rt/share/html/RTx/Statistics/Elements/StatColumnMap new file mode 100644 index 000000000..aef9e2f3e --- /dev/null +++ b/rt/share/html/RTx/Statistics/Elements/StatColumnMap @@ -0,0 +1,173 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +%# +%# +%# 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 => undef +$Attr => undef +</%ARGS> + + +<%ONCE> +our ( $STAT_COLUMN_MAP ); + +sub StatColumnMap { + my $name = shift; + my $attr = shift; + + # First deal with the simple things from the map + if ( $STAT_COLUMN_MAP->{$name} ) { + return ( $STAT_COLUMN_MAP->{$name}->{$attr} ); + } + + # now, let's deal with harder things, like Custom Fields + + elsif ( $name =~ /^(?:CF|CustomField)\.\{(.+)\}$/ ) { + my $field = $1; + + if ( $attr eq 'attribute' ) { + return (undef); + } + elsif ( $attr eq 'title' ) { + return ( $field ); + } + elsif ( $attr eq 'value' ) { + # Display custom field contents, separated by newlines. + # For Image custom fields we also show a thumbnail here. + return sub { + my $values = $_[0]->CustomFieldValues($field); + return map { + ( + ($_->CustomFieldObj->Type eq 'Image') + ? \($m->scomp( '/Elements/ShowCustomFieldImage', Object => $_ )) + : $_->Content + ), + \'<br>', + } @{ $values->ItemsArrayRef } + }; + } + } +} + +sub LinkCallback { + my $method = shift; + + my $mode = $RT::Ticket::LINKTYPEMAP{$method}{Mode}; + my $type = $RT::Ticket::LINKTYPEMAP{$method}{Type}; + my $mode_uri = $mode.'URI'; + my $local_type = 'Local'.$mode; + + return sub { + map { + \'<A HREF="', + $_->$mode_uri->Resolver->HREF, + \'">', + ( $_->$mode_uri->IsLocal ? $_->$local_type : $_->$mode ), + \'</A><BR>', + } @{ $_[0]->Links($mode,$type)->ItemsArrayRef } + } +} + +$STAT_COLUMN_MAP = { + LastUpdated => { + attribute => 'LastUpdated', + title => 'Last Updated', + value => sub { return $_[0]->LastUpdatedObj->AsString } + }, + + Statistics_Date => { + title => 'Date', + value => sub { return $_[0]{values}{Statistics_Date} } + }, + + Statistics_Created_Count => { + title => 'Created', + value => sub { return $_[0]{values}{Statistics_Created_Count} } + }, + + Statistics_Resolved_Count => { + title => 'Resolved', + value => sub { return $_[0]{values}{Statistics_Resolved_Count} } + }, + + Statistics_Deleted_Count => { + title => 'Deleted', + value => sub { return $_[0]{values}{Statistics_Deleted_Count} } + }, + + Statistics_Totals => { + title => 'Totals', + value => sub { return $_[0]{values}{Statistics_Totals} } + }, + + Statistics_Status => { + title => 'Status', + value => sub { return $_[0]{values}{Statistics_Status} } + }, + + Statistics_Dynamic => { + # Depends on having a KEY as second param + value => sub { + my $record = shift; + my $line = shift; + my $key = shift; + return $$record{values}{$key} + } + }, + + # Everything from LINKTYPEMAP + (map { + $_ => { value => LinkCallback( $_ ) } + } keys %RT::Ticket::LINKTYPEMAP), + + '_CLASS' => { + value => sub { return $_[1] % 2 ? 'oddline' : 'evenline' } + }, + +}; +</%ONCE> +<%init> +$m->comp( '/Elements/Callback', STAT_COLUMN_MAP => $STAT_COLUMN_MAP, _CallbackName => 'StatColumnMap'); +return StatColumnMap($Name, $Attr); +</%init> diff --git a/rt/share/html/RTx/Statistics/Elements/Tabs b/rt/share/html/RTx/Statistics/Elements/Tabs new file mode 100755 index 000000000..4fde113ea --- /dev/null +++ b/rt/share/html/RTx/Statistics/Elements/Tabs @@ -0,0 +1,72 @@ +%# BEGIN LICENSE BLOCK +%# +%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +%# +%# (Except where explictly superceded by other copyright notices) +%# +%# 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. +%# +%# Unless otherwise specified, all modifications, corrections or +%# extensions to this work which alter its source code become the +%# property of Best Practical Solutions, LLC when submitted for +%# inclusion in the work. +%# +%# +%# END LICENSE BLOCK +<& /Elements/Tabs, + tabs => $tabs, + current_toptab => 'RTx/Statistics/index.html', + current_tab => $current_tab, + Title => $Title &> + +<%INIT> + my $tabs = { A => { title => loc('Tickets per Day'), + path => 'RTx/Statistics/CallsQueueDay/index.html', + }, + B => { title => loc('Tickets by status'), + path => 'RTx/Statistics/OpenStalled/index.html', + }, + C => { title => loc('Multiple Queues'), + path => 'RTx/Statistics/CallsMultiQueue/index.html', + }, + D => { title => loc('Ticket Trends by Day'), + path => 'RTx/Statistics/DayOfWeek/index.html', + }, + E => { 'title' => loc('Time to Resolve'), + path => 'RTx/Statistics/Resolution/index.html', + }, + F => { 'title' => loc('Resolve Time Graph'), + path => 'RTx/Statistics/TimeToResolve/index.html', + }, + Z => { 'title' => loc('FAQ'), + path => 'RTx/Statistics/FAQ/index.html', + }, + }; + + # Now let callbacks add their extra tabs + $m->comp('/Elements/Callback', tabs => $tabs, %ARGS); + + foreach my $tab (sort keys %{$tabs}) { + if ($tabs->{$tab}->{'path'} eq $current_tab) { + $tabs->{$tab}->{"subtabs"} = $subtabs; + $tabs->{$tab}->{"current_subtab"} = $current_subtab; + } + } + +</%INIT> + + +<%ARGS> +$subtabs => undef +$current_tab => undef +$current_subtab => undef +$Title => undef +</%ARGS> diff --git a/rt/share/html/RTx/Statistics/FAQ/index.html b/rt/share/html/RTx/Statistics/FAQ/index.html new file mode 100644 index 000000000..e7839eaad --- /dev/null +++ b/rt/share/html/RTx/Statistics/FAQ/index.html @@ -0,0 +1,23 @@ +<& /Elements/Header, Title => 'FAQ and known issues' &> +<& /RTx/Statistics/Elements/Tabs, Title => loc("FAQ and Known Issues") &> +<hr noshade size="1"> +<p>This page will be used to contain known issues and FAQ`s for the Statistics +package<br /> +This will also be used to clarify limitations of the package as they stand.</p> + +<p><strong>What Version of the Statistics package is this?</strong></p> +<p>0.1.8</p> + +<p><strong>What time zone are the charts set to?</strong></p> +<p>Because of the new programming method of the date functions, the charts are currently built in GMT(UTC). This may once again be +customisable in a future release.</p> + +<p><strong>What is the default date period and queue?</strong></p> +<p>The default date period is the previous 10 days, except where the chart is over a fixed 7 day period. The default queue is either +General, or another queue set in your local configuration.</p> + +<p><strong>What are the limitations of the date function?</strong></p> +<p>It has few, but it will not let you chose less than one day. you cannot select an end date before the start date and it is not +recommended to select a date in the future or an illegal date, such at 30th February. Code has been put in place to trap these, but it may +not be fool proof.</p> +<hr size="1" noshade> diff --git a/rt/share/html/RTx/Statistics/OpenStalled/Elements/Chart b/rt/share/html/RTx/Statistics/OpenStalled/Elements/Chart new file mode 100755 index 000000000..e9057ce73 --- /dev/null +++ b/rt/share/html/RTx/Statistics/OpenStalled/Elements/Chart @@ -0,0 +1,27 @@ +<%perl> +$r->content_type("image/$format"); +print $graph->plot(\@data)->$format(); +$m->abort(); +print $#data+1 . " Elements:<p>"; +for (0..$#data) { +print $data[$_]; +print "<p>"; +} +</%perl> +<%INIT> +use GD::Graph::bars; + +my @data; +my $graph = GD::Graph::bars->new($Statistics::GraphWidth||500,$Statistics::GraphHeight||400); +$graph->set(export_format => "png", + x_label => 'Queue name', + y_label => 'Total per queue by status'); +my $format = $graph->export_format; +$graph->set_legend(split /,/ , $ARGS{set_legend}); +push @data, [split /,/ , $ARGS{x_labels}]; +push @data, [split /,/ , $ARGS{data1}]; +push @data, [split /,/ , $ARGS{data2}]; +push @data, [split /,/ , $ARGS{data3}]; +</%INIT> +<%ARGS> +</%ARGS> diff --git a/rt/share/html/RTx/Statistics/OpenStalled/Results.tsv b/rt/share/html/RTx/Statistics/OpenStalled/Results.tsv new file mode 100644 index 000000000..2ec1e0c4a --- /dev/null +++ b/rt/share/html/RTx/Statistics/OpenStalled/Results.tsv @@ -0,0 +1,114 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +%# +%# +%# 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> +@queues => @Statistics::OpenStalledQueueList +</%ARGS> + +<%INIT> +use RTx::Statistics; +use Time::Local; + + my $n = 0; + my @data; + my @msgs; + my %totals; + my $QueryString; + my $now = new RT::Date($session{CurrentUser}); + my $tix = new RT::Tickets($session{'CurrentUser'}); + + my %queues = map { + $_ => 1; + } (@queues); + + # set content type + $r->content_type('application/vnd.ms-excel'); + + $QueryString = "queues=" . join("&queues=", @queues); + + my $queue = new RT::Queues($session{CurrentUser}); + $queue->UnLimit; + + my $QueueObj = new RT::Queue($session{'CurrentUser'}); + $QueueObj->Load($queue); + + # Put out some data about the generation of this file + $m->out("Tickets by Status by Queue for Queues:\t" . join(',', @queues) . "\tGenerated at:\t" . Statistics::FormatDate("%x %X", $now). "\n\n"); + + # basically the same as index.html + + # Output header row + $m->out("Status"); + for ( sort keys %queues) { + push @data, $_; + my $Queueobj = new RT::Queue($session{'CurrentUser'}); + $Queueobj->Load($_); + next if !$Queueobj->CurrentUserHasRight('SeeQueue'); + $m->out("\t" . $_); + } + $m->out("\tTotals\n"); + + foreach my $s (qw(new open stalled)) { + $m->out("$s"); + my $total=0; + foreach my $q (sort keys %queues) { + $tix = new RT::Tickets($session{'CurrentUser'}); + $tix->LimitQueue(VALUE => "$q"); + $tix->LimitStatus(VALUE => "$s"); + $totals{$q} += $tix->Count; # Add up columns for each queue + $m->out("\t" . $tix->Count); + $total += $tix->Count; + } + $m->out("\t$total\n"); + $totals{"Totals"} += $total; + } + $m->out("Totals"); + foreach my $q (sort keys %queues) { + $m->out("\t" . $totals{$q}); + } + $m->out("\t" . $totals{"Totals"} . "\n"); + + $m->abort(); +</%INIT> diff --git a/rt/share/html/RTx/Statistics/OpenStalled/index.html b/rt/share/html/RTx/Statistics/OpenStalled/index.html new file mode 100755 index 000000000..4a1badb7e --- /dev/null +++ b/rt/share/html/RTx/Statistics/OpenStalled/index.html @@ -0,0 +1,183 @@ +<& /Elements/Header, Title => loc('New, Open and Stalled tickets by Queue') &> +<& /RTx/Statistics/Elements/Tabs, Title => loc('New, Open and Stalled tickets by Queue') &> + +<h3>Description</h3> +<p>The purpose of this page is to show a snapshot of the current status of tickets by Queue. You can multi select Queues from the dropdown +list or simply show all available queues. This will indicate how many tickets have not yet been viewed (New), how many have been at least +viewed once (Open) and how many have had their status changed to stalled.</p> + +<form method="POST" action="index.html"> + +%my $tix = new RT::Tickets($session{'CurrentUser'}); +%if ($queue) { +% $tix->LimitQueue (VALUE => $queue); +%} + + +%my $title = "New, Open and Stalled Tickets in " . join(', ', @queues); +<& /Elements/TitleBoxStart, title => $title, title_href => "/RTx/Statistics/OpenStalled/index.html?$QueryString"&> +<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH="100%"> +% if ($ShowHeader) { +<& /RTx/Statistics/Elements/CollectionAsTable/Header, + Format => \@RowFormat, + FormatString => $RowFormat, + AllowSorting => $AllowSorting, + Order => $Order, + Query => undef, + Rows => $Rows, + Page => $Page, + OrderBy => $OrderBy , + BaseURL => $BaseURL, + maxitems => $maxitems &> +% } + +% for ( sort keys %queues_to_show) { +% push @data, $_; +% } +% my @legend; +% my $total = 0; +% my $line = 0; +%# NOTE need to handle all status values (see share/html/Elements/SelectStatus). +% foreach my $s (qw(new open stalled)) { +% $line++; +% push @legend, $s; +% $total=0; +% foreach my $q (sort keys %queues_to_show) { +% $tix = new RT::Tickets($session{'CurrentUser'}); +% $tix->LimitQueue(VALUE => "$q"); +% $tix->LimitStatus(VALUE => "$s"); +% push @data, $tix->Count; +% $totals{$q} += $tix->Count; # Add up columns for each queue +% $total += $tix->Count; +% $values{$q} = $tix->Count; +% } +% $totals{"Totals"} += $total; +% $values{Statistics_Status} = $s; +% $values{Statistics_Totals} = $total; +<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@RowFormat, i => $line, record => $record, maxitems => $maxitems &> +% } +% $values{Statistics_Status} = "Totals"; +% foreach my $q (sort keys %queues_to_show) { +% $values{$q} = $totals{$q}; +% } +% $values{Statistics_Totals} = $totals{"Totals"}; +<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@BoldRowFormat, i => $line+1, record => $record, maxitems => $maxitems &> +</table> +<& /Elements/TitleBoxEnd&> + +% use Data::Dumper; +% Statistics::DebugLog("Dump of data array is " . Dumper(@data) . "\n"); +% my $url = 'Elements/Chart?x_labels='; +% for (1..(scalar keys %queues_to_show)) { +% $url .= $m->interp->apply_escapes((shift @data),'u') . ','; +% } +% chop $url; +% $url .= '&data1=' ; +% for (1..(scalar keys %queues_to_show)) { +% $url .= $m->interp->apply_escapes((shift @data),'u') . ','; +% } +% chop $url; +% $url .= '&data2=' ; +% for (1..(scalar keys %queues_to_show)) { +% $url .= $m->interp->apply_escapes((shift @data),'u') . ','; +% } +% chop $url; +% $url .= '&data3=' ; +% for (1..(scalar keys %queues_to_show)) { +% $url .= $m->interp->apply_escapes((shift @data),'u') . ','; +% } +% $url .= '&set_legend='.(join ",", @legend); + + +<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &> + +<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, Title => "Select Queues", ShowMultiQueues => 1, queues_ref => \@queues &> + +<a href="<%$RT::WebPath%>/RTx/Statistics/OpenStalled/index.html?<% $QueryString %>"><&|/l&>Bookmarkable link</&></a> +%# | <a href="<%$RT::WebPath%>/RTx/Statistics/OpenStalled/Results.tsv?<%$QueryString%>"><&|/l&>spreadsheet</&></a> +<BR> +<BR> + +</FORM> + +% Statistics::DebugInit( $m ); + +<%ARGS> +@queues => @Statistics::OpenStalledQueueList +$AllowSorting => undef +$Order => undef +$OrderBy => undef +$ShowNavigation => 1 +$ShowHeader => 1 +$Rows => 50 +$Page => 1 +$BaseURL => undef +$AddAllCheck => undef +</%ARGS> + +<%INIT> + use RTx::Statistics; + + my $n = 0; + my @data; + my @msgs; + my %totals; + my $QueryString; + my %queues_to_show; + my $maxitems; + my $RowFormat; + my $BoldRowFormat; + my %record; + my %values; + my $record = \%record; + + $record{values} = \%values; + + Statistics::DebugClear(); + + # Handle the Add All Checkbox + if($AddAllCheck eq "on") { + $AddAllCheck = undef; + undef (@queues); + my $q=new RT::Queues($session{'CurrentUser'}); + $q->UnLimit; + while (my $queue=$q->Next) { + next if !$queue->CurrentUserHasRight('SeeQueue'); + push @queues, $queue->Name; + } + } + + # If the user has the right to see the queue, put it into the map + for my $q (@queues) { + my $Queueobj = new RT::Queue($session{'CurrentUser'}); + $Queueobj->Load($q); + next if !$Queueobj->CurrentUserHasRight('SeeQueue'); + $queues_to_show{$q} = 1; + } + + $maxitems = (scalar @queues) + 2; + + # Build the new query string + $QueryString = "queues=" . join("&queues=", @queues); + + # Build the format strings + $RowFormat = "'__Statistics_Status__'"; + $BoldRowFormat = "'<B>__Statistics_Status__</B>'"; + for my $q (@queues) { + $RowFormat .= ",'__Statistics_Dynamic__/KEY:$q/TITLE:$q/STYLE:text-align:right;'"; + $BoldRowFormat .= ",'<B>__Statistics_Dynamic__</B>/KEY:$q/TITLE:$q/STYLE:text-align:right;'"; + } + $RowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'"; + $BoldRowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'"; + # Parse the formats into structures. + my (@RowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $RowFormat); + my (@BoldRowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldRowFormat); + + + my $queue = new RT::Queues($session{CurrentUser}); + $queue->UnLimit; + + my $QueueObj = new RT::Queue($session{'CurrentUser'}); + $QueueObj->Load($queue); + +</%INIT> diff --git a/rt/share/html/RTx/Statistics/Resolution/Elements/Chart b/rt/share/html/RTx/Statistics/Resolution/Elements/Chart new file mode 100755 index 000000000..01196dc9d --- /dev/null +++ b/rt/share/html/RTx/Statistics/Resolution/Elements/Chart @@ -0,0 +1,29 @@ +<%perl> +$r->content_type("image/$format"); +print $graph->plot(\@data)->$format(); +$m->abort(); +print $#data+1 . " Elements:<p>"; +for (0..$#data) { +print $data[$_]; +print "<p>"; +} +</%perl> +<%INIT> +use GD::Graph::lines; + +my @data; +my $graph = GD::Graph::lines->new($Statistics::GraphWidth||500,$Statistics::GraphHeight||400); +$graph->set(export_format => "png", + x_label => 'Days', + y_label => 'Average time in Days'); + +push @data, [split /,/ , $ARGS{x_labels}]; +push @data, [split /,/ , $ARGS{data1}]; +push @data, [split /,/ , $ARGS{data2}]; +push @data, [split /,/ , $ARGS{data3}]; + +my $format = $graph->export_format; +#$r->content_type("image/$format"); +</%INIT> +<%ARGS> +</%ARGS> diff --git a/rt/share/html/RTx/Statistics/Resolution/index.html b/rt/share/html/RTx/Statistics/Resolution/index.html new file mode 100644 index 000000000..838d27392 --- /dev/null +++ b/rt/share/html/RTx/Statistics/Resolution/index.html @@ -0,0 +1,264 @@ +<& /Elements/Header, Title => 'Time to Resolution' &> +<& /RTx/Statistics/Elements/Tabs, Title => loc("Time To Resolve tickets by Queue for : " .$QueueObj->Name()) &> +<h3>Description</h3> +<p>This page shows details of resolution of tickets in the selected queue. It displays tickets created on each day in your selected date +range. Of those tickets created on that day, how many have been resolved and the total time it has taken for all tickets created on that +day to be resolved.</p> +<p>At the bottom of the chart is shows total time taken to resolve all tickets +in the selected date range and the average time per ticket to +resolve.</p> + +<form method="POST" action="index.html"> + +%my $title = "Time to resolve in " . $QueueObj->Name() . " per day from " . +% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[0]) . " through " . +% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$#dates-1]); +<&|/Elements/TitleBox, + title => $title, + title_href => "/RTx/Statistics/Resolution/index.html?$QueryString" &> +<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%> +% if ($ShowHeader) { +<& /RTx/Statistics/Elements/CollectionAsTable/Header, + Format => \@Format, + FormatString => $Format, + AllowSorting => $AllowSorting, + Order => $Order, + Query => undef, + Rows => $Rows, + Page => $Page, + OrderBy => $OrderBy , + BaseURL => $BaseURL, + maxitems => $maxitems &> +% } +% my $line = 1; +% LINE: for my $d (0..$#dates ) { +% if ($d == $#dates ){ +% next LINE; +% } +% my $x = 1; +% $values{Statistics_Date} = Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$d]); +% my $tix = new RT::Tickets($session{'CurrentUser'}); +% $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">="); +% if ($dates[$d+1]) { +% $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<="); +% } +% if ($Queue) { +% $tix->LimitQueue (VALUE => $Queue); +% } +% $values{Statistics_Created_Count} = $tix->Count; +% $tix->LimitStatus(VALUE => "resolved"); +% $values{Statistics_Resolved_Count} = $tix->Count; +% if ($tix->Count) { +% my @tix = @{$tix->ItemsArrayRef}; +% my $total; +% $total += ($_->ResolvedObj->Unix - $_->CreatedObj->Unix) for @tix; +% $size+= ($#tix +1); +% $grandtotal += $total; +% $values{Duration} = Statistics::DurationAsString($total); +% $data[$x++][$d] = int ($total ); +% } else { +% $values{Duration} = "N/A"; +% } +<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@Format, i => $line, record => $record, maxitems => $maxitems &> +% $line++; +%} +% $size =1 if $size==0; +% $values{text} = "Average time to resolve = " . Statistics::DurationAsString($grandtotal / $size); +<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@OneCellFormat, i => $line, record => $record, maxitems => $maxitems &> +% $line++; +% $values{text} = "Total time to resolve = " . Statistics::DurationAsString( $grandtotal ); +<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@OneCellFormat, i => $line, record => $record, maxitems => $maxitems &> +% $line++; +</table> +</&> + +<%perl> +# Create the graph URL + +# change the total time to resolve to a floating point number of days +foreach my $dat(@{$data[1]} ){ + $dat = ($dat / $Statistics::secsPerDay); + $dat = sprintf("%0.4f", $dat); +} + +my $url = 'Elements/Chart?x_labels='; +for (0..$diff-1) { + $url .= $data[0][$_] . ","; +} +chop $url; +shift @data; +$url .= "&data1="; +for(0..$diff-1) { + $data[0][$_] = 0 if !$data[0][$_]; + $url .= $data[0][$_] . ","; +} +</%perl> + +<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &> + +<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, + Title => "Change Queue or Dates", + ShowDates => 1, sMonth => \$sMonth, sDay => \$sDay, sYear => \$sYear, + eMonth => \$eMonth, eDay => \$eDay, eYear => \$eYear, + weekends => $weekends, + ShowSingleQueue => 1, Queue => $Queue + &> + +</form> + +<%ARGS> +$max => $Statistics::TimeToResolveMaxRows +$Queue => undef +$weekends =>$Statistics::TimeToResolveWeekends +$sMonth=>undef +$sDay=>undef +$sYear=>undef +$eMonth=>undef +$eDay=>undef +$eYear=>undef +$days=>undef +$currentMonth=>undef + +$AllowSorting => undef +$Order => undef +$OrderBy => undef +$ShowNavigation => 1 +$ShowHeader => 1 +$Rows => 50 +$Page => 1 +$BaseURL => undef +</%ARGS> + +<%INIT> +use RTx::Statistics; +use Time::Local; +my $n = 0; +my @data = ([]); +my @dates; +my @msgs; +my $size; +my $selected; +my $grandtotal = 0; +my $diff; +my $sEpoch=0; +my $eEpoch=0; +my $QueryString; + +my $maxitems = 4; +my %record; +my %values; +my $record = \%record; + +$record{values} = \%values; + + +# If debugging, set things up and display all the args +Statistics::DebugClear(); +Statistics::DebugLog("CallsQueueDay/index.html ARGS:\n"); +for my $key (keys %ARGS) { + Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n"); +} + +my $Format = qq{ Statistics_Date, + '__Statistics_Created_Count__/STYLE:text-align:right;', + '__Statistics_Resolved_Count__/STYLE:text-align:right;', + '__Statistics_Dynamic__/KEY:Duration/TITLE:Time To Resolve/STYLE:text-align:right;' }; +my $BoldFormat = qq{ '<B>__Statistics_Date__</B>', + '<B>__Statistics_Created_Count__</B>/STYLE:text-align:right;', + '<B>__Statistics_Resolved_Count__</B>/STYLE:text-align:right;', + '<B>__Statistics_Dynamic__</B>/KEY:Duration/TITLE:Time To Resolve/STYLE:text-align:right;' }; + +# TODO need way to make this cell do colspan +my $OneCellFormat = qq{ '<B>__Statistics_Dynamic__</B>/KEY:text/STYLE:text-align:left;','','','' }; + +my (@Format) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $Format); +my (@BoldFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldFormat); +my (@OneCellFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $OneCellFormat); + +Statistics::DebugLog("CallsQueueDay/index.html Format array=" . join(',', @Format) . "\n"); + +if ($sDay > $Statistics::monthsMaxDay{$sMonth}) { + $sDay = $Statistics::monthsMaxDay{$sMonth}; +} + +if ($eDay > $Statistics::monthsMaxDay{$eMonth}) { + $eDay = $Statistics::monthsMaxDay{$eMonth}; +} + +if ($sYear){ + $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900); +} +if ($eYear){ +Statistics::DebugLog("eMonth = " . $eMonth . "\n"); + $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900); +} else { + # This case happens when the page is first loaded + my @local = localtime(time); + ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]); + $eYear += 1900; + $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]); +Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n"); +} + +if (($eEpoch < $sEpoch) || ($sEpoch == 0)) { + # We have an end, but not a start, or, overlapping. + + # if $currentMonth is set, just set the day to 1 + if($currentMonth) { + # set start vars from end, but with day set to 1 + (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch); + $sDay=1; + $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear); + } else { + # If the user has specified how many days back to go, use that, + # If not, set start to configured default period before end + if(defined $days) { + $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay); + } else { + $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay); + } + (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch); + } + $sYear += 1900; +} + +# Compute days to chart. +# The +1 is because we need to generate one more date. If the user +# selected a 10 day range, we need to generate 11 days. +$diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1; +Statistics::DebugLog("Setting diff=$diff\n"); + +Statistics::DebugLog("sEpoch=$sEpoch, components=" . join(',', localtime($sEpoch)) . "\n"); +Statistics::DebugLog("eEpoch=$eEpoch, components=" . join(',', localtime($eEpoch)) . "\n"); + +my $QueueObj = new RT::Queue($session{'CurrentUser'}); +if (!defined $Queue) { + $QueueObj->Load($Statistics::TimeToResolveQueue); + $Queue = $QueueObj->Id(); +} + +# Set up the string for the current query for bookmarkable link +$QueryString = "sDay=$sDay&sMonth=$sMonth&sYear=$sYear&eDay=$eDay&eMonth=$eMonth&eYear=$eYear&weekends=$weekends&Queue=$Queue"; + +# Set up the end date to be midnight(morning) of the date after the one the user wanted. +my $endRange = $eEpoch + $Statistics::secsPerDay; +$QueueObj->Load($Queue); +# NOTE: list loop starts at the end of the date range, unshifting dates onto +# the arrays, so that they end up in start to finish order. +$eEpoch += $Statistics::secsPerDay; +$n = 0; +until ($#dates == $diff ) { + my $date = new RT::Date($session{CurrentUser}); + $date->Set(Value=>$endRange - $n, Format => 'unix'); + # Note: we used to adjust the time to local midnight, but + # none of the other date entry fields in RT seem to adjust, so we've stopped. + #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n"); + $n+= $Statistics::secsPerDay; + # If we aren't showing weekends and this is one, decrement the number + # of days to show and skip to the next date. + if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;} + unshift @dates, $date; +Statistics::DebugLog("pushing date " . Statistics::FormatDate("%c", $date) . "\n"); + unshift @{ $data[0] }, Statistics::FormatDate($Statistics::PerDayLabelDateFormat, $date); +} +</%INIT> diff --git a/rt/share/html/RTx/Statistics/TimeToResolve/Elements/Chart b/rt/share/html/RTx/Statistics/TimeToResolve/Elements/Chart new file mode 100755 index 000000000..a069a7bfb --- /dev/null +++ b/rt/share/html/RTx/Statistics/TimeToResolve/Elements/Chart @@ -0,0 +1,23 @@ +<%perl> +print $graph->plot(\@data)->$format(); +$m->abort(); +</%perl> +<%INIT> +use GD::Graph::points; + +my @data; +my $graph = GD::Graph::points->new(400,300); +$graph->set(export_format => "png", + marker_size => $ARGS{marker_size}, + x_label => 'Average time to resolve (Days)', + y_label => 'Number of tickets resolved' ); +#$r->content_type("image/$format"); +my $format = $graph->export_format; +push @data, [split /,/ , $ARGS{x_labels}]; +for (1..((scalar keys %ARGS)-2)) { + push @data, [split /,/ , $ARGS{"data".$_}]; +} + +</%INIT> +<%ARGS> +</%ARGS> diff --git a/rt/share/html/RTx/Statistics/TimeToResolve/index.html b/rt/share/html/RTx/Statistics/TimeToResolve/index.html new file mode 100755 index 000000000..ad3e23ace --- /dev/null +++ b/rt/share/html/RTx/Statistics/TimeToResolve/index.html @@ -0,0 +1,70 @@ +<& /Elements/Header, Title => 'Time to Resolve in Queue' &> +<& /RTx/Statistics/Elements/Tabs, Title => 'Time to Resolve, by ticket in Queue:' . $QueueObj->Name() &> + +<h3>Description</h3> +<p>This page displays the same information as the Time to Resolve chart, but in a scattergraph format and only for the previous 7 calendar +days. It only displays data for tickets which have been resolved. Each division on the Days axis is one day and the granularity of this chart +is 30 minutes.</p> + +<form method="POST"> + +% my $url = 'Elements/Chart?x_labels='; +% my $i; +% $url .= join ",", (map {(int($_/2) == $_/2 && (++$i)%2) ? $_/2 : ""} grep {$counts[$_]} 0..($#counts-1)), "longer"; +% $url .= '&'; +% $url .= "marker_size=1&"; +% $url .= "data1=".(join ",", map { $_ || () } @counts)."&"; +% chop $url; +<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &> + +<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, + Title => "Change Queue", + ShowSingleQueue => 1, Queue=>$Queue, +&> + +</form> + +%Statistics::DebugInit($m); + +<%ARGS> +$Queue => undef +</%ARGS> + +<%INIT> +use RTx::Statistics; + +my @days = qw(Sun Mon Tue Wed Thu Fri Sat); +my $n = 0; +my @data = ([]); +my @msgs; +my @counts; + +Statistics::DebugClear(); +Statistics::DebugLog("TimeToResolve/index.html ARGS:\n"); +for my $key (keys %ARGS) { + Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n"); +} + +my $QueueObj = new RT::Queue($session{'CurrentUser'}); +#if (!defined $queue) { +# $QueueObj->Load($Statistics::TimeToResolveGraphQueue); +# $queue = $QueueObj->Id(); +#} else { + $QueueObj->Load($Queue); +#} + + +my $tix = new RT::Tickets($session{'CurrentUser'}); +$tix->LimitQueue (VALUE => $Queue) if $Queue; +$tix->LimitStatus(VALUE => "resolved"); +$tix->UnLimit; +if ($tix->Count) { + while (my $t = $tix->RT::SearchBuilder::Next) { # BLOODY HACK + my $when = $t->ResolvedObj->Unix - $t->CreatedObj->Unix; + next unless $when > 0; # Doubly bloody hack + my $max = (60*60*24*2) / 1800; + my $x = int($when / 1800); + $counts[$x > $max ? $max : $x]++; + } +} +</%INIT> diff --git a/rt/share/html/RTx/Statistics/UserTest/Elements/Chart b/rt/share/html/RTx/Statistics/UserTest/Elements/Chart new file mode 100755 index 000000000..99eb2a2b1 --- /dev/null +++ b/rt/share/html/RTx/Statistics/UserTest/Elements/Chart @@ -0,0 +1,28 @@ +<%perl> +print $graph->plot(\@data)->$format(); +$m->abort(); +print $#data+1 . " Elements:<p>"; +for (0..$#data) { +print $data[$_]; +print "<p>"; +} +</%perl> +<%INIT> +use GD::Graph::lines; + +my @data; +my $graph = GD::Graph::lines->new(640,480); +$graph->set(export_format => "png", + x_label => 'Days', + y_label => 'Average time in Days'); + +push @data, [split /,/ , $ARGS{x_labels}]; +push @data, [split /,/ , $ARGS{data1}]; +push @data, [split /,/ , $ARGS{data2}]; +push @data, [split /,/ , $ARGS{data3}]; + +my $format = $graph->export_format; +#$r->content_type("image/$format"); +</%INIT> +<%ARGS> +</%ARGS> diff --git a/rt/share/html/RTx/Statistics/UserTest/index.html b/rt/share/html/RTx/Statistics/UserTest/index.html new file mode 100755 index 000000000..7bc25da70 --- /dev/null +++ b/rt/share/html/RTx/Statistics/UserTest/index.html @@ -0,0 +1,54 @@ +<& /Elements/Header, Title => 'Time to Resolve in Queue' &> +<& /RTx/Statistics/Elements/Tabs, Title => 'Time to Resolve, by ticket in Queue:' . $QueueObj->Name() &> + + +<form method="POST"> + +See Queue:<BR> +<& /Elements/SelectQueue, Name=>"queue", Default => "$queue" &> +<BR> +<INPUT TYPE="submit" VALUE="Go!"</INPUT> +</form> + +<BR> +% my $url = 'Elements/Chart?x_labels='; +% my $i; +% $url .= join ",", (map {(int($_/2) == $_/2 && (++$i)%2) ? $_/2 : ""} grep {$counts[$_]} 0..($#counts-1)), "longer"; +% $url .= '&'; +% $url .= "marker_size=1&"; +% $url .= "data1=".(join ",", map { $_ || () } @counts)."&"; +% chop $url; +<IMG SRC="<% $url %>"> + +<BR> + +<%ARGS> +$queue => $Statistics::TimeToResolveGraphQueue; +</%ARGS> + +<%INIT> +use RTx::Statistics; + +my @days = qw(Sun Mon Tue Wed Thu Fri Sat); +my $n = 0; +my @data = ([]); +my @msgs; +my @counts; + +my $QueueObj = new RT::Queue($session{'CurrentUser'}); +$QueueObj->Load($queue); + +my $tix = new RT::Tickets($session{'CurrentUser'}); +$tix->LimitQueue (VALUE => $queue) if $queue; +$tix->LimitStatus(VALUE => "resolved"); +$tix->UnLimit; +if ($tix->Count) { + while (my $t = $tix->RT::SearchBuilder::Next) { # BLOODY HACK + my $when = $t->ResolvedObj->Unix - $t->CreatedObj->Unix; + next unless $when > 0; # Doubly bloody hack + my $max = (60*60*24*2) / 1800; + my $x = int($when / 1800); + $counts[$x > $max ? $max : $x]++; + } +} +</%INIT> diff --git a/rt/share/html/RTx/Statistics/index.html b/rt/share/html/RTx/Statistics/index.html new file mode 100755 index 000000000..41490de18 --- /dev/null +++ b/rt/share/html/RTx/Statistics/index.html @@ -0,0 +1,59 @@ +%# BEGIN LICENSE BLOCK +%# +%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +%# +%# (Except where explictly superceded by other copyright notices) +%# +%# Copyright this file (c) 2003 Harald Wagener <hwagener@hamburg.fcb.com> +%# +%# 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. +%# +%# Unless otherwise specified, all modifications, corrections or +%# extensions to this work which alter its source code become the +%# property of Best Practical Solutions, LLC when submitted for +%# inclusion in the work. +%# +%# +%# END LICENSE BLOCK +<& /Elements/Header, Title => loc('RT Statistics') &> +<& /RTx/Statistics/Elements/Tabs, Title => loc('RT Statistics') &> + +<&|/l&><h2>Description</h2> +<p>These 6 options below enable you to display management data from the RT Database in table and graphical forms, enabling trends, bottlenecks, load problems etc to be identified. +Each contains a description of how the data is displayed and describes the options available to you.</p></&> +<ul> +<li><strong><a href="CallsQueueDay/index.html"> +<&|/l&>Tickets per day per Queue</&></a></strong><br /> +<&|/l&>View the number of tickets created, resolved or deleted in a<br /> specific Queue, over the requested period of days</&> +</li> +<li><strong><a href="OpenStalled/index.html"> +<&|/l&>Tickets status by Queue</&></a></strong><br> +<&|/l&>View numbers of new, open and stalled tickets in a selected Queue</&> +</li> +<li><strong><a href="CallsMultiQueue/index.html"> +<&|/l&>Tickets per Day in Multiple Queues</&> +</a></strong><br> +<&|/l&>View tickets created, resolved or deleted on in one or more Queues<br /> over a specified time period</&> +</li> +<li><strong><a href="DayOfWeek/index.html"> +<&|/l&>Tickets per Day of Week (absolute)</&></a></strong><br> +<&|/l&>View trends showing when tickets are created, resolved or deleted</&> +</li> +<li><strong><a href="Resolution/index.html"> +<&|/l&>Time to Resolve</&></a></strong><br> +<&|/l&>View how long tickets take to be resolved by Queue</&> +</li> +</li> +<li><strong><a href="TimeToResolve/index.html"> +<&|/l&>Time to Resolve (scatter graph)</&></a></strong><br> +<&|/l&>View a detailed scatter graph of time to resolve tickets by Queue</&> +</li> +</ul> diff --git a/rt/share/html/Search/Build.html b/rt/share/html/Search/Build.html index 7b105e440..9507a2dbb 100644 --- a/rt/share/html/Search/Build.html +++ b/rt/share/html/Search/Build.html @@ -85,9 +85,9 @@ <div id="pick-criteria"> <& Elements/PickCriteria, query => $query{'Query'}, cfqueues => $queues &> -</div> <& /Elements/Submit, Label => loc('Add these terms'), Name => 'AddClause'&> <& /Elements/Submit, Label => loc('Add these terms and Search'), Name => 'DoSearch'&> +</div> <div id="editquery"> diff --git a/rt/share/html/Search/Bulk.html b/rt/share/html/Search/Bulk.html index e2b757499..4a510ce97 100755 --- a/rt/share/html/Search/Bulk.html +++ b/rt/share/html/Search/Bulk.html @@ -111,7 +111,13 @@ <tr><td class="label"> <&|/l&>Make subject</&>: </td> <td class="value"> <input name="Subject" size="20" value="<% $ARGS{Subject} || '' %>"/> </td></tr> <tr><td class="label"> <&|/l&>Make priority</&>: </td> -<td class="value"> <& /Elements/SelectPriority, Name => "Priority", Default => $ARGS{Priority} &> </td></tr> +% my $rel = ($ARGS{Priority} =~ s/^R//); +<td class="value"> <& /Elements/SelectPriority, Name => "Priority", Default => $ARGS{Priority} &> +<select name="Priority-Mode"> +<option value="absolute" <% !$rel && 'selected' %>>absolute</option> +<option value="relative" <% $rel && 'selected' %>>relative</option> +</select> +</td></tr> <tr><td class="label"> <&|/l&>Make queue</&>: </td> <td class="value"> <& /Elements/SelectQueue, Name => "Queue", Default => $ARGS{Queue} &> </td></tr> <tr><td class="label"> <&|/l&>Make Status</&>: </td> @@ -283,7 +289,7 @@ $Page ||= 1; $Format ||= RT->Config->Get('DefaultSearchResultFormat'); # inject _CHECKBOX to the first field. -$Format =~ s/'?([^']+)'?,/'___CHECKBOX__$1',/; +$Format =~ s/'?([^']+)'?,/'___CHECKBOX__$1',/; #' my $Tickets = RT::Tickets->new( $session{'CurrentUser'} ); $Tickets->FromSQL($Query); @@ -332,6 +338,13 @@ my $cf_del_keys; my $cf_add_keys; @$cf_add_keys = grep { /^Bulk-Add-CustomField/ } keys %ARGS; +if ( defined($ARGS{'Priority'}) + and ($ARGS{'Priority-Mode'} || '') eq 'relative' ) { + # magic in Ticket::SetPriority + $ARGS{'Priority'} = 'R'.$ARGS{'Priority'}; +} +delete $ARGS{'Priority-Mode'}; + unless ( $ARGS{'AddMoreAttach'} ) { # Add session attachments if any to be processed by ProcessUpdateMessage $ARGS{'UpdateAttachments'} = $session{'Attachments'} if ( $session{'Attachments'} ); @@ -428,13 +441,20 @@ unless ( $ARGS{'AddMoreAttach'} ) { } } } - my @tempresults = ( + my @statusresults = + ProcessTicketStatus( TicketObj => $Ticket, ARGSRef => \%ARGS ); + + my @tempresults = ( @watchresults, @basicresults, @dateresults, - @updateresults, @linkresults, @cfresults + @updateresults, @linkresults, @cfresults, + @statusresults ); @tempresults = - map { loc( "Ticket [_1]: [_2]", $Ticket->Id, $_ ) } @tempresults; + map { + $_ =~ /^Ticket \d+:/ ? $_ : + loc( "Ticket [_1]: [_2]", $Ticket->Id, $_ ) + } @tempresults; @results = ( @results, @tempresults ); } diff --git a/rt/share/html/Search/Calendar.html b/rt/share/html/Search/Calendar.html new file mode 100644 index 000000000..9d2b6f546 --- /dev/null +++ b/rt/share/html/Search/Calendar.html @@ -0,0 +1,238 @@ +<%args> +$Month => (localtime)[4] +$Year => (localtime)[5] + 1900 +$Query => undef +$Format => undef +$Order => undef +$OrderBy => undef +$RowsPerPage => undef +$NewQuery => 0 +</%args> + +<& /Elements/Header, Title => $title &> +<& /Ticket/Elements/Tabs, + current_tab => "Search/Calendar.html?$QueryString", + Title => $title &> +<&| /Widgets/TitleBox, + title => loc('Calendar for ') . $rtdate->GetMonth($Month) . " $Year" , + title_class=> 'inverse', + color => "#993333" &> + +<table width="100%"> +<tr> +<td align="left"> +% my ($PMonth, $PYear) = ($Month - 1, $Year); +% if ($PMonth < 0) { +% $PYear--; +% $PMonth = 11; +% } +<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">«<%$rtdate->GetMonth($PMonth)%></a> +</td> +<th align="center"> + <font size="+1"><% $rtdate->GetMonth($Month). " $Year" %></font> +</th> +<td align="right"> +% my ($NMonth, $NYear) = ($Month + 1, $Year); +% if ($NMonth > 11) { +% $NYear++; +% $NMonth = 0; +% } +<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%>»</a> +</td> +</tr> +</table> + +<table class="rtxcalendar"> + +<thead> +<tr> +% for ( @{$week{$weekstart}} ) { +<th width="14%"><%$rtdate->GetWeekday($_)%></th> +% } +</tr> +</thead> + +<tbody> +<tr> +% while ($date <= $end) { +% +% my $offmonth = $date->month != ($Month + 1); +% my $is_today = (DateTime->compare($today, $date) == 0); +% my $is_yesterday = (DateTime->compare($yesterday, $date) == 0); +% my $is_aweekago = (DateTime->compare($aweekago, $date) == 0); + + <td class="<% $offmonth ? 'offmonth' + : $is_today ? 'today' + : $is_yesterday ? 'yesterday' + : $is_aweekago ? 'aweekago' + : '' + %>" + > + <div class="<% $is_today ? 'todays' + : $offmonth ? 'offmonth' + :'' %>calendardate" + ><%$date->day%></div> + +% my $sp = 3; +% for my $t ( @{ $Tickets{$date->strftime("%F")} } ) { +% $sp--; + <& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &> +% } + <% ($sp>0) ? '<BR>'x$sp : '' |n %> + + </td> + +% $date = $set->next($date); +% if ( $date->day_of_week == $startday_of_week ) { + </tr><tr> +% } + +% } +</tr> +</tbody> +</table> + +<table width="100%"> +<tr> +<td align="left"> +<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">«<%$rtdate->GetMonth($PMonth)%></a> +</td> + +<td valign="top" align="center"> +<form action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post"> + +<select name="Month"> +% for (0..11) { +<option value="<%$_%>" <% $_ == $Month ? 'selected' : ''%> ><%$rtdate->GetMonth($_)%></option> +% } +</select> +% my $year = (localtime)[5] + 1900; +<select name="Year"> +% for ( ($year-5) .. ($year+5)) { +<option value="<%$_%>" <% $_ == $Year ? 'selected' : ''%>><%$_%></option> +% } +</select> + +%# <& /Elements/Submit&> +<input type="submit" value="<% loc('Submit') %>" class="button" /> + +</form> +</td> + +<td align="right"> +<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%>»</a> +</td> +</tr> +</table> + +<table width="100%"> +<tr> + +<td valign="top" rowspan=9> + <BR> + <a href="<%$RT::WebPath%>/Prefs/Calendar.html">Calendar Preferences and Help</a> +</td> + +% foreach my $legend (keys %legend) { + <tr> + <td align="right"> + <img src="<%$RT::WebImagesURL%>/<%$legend%>.png" /> + </td> + <td align="left"> +% my $more = 0; +% foreach ( @{$legend{$legend}} ) { + <% $more++ ? ', ' : '' %> + <&|/l&><% $_ %></&> +% } + </td> + </tr> +% } + +</table> + +</&> + +<%ONCE> + +my %legend = ( + 'created' => ['Created'], + 'due' => ['Due'], + 'resolved' => ['Resolved'], + 'updated' => ['Last Updated'], + 'created_due' => ['Created','Due'], + 'reminder' => ['Reminders'], + 'started' => ['Started'], + 'starts_due' => ['Starts','Due'], +); + +</%ONCE> +<%INIT> +use RTx::Calendar qw(FirstDay LastDay); + +my $title = loc("Calendar"); + +my @DateTypes = qw/Created Starts Started Due LastUpdated Resolved/; + +my $rtdate = RT::Date->new($session{'CurrentUser'}); + +my $weekstart = 'Sunday'; #RT::SiteConfig? user pref? +my %week = ( + 'Saturday' => [6,0..5], + 'Sunday' => [0..6], + 'Monday' => [1..6,0], +); +my $startday_of_week = ${$week{$weekstart}}[0] || 7; +my $endday_of_week = ${$week{$weekstart}}[-1] || 7; + +my $today = DateTime->today; +my $yesterday = $today->clone->subtract( days=>1 ); +my $aweekago = $today->clone->subtract( days=>7 ); +my $date = FirstDay($Year, $Month + 1, $startday_of_week ); +my $end = LastDay ($Year, $Month + 1, $endday_of_week ); + +# use this to loop over days until $end +my $set = DateTime::Set->from_recurrence( + next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) } +); + +my $QueryString = + $m->comp( + '/Elements/QueryString', + Query => $Query, + Format => $Format, + Order => $Order, + OrderBy => $OrderBy, + Rows => $RowsPerPage + ) + if ($Query); + +$QueryString ||= 'NewQuery=1'; + +# Default Query and Format +my $TempFormat = "__Starts__ __Due__"; +my $TempQuery = "( Status = 'new' OR Status = 'open' OR Status = 'stalled') + AND ( Owner = '" . $session{CurrentUser}->Id ."' OR Owner = 'Nobody' ) + AND ( Type = 'reminder' OR 'Type' = 'ticket' )"; + +if ( my $Search = RTx::Calendar::SearchDefaultCalendar($session{CurrentUser}) ) { + $TempFormat = $Search->SubValue('Format'); + $TempQuery = $Search->SubValue('Query'); +} + +# we overide them if needed +$TempQuery = $Query if $Query; +$TempFormat = $Format if $Format; + +# we search all date types in Format string +my @Dates = grep { $TempFormat =~ m/__${_}(Relative)?__/ } @DateTypes; + +# used to display or not a date in Element/CalendarEvent +my %DateTypes = map { $_ => 1 } @Dates; + +$TempQuery .= RTx::Calendar::DatesClauses(\@Dates, $date->strftime("%F"), $end->strftime("%F")); + +# print STDERR ("-" x 30), "\n", $TempQuery, "\n"; + +my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F")); + +</%INIT> diff --git a/rt/share/html/Search/Elements/BuildFormatString b/rt/share/html/Search/Elements/BuildFormatString index 89ac642a7..dc07c683b 100644 --- a/rt/share/html/Search/Elements/BuildFormatString +++ b/rt/share/html/Search/Elements/BuildFormatString @@ -71,6 +71,9 @@ $CurrentDisplayColumns => undef # All the things we can display in the format string by default my @fields = qw( id QueueName Subject + + Customer Agent CustomerClass CustomerTags + Status ExtendedStatus UpdateStatus Type @@ -96,6 +99,7 @@ my @fields = qw( Bookmark NEWLINE + ); # loc_qw $m->callback( CallbackOnce => 1, CallbackName => 'SetFieldsOnce', Fields => \@fields ); diff --git a/rt/share/html/Search/Elements/DisplayOptions b/rt/share/html/Search/Elements/DisplayOptions index f69fb2630..c83035884 100644 --- a/rt/share/html/Search/Elements/DisplayOptions +++ b/rt/share/html/Search/Elements/DisplayOptions @@ -115,6 +115,8 @@ $fields{$_}=1 for @cfs; # Add PAW sort $fields{'Custom.Ownership'} = 1; +$fields{"Customer.$_"} = 1 foreach qw( Number Name ); #Freeside + my @Order = split /\|/, $Order; my @OrderBy = split /\|/, $OrderBy; if ($Order =~ /\|/) { diff --git a/rt/share/html/Search/Elements/PickCFs b/rt/share/html/Search/Elements/PickCFs index 452014779..9abab4443 100644 --- a/rt/share/html/Search/Elements/PickCFs +++ b/rt/share/html/Search/Elements/PickCFs @@ -78,20 +78,41 @@ while ( my $CustomField = $CustomFields->Next ) { my %line; $line{'Name'} = "'CF.{" . $CustomField->Name . "}'"; $line{'Field'} = $CustomField->Name; - $line{'Op'} = { - Type => 'component', - Path => '/Elements/SelectCustomFieldOperator', - Arguments => { True => loc("is"), - False => loc("isn't"), - TrueVal=> '=', - FalseVal => '!=', - }, - }; - $line{'Value'} = { - Type => 'component', - Path => '/Elements/SelectCustomFieldValue', - Arguments => { CustomField => $CustomField }, - }; + + # Op + if ($CustomField->Type eq 'Date') { + $line{'Op'} = { + Type => 'component', + Path => '/Elements/SelectDateRelation', + Arguments => {}, + }; + } else { + $line{'Op'} = { + Type => 'component', + Path => '/Elements/SelectCustomFieldOperator', + Arguments => { True => loc("is"), + False => loc("isn't"), + TrueVal=> '=', + FalseVal => '!=', + }, + }; + } + + # Value + if ($CustomField->Type eq 'Date') { + $line{'Value'} = { + Type => 'component', + Path => '/Elements/SelectDate', + Arguments => {}, + }; + } else { + $line{'Value'} = { + Type => 'component', + Path => '/Elements/SelectCustomFieldValue', + Arguments => { CustomField => $CustomField }, + }; + } + push @lines, \%line; } diff --git a/rt/share/html/Search/Elements/ResultViews b/rt/share/html/Search/Elements/ResultViews index c146e6736..9ddbd1359 100644 --- a/rt/share/html/Search/Elements/ResultViews +++ b/rt/share/html/Search/Elements/ResultViews @@ -57,10 +57,12 @@ $ShortQueryString => undef </%args> <ul class="search-result-views"> -<li><a href="<%RT->Config->Get('WebPath')%>/Search/Results.tsv<%$QueryString%>"><&|/l&>Spreadsheet</&></a></li> <li><a href="<%$RSSFeedURL%>"><&|/l&>RSS</&></a></li> <li><a href="<%RT->Config->Get('WebURL')%>/NoAuth/iCal/<% $ical_path %>"><% loc('iCal') %></a></li> <li><a href="<%RT->Config->Get('WebPath')%>/Tools/Offline.html<%$ShortQueryString%>"><&|/l&>Editable text</&></a></li> +<li><a href="<%RT->Config->Get('WebPath')%>/Search/Results.tsv<%$QueryString%>"><&|/l&>TSV</&></a></li> +<li><a href="<%RT->Config->Get('WebPath')%>/Search/Results.csv<%$QueryString%>"><&|/l&>CSV</&></a></li> +<li><a href="<%RT->Config->Get('WebPath')%>/Search/Results.xls<%$QueryString%>"><&|/l&>XLS</&></a></li> % # Now let callbacks add their extra tools % $m->callback( %ARGS, CallbackName => 'AfterTools' ); </ul> diff --git a/rt/share/html/Search/Elements/ResultsStructuredView b/rt/share/html/Search/Elements/ResultsStructuredView new file mode 100644 index 000000000..495f0d0c8 --- /dev/null +++ b/rt/share/html/Search/Elements/ResultsStructuredView @@ -0,0 +1,173 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<%ARGS> +$Query => undef +$OrderBy => 'id' +$Order => 'ASC' +$Format => undef + +#Callbacks +$WriteHeader => sub { $RT::Logger->error('WriteHeader callback required'); '' } +$WriteRow => sub { $RT::Logger->error('WriteRow callback required'); '' } +$FormatDate => sub { $_[0]->AsString } +</%ARGS> +<%INIT> + +use HTML::TreeBuilder; +use HTML::FormatText; + +my $Tickets = RT::Tickets->new( $session{'CurrentUser'} ); +$Tickets->FromSQL( $Query ); +if ( $OrderBy =~ /\|/ ) { + # Multiple Sorts + my @OrderBy = split /\|/, $OrderBy; + my @Order = split /\|/, $Order; + $Tickets->OrderByCols( + map { { FIELD => $OrderBy[$_], ORDER => $Order[$_] } } + ( 0 .. $#OrderBy ) + ); +} +else { + $Tickets->OrderBy( FIELD => $OrderBy, ORDER => $Order ); +} + +# Convert the format string to column info +$Format = $m->comp('/Elements/ScrubHTML', Content => $Format); +my @Format = $m->comp('/Elements/CollectionAsTable/ParseFormat', + Format => $Format); + +# Generate the header row +my $item = 0; +my @header; +foreach my $column (@Format) { +# see /Element/CollectionAsTable/Header + my $title = $column->{'title'} || ''; + if ( $title eq 'NEWLINE' ) { + # ignore these for now + undef $column; + next; + } + $title = '' if $title eq 'NBSP'; + + if ( !defined $column->{'title'} ) { + my $attr = $column->{'attribute'} || $column->{'last_attribute'}; + my $tmp = $m->comp( '/Elements/ColumnMap', + Class => 'RT__Ticket', + Name => $attr, + Attr => 'title' + ); + $title = ProcessColumnMapValue($tmp, Arguments => [ $attr ]); + } + + push @header, $title; + +} #foreach $column + +&{ $WriteHeader }(@header); + +my $plaintext = HTML::FormatText->new; +my $row = 1; +my $ColumnMap = {}; +while ( my $Ticket = $Tickets->Next()) { + my $height = 0; + my @row = (); + foreach my $column ( @Format ) { + next if !defined $column; + + my $value = ''; + my @out; + # Ignore almost all formatting here. + foreach my $subcol ( @{ $column->{output} } ) { + my ($col) = ($subcol =~ /^__(.*?)__$/ ); + + if ( !$col ) { + push @out, $subcol; + next; + } + + if ( !exists $ColumnMap->{$col}{'value'} ) { + my $map = {}; + foreach ('attribute', 'value', 'date') { + $map->{$_} = $m->comp( + '/Elements/ColumnMap', + Class => 'RT__Ticket', + Name => $col, + Attr => $_, + ); + } + # Canonicalize dates + if ( defined $map->{'date'} ) { + $map->{value} = sub { + my $DateObj = $map->{'date'}->(@_) or return undef; + $FormatDate->($DateObj); + }; + } + $ColumnMap->{$col} = $map; + } + + push @out, ProcessColumnMapValue( + $ColumnMap->{$col}{'value'}, + Arguments => [ $Ticket, $row ], + ); + } #foreach $subcol + $value = join('', '<span>', @out, '</span>'); + # strip out all HTML except line breaks + my $tree = HTML::TreeBuilder->new->parse($value); + my $text = $plaintext->format($tree); + # along with leading/trailing whitespace + my @lines = map { s/^\s*//; s/\s*$//; $_ } split("\n", $text); + + # then provide separate lines to the callback + push @row, \@lines; + } #foreach $column + + &{ $WriteRow }(@row); + +} # while $Tickets->Next + +</%INIT> diff --git a/rt/share/html/Search/Results.csv b/rt/share/html/Search/Results.csv new file mode 100644 index 000000000..f9b4e97c3 --- /dev/null +++ b/rt/share/html/Search/Results.csv @@ -0,0 +1,90 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<%ARGS> +$Query => undef +$OrderBy => 'id' +$Order => 'ASC' +$Format => undef +</%ARGS> +<%INIT> + +use Text::CSV_XS; #does anyone not have this? + +$r->content_type('text/csv'); +$r->header_out('Content-Disposition' => 'attachment;filename="Results.csv"'); + +my $csv = Text::CSV_XS->new( { eol => "\n" } ); + +my $WriteHeader = sub { + my @header = @_; + $csv->combine(@header); + $m->out($csv->string); +}; + +my $WriteRow = sub { + my @row = @_; + $csv->combine( + map { join('; ', @$_) } @row + ); + $m->out($csv->string); +}; + +$m->comp('Elements/ResultsStructuredView', + Query => $Query, + OrderBy => $OrderBy, + Order => $Order, + Format => $Format, + WriteHeader => $WriteHeader, + WriteRow => $WriteRow, + FormatDate => sub { $_[0]->AsString(Format => 'ISO') }, +); + +$m->flush_buffer; +$m->abort(); + +</%INIT> diff --git a/rt/share/html/Search/Results.tsv b/rt/share/html/Search/Results.tsv index 884628437..cdf1ebad0 100644 --- a/rt/share/html/Search/Results.tsv +++ b/rt/share/html/Search/Results.tsv @@ -95,7 +95,8 @@ my @attrs = qw( ResolvedObj->ISO LastUpdatedObj->ISO LastUpdatedByObj->Name ); -$r->content_type('application/vnd.ms-excel'); +$r->content_type('text/tab-separated-values'); +$r->header_out('Content-Disposition' => 'attachment;filename="Results.tsv"'); { my @header; foreach my $attr (@attrs) { diff --git a/rt/share/html/Search/Results.xls b/rt/share/html/Search/Results.xls new file mode 100644 index 000000000..52a05daed --- /dev/null +++ b/rt/share/html/Search/Results.xls @@ -0,0 +1,148 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# <jesse@bestpractical.com> +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<%ARGS> +$Query => undef +$OrderBy => 'id' +$Order => 'ASC' +$Format => undef +</%ARGS> +<%INIT> + +use Spreadsheet::WriteExcel; +use List::Util qw( max ); +use Date::Format qw( time2str ); + +$r->content_type('application/vnd.ms-excel'); +$r->header_out('Content-Disposition' => 'attachment;filename="Results.xls"'); + +my $xls; +my $fh; +open ($fh, ">", \$xls) or die "$!"; +my $workbook = Spreadsheet::WriteExcel->new($fh) or die $!; +my $worksheet = $workbook->add_worksheet(); + +my $default_format = $workbook->add_format( + color => 'black', + size => 10, +); +my $date_format = $workbook->add_format( + color => 'black', + size => 10, + num_format => 'YYYY-MM-DD', #configurable? +); +my $title_format = $workbook->add_format( + color => 'black', + size => 10, + bold => 1, + bg_color=> 'silver', +); + +my @width; +my $row = 1; + +my $WriteHeader = sub { + my @header = @_; + @width = (); + for ( my $item = 0; $item < scalar @header; $item++ ) { + my $title = $header[$item]; + $worksheet->write(0, $item, $title, $title_format); + $width[$item] = 1.1 * length($title); + } +}; + +my $WriteRow = sub { + my @row = @_; + my $height = 0; + for ( my $item = 0; $item < scalar @row; $item++ ) { + my @lines = @{ $row[$item] }; + # record row/column sizes + $height = max( $height, scalar(@lines) ); + $width[$item] = max( $width[$item], map {length} @lines ); + + if ( scalar(@lines) == 1 and + $lines[0] =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/ ) { + + $worksheet->write_date_time($row, $item, $lines[0], $date_format); + next; + } + + $worksheet->write($row, $item, join("\n", @lines), $default_format); + } + $worksheet->set_row($row, $height * 14); + $row++; +}; + +my $FormatDate = sub { + my $DateObj = shift; + return '' if $DateObj->Unix == 0; + return time2str('%Y-%m-%dT%H:%M', $DateObj->Unix); +}; + +# Write everything to the worksheet +$m->comp('Elements/ResultsStructuredView', + Query => $Query, + OrderBy => $OrderBy, + Order => $Order, + Format => $Format, + WriteHeader => $WriteHeader, + WriteRow => $WriteRow, + FormatDate => $FormatDate, +); + +# Set column widths +for( my $item = 0; $item < scalar @width; $item++ ) { + $worksheet->set_column($item, $item, $width[$item]); +} + +# and then write everything out. +$workbook->close; +close($fh); +$m->print($xls); +$m->abort(); + +</%INIT> diff --git a/rt/share/html/Ticket/Checklist.html b/rt/share/html/Ticket/Checklist.html new file mode 100644 index 000000000..7394b0c10 --- /dev/null +++ b/rt/share/html/Ticket/Checklist.html @@ -0,0 +1,30 @@ +<& /Elements/Header, Title => loc("Checklist for Ticket #[_1] [_2]", $Ticket->Id, $Ticket->Subject) &> +<& /Ticket/Elements/Tabs, + Ticket => $Ticket, current_tab => 'Ticket/Checklist.html?id='.$Ticket->id, + Title => loc("Ticket Checklist # [_1] [_2]", $Ticket->Id, $Ticket->Subject) &> + +<& /Ticket/Elements/ShowMembers_Checklist, Ticket => $Ticket &> + +% if ( $show_hint ) { + +<A HREF="ModifyLinks.html?id=<%$Ticket->id%>">Link</A> +or <A HREF="Create.html?Queue=<%$Ticket->QueueObj->Id%>&new-MemberOf=<%$Ticket->id%>">create</A> +create child tickets to make a checklist. + +% } + +<%ARGS> +$id => undef +</%ARGS> + +<%INIT> + +my $Ticket = LoadTicket ($id); + +unless ($Ticket->CurrentUserHasRight('ShowTicket')) { + Abort("No permission to view ticket"); +} + +my $show_hint = ! $Ticket->Members->Count; + +</%INIT> diff --git a/rt/share/html/Ticket/Create.html b/rt/share/html/Ticket/Create.html index 651c61041..6ea2c9f39 100755 --- a/rt/share/html/Ticket/Create.html +++ b/rt/share/html/Ticket/Create.html @@ -64,8 +64,14 @@ <&| /Widgets/TitleBox, title => $title &> <table border="0" cellpadding="0" cellspacing="0"> <tr><td class="label"><&|/l&>Queue</&>:</td> -<td class="value"><& Elements/ShowQueue, QueueObj => $QueueObj &> -<input type="hidden" class="hidden" name="Queue" value="<% $QueueObj->Name %>" /> +%#<td class="value"><& Elements/ShowQueue, QueueObj => $QueueObj &> +%#<input type="hidden" class="hidden" name="Queue" value="<% $QueueObj->Name %>" /> +<td class="value"><& /Elements/SelectQueue, + Name => 'Queue', + Default => $QueueObj->Name, + ShowNullOption => 0, + ShowAllQueues => 0, + OnChange => "document.getElementsByName('id')[0].value = ''; form.submit()" &> </td> <td class="label"><&|/l&>Status</&>: </td> @@ -234,6 +240,7 @@ <tr><td class="label"><&|/l&>Children</&></td><td><input size="10" name="MemberOf-new" value="<% $ARGS{'MemberOf-new'} || '' %>" /></td></tr> <tr><td class="label"><&|/l&>Refers to</&></td><td><input size="10" name="new-RefersTo" value="<% $ARGS{'new-RefersTo'} || '' %>" /></td></tr> <tr><td class="label"><&|/l&>Referred to by</&></td><td><input size="10" name="RefersTo-new" value="<% $ARGS{'RefersTo-new'} || '' %>" /></td></tr> +<tr><td class="label">Customer ID</td><td><input size="10" name="new-Customer" value="<% $ARGS{'new-Customer'} || '' %>" /></td></tr> </table> @@ -324,6 +331,16 @@ if ($CloneTicket) { @cf_values; } + # Pass customer links along (even though cloning of parent links + # in general is disabled). + my $customers = $CloneTicketObj->Customers; + my @customers; + while ( my $customer = $customers->Next ) { + my ($custnum) = $customer->Target =~ /cust_main\/(\d+)$/; + push @customers, $custnum if $custnum; + } + $clone->{'new-Customer'} = join(' ', @customers); + for ( keys %$clone ) { $ARGS{$_} = $clone->{$_} if not defined $ARGS{$_}; } @@ -417,7 +434,18 @@ $m->callback( CallbackName => 'BeforeCreate', ARGSRef => \%ARGS, skip_create => if ((!exists $ARGS{'AddMoreAttach'}) and (defined($ARGS{'id'}) and $ARGS{'id'} eq 'new')) { # new ticket? if ( $ValidCFs && !$checks_failure && !$skip_create ) { +# CREATE THE TICKET. +# For some reason it's done by a Mason component named "Display.html" +# and the call is buried in obscure error-handling stuff. +# This comment exists to make it more visually obvious. +# ************************************************************ + $m->comp('Display.html', %ARGS); + +# ************************************************************ +# Execution should not continue here. Display.html calls +# Redirect() which does an $m->abort. We only get here if the +# code dies before then, hence "$@". $RT::Logger->crit("After display call; error is $@"); $m->abort(); } diff --git a/rt/share/html/Ticket/Display.html b/rt/share/html/Ticket/Display.html index 21be7953a..59adbd68d 100755 --- a/rt/share/html/Ticket/Display.html +++ b/rt/share/html/Ticket/Display.html @@ -46,12 +46,12 @@ %# %# END BPS TAGGED BLOCK }}} <& /Elements/Header, - Title => loc("#[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject), + Title => loc("Ticket #[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject), LinkRel => \%link_rel &> <& /Ticket/Elements/Tabs, Ticket => $TicketObj, current_tab => 'Ticket/Display.html?id='.$TicketObj->id, - Title => loc("#[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject) &> + Title => loc("Ticket #[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject) &> % $m->callback(CallbackName => 'BeforeActionList', %ARGS, Actions => \@Actions, ARGSRef => \%ARGS, Ticket => $TicketObj); @@ -104,6 +104,14 @@ unless ($id || $TicketObj) { if ($ARGS{'id'} eq 'new') { # {{{ Create a new ticket + # Massage customer IDs into member links. + my @cust_uris = map { + /^(\d+)$/ && "freeside://freeside/cust_main/$1" + } split(' ', $ARGS{'new-Customer'}); + + $ARGS{'new-MemberOf'} = + join(' ', $ARGS{'new-MemberOf'}, @cust_uris); + my $Queue = new RT::Queue( $session{'CurrentUser'} ); $Queue->Load($ARGS{'Queue'}); unless ( $Queue->id ) { @@ -155,10 +163,13 @@ if ($ARGS{'id'} eq 'new') { push @Actions, ProcessTicketBasics( ARGSRef => \%ARGS, TicketObj => $TicketObj ); push @Actions, ProcessTicketLinks( ARGSRef => \%ARGS, TicketObj => $TicketObj ); push @Actions, ProcessTicketDates( ARGSRef => \%ARGS, TicketObj => $TicketObj ); - push @Actions, ProcessObjectCustomFieldUpdates(ARGSRef => \%ARGS, TicketObj => $TicketObj ); + push @Actions, ProcessTicketCustomFieldUpdates(ARGSRef => \%ARGS, TicketObj => $TicketObj ); + # If this fails due to required fields being empty, it will set + # notes('RedirectToBasics'). + push @Actions, ProcessTicketStatus( ARGSRef => \%ARGS, TicketObj => $TicketObj ); - # XXX: we shouldn't block actions here if user has no right to see the ticket, - # but we should allow him to see actions he has done + # XXX: we shouldn't block actions here if user has no right to see the + # ticket, but we should allow him to see actions he has done unless ($TicketObj->CurrentUserHasRight('ShowTicket')) { Abort("No permission to view ticket"); } @@ -189,7 +200,14 @@ if (@Actions) { my $key = Digest::MD5::md5_hex( rand(1024) ); push @{ $session{"Actions"}->{$key} ||= [] }, @Actions; $session{'i'}++; - my $url = RT->Config->Get('WebURL') . "Ticket/Display.html?id=" . $TicketObj->id . "&results=" . $key; + my $url = RT->Config->Get('WebURL'); + if ( $m->notes('RedirectToBasics') ) { + $url .= 'Ticket/Modify.html'; + } + else { + $url .= 'Ticket/Display.html'; + } + $url .= '?id=' . $TicketObj->id . "&results=" . $key; $url .= '#' . $ARGS{Anchor} if $ARGS{Anchor}; RT::Interface::Web::Redirect($url); } diff --git a/rt/share/html/Ticket/Elements/AddCustomers b/rt/share/html/Ticket/Elements/AddCustomers new file mode 100644 index 000000000..09acdfd3f --- /dev/null +++ b/rt/share/html/Ticket/Elements/AddCustomers @@ -0,0 +1,55 @@ +%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am> +%# Copyright (c) 2008 Freeside Internet Services, Inc. +%# +%# 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. +<BR> +<%$msg%><br> + +% if (@Customers) { + +<br><i>(Check box to link)<i> +<table> +% foreach my $customer (@Customers) { +<tr> + <td> + <input type="checkbox" name="Ticket-AddCustomer-<% $customer->{'custnum'} %>" VALUE="1" <% scalar(@Customers) == 1 ? 'CHECKED' : '' %>> + <A HREF="<%$freeside_url%>/view/cust_main.cgi?<% $customer->{'custnum'} %>"><% &RT::URI::freeside::small_custview($customer->{'custnum'}, &RT::URI::freeside::FreesideGetConfig('countrydefault'), 1) |n %> + </td> +</tr> +% } +</table> + +% } + +<%INIT> +my ($msg); + +my $freeside_url = &RT::URI::freeside::FreesideURL(); + +my @Customers = (); +if ( $CustomerString ) { + @Customers = &RT::URI::freeside::smart_search( + 'search' => $CustomerString, + 'no_fuzzy_on_exact' => 1, #pref? + ); +} + +my @Services = (); +if ($ServiceString) { + @Services = (); #service_search(); +} + +</%INIT> + +<%ARGS> +$CustomerString => undef +$ServiceString => undef +</%ARGS> diff --git a/rt/share/html/Ticket/Elements/BulkLinks b/rt/share/html/Ticket/Elements/BulkLinks index b97270e43..181fbed56 100755 --- a/rt/share/html/Ticket/Elements/BulkLinks +++ b/rt/share/html/Ticket/Elements/BulkLinks @@ -163,7 +163,7 @@ $Tickets => undef <%INIT> my %hash; if ( $Tickets && $Tickets->Count ) { - my $first_ticket = $Tickets->Next; + my $first_ticket = $Tickets->Next or last; #avoid errors on bulk delete # we only show current links that eixst on all the tickets for my $type ( qw/DependsOn DependedOnBy Members MemberOf RefersTo ReferredToBy/ ) { diff --git a/rt/share/html/Ticket/Elements/EditCustomFields b/rt/share/html/Ticket/Elements/EditCustomFields index 918f4d4f5..ee55e996f 100755 --- a/rt/share/html/Ticket/Elements/EditCustomFields +++ b/rt/share/html/Ticket/Elements/EditCustomFields @@ -105,6 +105,13 @@ $m->callback( %ARGS, CallbackName => 'MassageCustomFields', CustomFields => $Cus my $single_column = RT->Config->Get('EditCustomFieldsSingleColumn'); +# show hints for missing required fields +if ( $TicketObj ) { + foreach my $field ( $TicketObj->MissingRequiredFields ) { + $m->notes('InvalidField-' . $field->Id => 'Required to resolve'); + } +} + </%INIT> <%ARGS> $NamePrefix => '' diff --git a/rt/share/html/Ticket/Elements/EditCustomers b/rt/share/html/Ticket/Elements/EditCustomers new file mode 100644 index 000000000..0ba6e447b --- /dev/null +++ b/rt/share/html/Ticket/Elements/EditCustomers @@ -0,0 +1,63 @@ +%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am> +%# Copyright (c) 2008 Freeside Internet Services, Inc. +%# +%# 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. +<TABLE width=100%> + <TR> + <TD VALIGN=TOP WIDTH=50%> + <h3><&|/l&>Current Customers</&></h3> + +<table> + <tr> + <td><i><&|/l&>(Check box to disassociate)</&></i></td> + </tr> + <tr> + <td class="value"> +% foreach my $link ( @{ $Ticket->Customers->ItemsArrayRef } ) { + + <INPUT TYPE=CHECKBOX NAME="DeleteLink--<%$link->Type%>-<%$link->Target%>"> +%# <& ShowLink, URI => $link->TargetURI &><br> + <A HREF="<% $link->TargetURI->Resolver->HREF %>"><% $link->TargetURI->Resolver->AsStringLong |n %></A> + <BR> +% } + </td> + </tr> +</table> + +</TD> + +<TD VALIGN=TOP> +<h3><&|/l&>New Customer Links</&></h3> +<&|/l&>Find customer</&><BR> +<input name="CustomerString"> +<input type=submit name="OnlySearchForCustomers" value="<&|/l&>Go!</&>"> +<br><i>cust #, name, company or phone</i> +<BR> +%#<BR> +%#<&|/l&>Find service</&><BR> +%#<input name="ServiceString"> +%#<input type=submit name="OnlySearchForServices" value="<&|/l&>Go!</&>"> +%#<br><i>username, username@domain, domain, or IP address</i> +%#<BR> + +<& AddCustomers, Ticket => $Ticket, + CustomerString => $CustomerString, + ServiceString => $ServiceString, &> + +</TD> +</TR> +</TABLE> + +<%ARGS> +$CustomerString => undef +$ServiceString => undef +$Ticket => undef +</%ARGS> diff --git a/rt/share/html/Ticket/Elements/EditTransactionCustomFields b/rt/share/html/Ticket/Elements/EditTransactionCustomFields index e2f42b2c5..a4ade8721 100644 --- a/rt/share/html/Ticket/Elements/EditTransactionCustomFields +++ b/rt/share/html/Ticket/Elements/EditTransactionCustomFields @@ -48,19 +48,25 @@ % $m->callback( CallbackName => 'BeforeTransactionCustomFields', TicketObj => $TicketObj, QueueObj => $QueueObj, NamePrefix => $NamePrefix ); % if ($CustomFields->Count) { % while (my $CF = $CustomFields->Next()) { +% $CF->SetContextObject($TicketObj || $QueueObj); % next unless $CF->CurrentUserHasRight('ModifyCustomField'); +% next unless $CF->UILocation eq $UILocation; <tr> -<td class="label"><% loc($CF->Name) %>:</td> +<td class="label"> +<% loc($CF->Name) %>: +</td> <td> <& /Elements/EditCustomField, CustomField => $CF, NamePrefix => $NamePrefix &> +% if ( $CF->Type ne 'TimeValue' ) { <em><% $CF->FriendlyType %></em> -% if (my $msg = $m->notes('InvalidField-' . $CF->Id)) { +% } +% if (my $msg = $m->notes('InvalidField-' . $CF->Id)) { <br /> <span class="cfinvalidfield"><% $msg %></span> -% } +% } </td> </td></tr> % } @@ -83,5 +89,6 @@ $m->callback( CallbackName => 'MassageTransactionCustomFields', CustomFields => $NamePrefix => "Object-RT::Transaction--CustomField-" $TicketObj => undef $QueueObj => undef +$UILocation => '' </%ARGS> diff --git a/rt/share/html/Ticket/Elements/ShowCustomers b/rt/share/html/Ticket/Elements/ShowCustomers new file mode 100644 index 000000000..3acf92dd4 --- /dev/null +++ b/rt/share/html/Ticket/Elements/ShowCustomers @@ -0,0 +1,38 @@ +%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am> +%# +%# 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. +<table> +% my $cust = 0; +% foreach my $custResolver ( map { $_->TargetURI->Resolver } +% @{ $Ticket->Customers->ItemsArrayRef } +% ) +% { +% $cust++; +% my $cust_main = ''; + <tr> + <td class="value"> + <A HREF="<% $custResolver->HREF %>"><% $custResolver->AsStringLong |n %></A> + </td> + </tr> +% } +% unless ( $cust ) { + <tr> + <td class="labeltop"> + <i>(none)<i> + </td> + </tr> + +% } +</table> +<%ARGS> +$Ticket => undef +</%ARGS> + diff --git a/rt/share/html/Ticket/Elements/ShowMembers_Checklist b/rt/share/html/Ticket/Elements/ShowMembers_Checklist new file mode 100644 index 000000000..68fb3b2c5 --- /dev/null +++ b/rt/share/html/Ticket/Elements/ShowMembers_Checklist @@ -0,0 +1,29 @@ + +<style type="text/css"> +ul.checklist { + list-style-type: none +} +</style> + +<ul class="checklist"> +% while (my $link = $members->Next) { +<li><& /Elements/ShowLink_Checklist, URI => $link->BaseURI &><br /> +% if ($depth < 8) { #why only 8? +<& /Ticket/Elements/ShowMembers_Checklist, Ticket => $link->BaseObj, depth => ($depth+1) &> +% } +</li> +% } +</ul> + +<%INIT> + +return unless $Ticket; +my $members = $Ticket->Members; +return unless $members->Count; + +</%INIT> + +<%ARGS> +$Ticket => undef +$depth => 1 +</%ARGS> diff --git a/rt/share/html/Ticket/Elements/ShowParents b/rt/share/html/Ticket/Elements/ShowParents index d3371154a..74bd8e174 100644 --- a/rt/share/html/Ticket/Elements/ShowParents +++ b/rt/share/html/Ticket/Elements/ShowParents @@ -47,6 +47,7 @@ %# END BPS TAGGED BLOCK }}} <ul> % while (my $link = $member_of->Next) { +% next if $link->Target and $link->Target =~ m(^freeside://); <li><& /Elements/ShowLink, URI => $link->TargetURI &><br /> % } </ul> diff --git a/rt/share/html/Ticket/Elements/ShowSummary b/rt/share/html/Ticket/Elements/ShowSummary index 24d8c4dfe..ef5960e01 100755 --- a/rt/share/html/Ticket/Elements/ShowSummary +++ b/rt/share/html/Ticket/Elements/ShowSummary @@ -66,6 +66,13 @@ </&> % } + <&| /Widgets/TitleBox, title => loc('Customers'), + title_href => RT->Config->Get('WebPath')."/Ticket/ModifyCustomers.html?id=".$Ticket->Id, + class => 'ticket-info-customers' + &> + <& /Ticket/Elements/ShowCustomers, Ticket => $Ticket &> + </&> + <&| /Widgets/TitleBox, title => loc('People'), title_href => RT->Config->Get('WebPath')."/Ticket/ModifyPeople.html?id=".$Ticket->Id, class => 'ticket-info-people', diff --git a/rt/share/html/Ticket/Elements/ShowTransactionAttachments b/rt/share/html/Ticket/Elements/ShowTransactionAttachments index 161a4853f..625e124f8 100644 --- a/rt/share/html/Ticket/Elements/ShowTransactionAttachments +++ b/rt/share/html/Ticket/Elements/ShowTransactionAttachments @@ -210,8 +210,14 @@ my $render_attachment = sub { # if it's a text/plain show the body elsif ( $message->ContentType =~ m{^(text|message)}i ) { - eval { require Text::Quoted; $content = Text::Quoted::extract($content); }; - if ($@) { $RT::Logger->warning( "Text::Quoted failed: $@" ) } + #don't want to use this even if it is installed, its + #segfaulting on weird characters and silently truncating the + #ticket history output + #see: + # r44838@pinglin: jesse | 2006-11-14 15:53:18 -0500 + # * Move Text::Quoted back to being a run-time require. So that it's possible to turn off the feature if it causes your perl to segfault. (Text::Tabs is...not robust in the face of perl bugs) + #eval { require Text::Quoted; $content = Text::Quoted::extract($content); }; + #if ($@) { $RT::Logger->warning( "Text::Quoted failed: $@" ) } $m->comp( 'ShowMessageStanza', diff --git a/rt/share/html/Ticket/Elements/Tabs b/rt/share/html/Ticket/Elements/Tabs index d88a2ad91..2f89dc61c 100755 --- a/rt/share/html/Ticket/Elements/Tabs +++ b/rt/share/html/Ticket/Elements/Tabs @@ -142,6 +142,14 @@ if ($Ticket) { title => loc('Links'), path => "Ticket/ModifyLinks.html?id=" . $id, }, + _Ea => { + title => loc('Checklist'), + path => "Ticket/Checklist.html?id=" . $id, + }, + _Eb=> { + title => loc('Customers'), + path => "Ticket/ModifyCustomers.html?id=" . $id, + }, _X => { title => loc('Jumbo'), path => "Ticket/ModifyAll.html?id=" . $id, @@ -188,7 +196,9 @@ if ($Ticket) { if ( $can{'ModifyTicket'} ) { if ( $Ticket->Status ne 'resolved' ) { $actions->{'G'} = { - path => + path => + ($Ticket->MissingRequiredFields) ? + "Ticket/Modify.html?Status=resolved&id=$id" : "Ticket/Update.html?Action=" . RT->Config->Get('ResolveDefaultUpdateType', $session{'CurrentUser'}) . "&DefaultStatus=resolved&id=" @@ -326,6 +336,11 @@ if ($has_query) { title => loc('Graph'), }; + $tabs->{"l"} = { + path => "Prefs/SavedSearches.html", + title => 'Saved Searches', + }; + } foreach my $searchtab ( keys %{$searchtabs} ) { diff --git a/rt/share/html/Ticket/Modify.html b/rt/share/html/Ticket/Modify.html index b80d86f68..9f1a95932 100755 --- a/rt/share/html/Ticket/Modify.html +++ b/rt/share/html/Ticket/Modify.html @@ -79,8 +79,10 @@ $m->comp( # Now let callbacks have a chance at editing %ARGS $m->callback( TicketObj => $TicketObj, CustomFields => $CustomFields, ARGSRef => \%ARGS ); -my @results = ProcessTicketBasics(TicketObj => $TicketObj, ARGSRef => \%ARGS); +my @results; +push @results, ProcessTicketBasics(TicketObj => $TicketObj, ARGSRef => \%ARGS); push @results, ProcessObjectCustomFieldUpdates(Object => $TicketObj, ARGSRef => \%ARGS); +push @results, ProcessTicketStatus(TicketObj => $TicketObj, ARGSRef => \%ARGS); $TicketObj->ApplyTransactionBatch; diff --git a/rt/share/html/Ticket/ModifyAll.html b/rt/share/html/Ticket/ModifyAll.html index 1776c40d8..cd9bb3891 100755 --- a/rt/share/html/Ticket/ModifyAll.html +++ b/rt/share/html/Ticket/ModifyAll.html @@ -236,6 +236,7 @@ unless ($OnlySearchForPeople or $OnlySearchForGroup or $ARGS{'AddMoreAttach'} ) push @results, ProcessTicketBasics( TicketObj => $Ticket, ARGSRef => \%ARGS ); push @results, ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS); } + push @results, ProcessTicketStatus( TicketObj => $Ticket, ARGSRef => \%ARGS ); $Ticket->ApplyTransactionBatch; diff --git a/rt/share/html/Ticket/ModifyCustomers.html b/rt/share/html/Ticket/ModifyCustomers.html new file mode 100644 index 000000000..72d103b23 --- /dev/null +++ b/rt/share/html/Ticket/ModifyCustomers.html @@ -0,0 +1,49 @@ +%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am> +%# +%# 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. +<& /Elements/Header, Title => loc("Customers for ticket #[_1]", $Ticket->Id) &> +<& /Ticket/Elements/Tabs, + Ticket => $Ticket, + current_tab => "Ticket/ModifyCustomers.html?id=".$Ticket->Id, + Title => loc("Customers for ticket #[_1]", $Ticket->Id) &> + +<& /Elements/ListActions, actions => \@results &> + +<form action="ModifyCustomers.html" method="post"> +<input type="hidden" name="id" value="<%$Ticket->id%>"> + +<& /Elements/TitleBoxStart, title => loc('Edit Customer Links'), color => "#7f007b"&> +<& Elements/EditCustomers, Ticket => $Ticket, CustomerString => $CustomerString, ServiceString => $ServiceString &> +<& /Elements/TitleBoxEnd &> +<& /Elements/Submit, color => "#7f007b", Label => loc('Save Changes') &> +</form> + + +<%INIT> + +my @results = (); +my $Ticket = LoadTicket($id); + +# if we're trying to search for customers/services and nothing else +unless ( $OnlySearchForCustomers || $OnlySearchForServices) { + @results = ProcessTicketCustomers( TicketObj => $Ticket, ARGSRef => \%ARGS); +} + +</%INIT> + + +<%ARGS> +$OnlySearchForCustomers => undef +$OnlySearchForServices => undef +$CustomerString => undef +$ServiceString => undef +$id => undef +</%ARGS> diff --git a/rt/share/html/Ticket/Update.html b/rt/share/html/Ticket/Update.html index 50c6f9327..62db0d1c3 100755 --- a/rt/share/html/Ticket/Update.html +++ b/rt/share/html/Ticket/Update.html @@ -65,8 +65,8 @@ <table width="100%" border="0"> % $m->callback(CallbackName => 'AfterTableOpens', ARGSRef => \%ARGS, Ticket => $TicketObj); -<tr><td class="label"><&|/l&>Status</&>:</td> -<td> +<tr><td valign="baseline" class="label"><&|/l&>Status</&>:</td> +<td valign="baseline"> <& /Elements/SelectStatus, Name=>"Status", DefaultLabel => loc("[_1] (Unchanged)", loc($TicketObj->Status)), Default => $ARGS{'Status'} || ($TicketObj->Status eq $DefaultStatus ? undef : $DefaultStatus)&> <span class="label"><&|/l&>Owner</&>:</span> <& /Elements/SelectOwner, @@ -76,13 +76,23 @@ DefaultLabel => loc("[_1] (Unchanged)", $m->scomp('/Elements/ShowUser', User => $TicketObj->OwnerObj)), Default => $ARGS{'Owner'} &> -<span class="label"><&|/l&>Worked</&>:</span> -<& /Elements/EditTimeValue, +</td> +<td rowspan=4 valign="top"> +<table style="float:right;"> +<tr> +<td class="label"><&|/l&>Worked</&>:</td> +<td><& /Elements/EditTimeValue, Name => 'UpdateTimeWorked', Default => $ARGS{UpdateTimeWorked}||'', InUnits => $ARGS{'UpdateTimeWorked-TimeUnits'}||'minutes', &> </td></tr> +<& /Ticket/Elements/EditTransactionCustomFields, + %ARGS, + TicketObj => $TicketObj, + UILocation => 'TimeWorked', +&> +</table></td></tr> % my $skip; % $m->callback( %ARGS, CallbackName => 'BeforeUpdateType', skip => \$skip ); % if (!$skip) { @@ -131,7 +141,7 @@ % } % $m->callback( %ARGS, CallbackName => 'AfterGnuPG' ); -<tr><td class="label" valign="top"><&|/l&>Message</&>:</td><td> +<tr><td class="label" valign="top"><&|/l&>Message</&>:</td><td colspan=2> % $m->callback( %ARGS, CallbackName => 'BeforeMessageBox' ); % if (exists $ARGS{UpdateContent}) { % # preserve QuoteTransaction so we can use it to set up sane references/in/reply to diff --git a/rt/share/html/User/Prefs.html b/rt/share/html/User/Prefs.html index f04b02f47..c62d8737c 100755 --- a/rt/share/html/User/Prefs.html +++ b/rt/share/html/User/Prefs.html @@ -45,7 +45,7 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<& /Elements/Header, Title=>loc("Preferences") &> +<& /Elements/Header, Title=>loc("Ticketing Preferences") &> <& /User/Elements/Tabs, current_tab => 'User/Prefs.html', Title => loc("Preferences") &> @@ -74,10 +74,10 @@ <td class="label"><&|/l&>Nickname</&>:</td> <td class="value"><input name="NickName" value="<%$UserObj->NickName || ''%>" /></td> </tr> - <tr> - <td class="label"><&|/l&>Language</&>:</td> - <td class="value"><& /Elements/SelectLang, Name => 'Lang', Default => $UserObj->Lang &></td> - </tr> +%# <tr> +%# <td class="label"><&|/l&>Language</&>:</td> +%# <td class="value"><& /Elements/SelectLang, Name => 'Lang', Default => $UserObj->Lang &></td> +%# </tr> <tr> <td class="label"><&|/l&>Timezone</&>:</td> <td class="value"><& /Elements/SelectTimezone, Name => 'Timezone', Default => $UserObj->Timezone &></td> diff --git a/rt/share/html/Widgets/TitleBoxEnd b/rt/share/html/Widgets/TitleBoxEnd index dab933166..d39512042 100755 --- a/rt/share/html/Widgets/TitleBoxEnd +++ b/rt/share/html/Widgets/TitleBoxEnd @@ -50,7 +50,7 @@ </div> % #Manually flush the content buffer after each titlebox is displayed -% $m->flush_buffer(); +% #wtf? this causes us to lose content #$m->flush_buffer(); <%ARGS> $title => undef diff --git a/rt/share/html/autohandler b/rt/share/html/autohandler index f9314a942..5a618fbb1 100755 --- a/rt/share/html/autohandler +++ b/rt/share/html/autohandler @@ -52,7 +52,9 @@ $m->callback( ARGSRef => \%ARGS, CallbackName => 'Init', CallbackPage => '/autoh RT::Interface::Web::HandleRequest(\%ARGS); -$m->comp( '/Elements/Footer', %ARGS ); +$m->comp( '/Elements/Footer', %ARGS ) + unless $r->content_type =~ qr<^(text|application)/(x-)?(css|javascript)>; + </%INIT> <%ARGS> $user => undef diff --git a/rt/share/html/index.html b/rt/share/html/index.html index 49d524a96..0a570e5c3 100755 --- a/rt/share/html/index.html +++ b/rt/share/html/index.html @@ -115,7 +115,11 @@ if ( $ARGS{'QuickCreate'} ) { From => $session{'CurrentUser'}->EmailAddress, Content => $ARGS{'Content'}, Subject => $ARGS{'Subject'}); - push @results, $msg; + if ( $t && $t->Id && RT->Config->Get('DisplayAfterQuickCreate', $session{'CurrentUser'}) ) { + RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."Ticket/Display.html?id=". $t->Id); + } else { + push @results, $msg; + } } elsif ( !$ValidCFs ) { push @results, "can't quickly create ticket in queue " . diff --git a/rt/share/html/m/_elements/footer b/rt/share/html/m/_elements/footer new file mode 100644 index 000000000..f3e0837cc --- /dev/null +++ b/rt/share/html/m/_elements/footer @@ -0,0 +1,11 @@ +<& /elements/footer.html &> +% if ( 0 ) { + <div id="bpscredits"> + <& /Elements/Logo, ShowName => 0 &> + <div id="copyright"> +<&|/l, '', '', '2010', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>', &>[_1] RT [_2] Copyright 1996-[_3] [_4].</&> +</div> +</div> +</body> +</html> +% } diff --git a/rt/share/html/m/_elements/full_site_link b/rt/share/html/m/_elements/full_site_link new file mode 100644 index 000000000..7f43968e0 --- /dev/null +++ b/rt/share/html/m/_elements/full_site_link @@ -0,0 +1 @@ +<a id="fullsite" href="<%RT->Config->Get('WebPath')%>/m/index.html?NotMobile=1"><&|/l&>Not using a mobile browser?</&></a> diff --git a/rt/share/html/m/_elements/header b/rt/share/html/m/_elements/header new file mode 100644 index 000000000..4af62996c --- /dev/null +++ b/rt/share/html/m/_elements/header @@ -0,0 +1,38 @@ +<%args> +$title => undef +$show_home_button => 1 +</%args> +<%init> +$r->headers_out->{'Pragma'} = 'no-cache'; +$r->headers_out->{'Cache-control'} = 'no-cache'; + +my $head = ''; + +my $etc = ''; + +</%init> +<& /elements/header.html, { + 'title' => $title, + 'head' => $head, + 'etc' => $etc, + 'nocss' => 1, + 'nobr' => 1, + 'mobile' => 1, +} &> +<link rel="stylesheet" type="text/css" href="<%RT->Config->Get('WebPath')|n%>/m/style.css"/> +% if ( 0 ) { # Disabled in favor of Freeside header +<html> +<head> +<title><%$title%></title> +</head> +<body> +% if ($show_home_button) { +% # The align is for older browsers, like the blackberry +<div id="gohome" align="right"> +<a href="<%RT->Config->Get('WebPath')%>/m/"><&|/l&>Homepage</&></a> +</div> +% } +% if ($title) { +<h1><%$title%></h1> +% } +% } # disabled diff --git a/rt/share/html/m/_elements/menu b/rt/share/html/m/_elements/menu new file mode 100644 index 000000000..54e7fe9a3 --- /dev/null +++ b/rt/share/html/m/_elements/menu @@ -0,0 +1,63 @@ +<&| /Widgets/TitleBox, class => 'menu'&> +<ul class="menu"> +% for my $item (@menu) { +% if (exists $item->{html}) { +<li><%$item->{html} |n%></li> +% } else { +<li><a href="<%RT->Config->Get('WebPath')%><%$item->{url}%>"><%$item->{label}%></a></li> +% } +% } +</ul> +</&> +<%init> +use RT::SavedSearches; +my @menu = ( + { html => '<form method="GET" id="search" action="' + . RT->Config->Get('WebPath') + . '/m/tickets/search">' + . loc("Search") + . ': <input type="text" name="q" id="q" value=""/>' + . '<input type="submit" value="' + . loc("Go") + . '"/></form>' + }, + { label => loc("New ticket"), + url => '/m/ticket/select_create_queue', + }, + { label => loc("Bookmarked tickets"), + url => '/m/tickets/search?name=Bookmarked%20Tickets', + }, + { label => loc("Tickets I own"), + url => '/m/tickets/search?name=My%20Tickets', + }, + { label => loc("Unowned tickets"), + url => '/m/tickets/search?name=Unowned%20Tickets', + }, + { label => loc("All tickets"), + url => '/m/tickets/search?query=id!%3d0&order_by=id&order=DESC' + }, +); + + +if ( $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch', Object => $RT::System)) + { + + my @Objects = RT::SavedSearches->new( $session{CurrentUser} )->_PrivacyObjects; + push @Objects, RT::System->new( $session{'CurrentUser'} ) + if $session{'CurrentUser'}->HasRight( + Object => $RT::System, + Right => 'SuperUser' + ); + + foreach my $object (@Objects) { + my @searches = $object->Attributes->Named('SavedSearch'); + foreach my $search (@searches) { + next unless $search->SubValue("SearchType") eq 'Ticket'; + push @menu, { label => $search->Description, url => '/m/tickets/search?query=' . $search->SubValue("Query").'&order='.$search->SubValue("Order").'&order_by='.$search->SubValue("OrderBy") }; + + } + } +} +push @menu, { label => loc("Logout"), url => '/m/logout', } + if !RT->Config->Get('WebExternalAuth'); +</%init> diff --git a/rt/share/html/m/_elements/raw_style b/rt/share/html/m/_elements/raw_style new file mode 100644 index 000000000..8c1997743 --- /dev/null +++ b/rt/share/html/m/_elements/raw_style @@ -0,0 +1,417 @@ +body { + font-family: helvetica, arial, sans-serif; + /*background-color: #ccf;*/ + background-color: #f8f8f8; + margin: 0; +} + +h1 { + font-size: 1.2em; + padding-top: 0.5em; + padding-left: 0.2em; + display: block; + background-color: #f8f8f8; + +} + +div.buttons { + text-align: right; + padding-right: 0.5em; + padding-bottom: 0.5em; +} + +.titlebox-title { + font-size: 1.1em; + margin-left: 0.5em; + margin-top: -1.2em; + top: -0.5em; + padding: 0.5em; + position: relative; + display: inline-block; + text-decoration: none; + /*background-color: #fff;*/ + background-color: #ccc; + -moz-border-radius: 0.25em; + -webkit-border-radius: 0.25em; + -webkit-box-shadow: #333 0px 0px 5px; + -moz-box-shadow: #333 0px 0px 5px; + box-shadow: #333 0px 0px 5px; +} + +ul.menu +{ + text-align: left; + list-style: none; + padding: 0; + margin: -0.6em; + left: 0; +} + +ul.menu li +{ + display: block; + margin: 0; + padding: 0; + font-weight: bold; +} + +ul.ticketlist li:active, ul.ticketlist li:hover, +ul.menu li:active, ul.menu li:hover { + background-color: #eee; +} + + +ul.menu li +{ + display: block; + padding: 1em; + margin: 0; + border:0; + border-top-width: 1px; + border-top-color: #666; + border-style: solid; + text-decoration: none; +} + +ul.menu li:first-child{ + border: none; +} + +ul.menu li#active a +{ + color: #800000; +} + +div.titlebox, #bpscredits, .ticket_menu{ + -moz-border-radius: 1em; + -webkit-border-radius: 1em; + margin: 0.5em; + background-color: #fff; + padding-top: 1em; + padding-bottom: 0.8em; + margin-top: 1.25em; + -webkit-box-shadow: #333 0px 0px 5px; + -moz-box-shadow: #333 0px 0px 5px; + box-shadow: #333 0px 0px 5px; + margin-bottom: 1em; +} + +div .titlebox-content { + padding-left: 0.5em; + padding-right: 0.5em; +} + +hr.clear { + display: none; +} + + +.label, .labeltop { + font-weight: normal; +} +.value { + font-weight: bold; + display:inline-block; +} + +ul.ticketlist { + list-style: none; + padding-left: -0.5em; + padding-right: -0.5em; /* to counteract the titlebox and get shading to the end*/ + margin-left: -0.5em; + margin-right: -0.5em; + padding: 0em; + padding-bottom: 1em; +} + +ul.ticketlist li.ticket { + padding: 0.5em; + font-weight: bold; + border-bottom: 1px solid #999; + +} +ul.ticketlist li.ticket:first-child { + border-top: 1px solid #999; +} + +ul.ticketlist li.ticket a.ticket{ + display: inline-block; + font-size: 1em; + width: 100%; + padding: 0.5em; + padding-bottom: 5em; + margin-bottom: -5em; +} +ul.ticketlist li.ticket div.metadata { +} + + +ul.ticketlist li.ticket div.metadata div { + padding: 0.2em; + font-size:0.8em; + display: block; +} + +ul.ticketlist li.ticket div.metadata .label { + display: inline-block; + width: 6em; + font-size: 0.8em; + text-align: right; + color: #666; +} + +div#paging { + text-align: center; +} + +.ticket-reply .titlebox-title, .titlebox.search .titlebox-title, .titlebox.menu .titlebox-title, .ticket_menu .titlebox-title, .history .titlebox-title, #ticket-create-basics .titlebox-title{ + display: none; +} + +a { + color: #000; +} + +.ticket_menu a, .menu a { + text-decoration: none; +} + +ul.menu a { + padding: 0.5em; + margin-top: -0.5em; + margin-bottom: -0.5em; + display: inline-block; + width: 100%; +} + +ul.menu a:after { + color: #666; + float: right; + content: ">"; + font-size: 1.5em; + padding: 0; + margin: 0; + padding-right: 1em; + +} + +ul.menu form { + display: inline; +} + +ul.menu form * { + display: inline; +} + + +ul.menu form input[type=text] { + width: 7em; +} + +ul.menu form input{ + + width: auto; + padding: 0.5em; + margin: -0.5em; + margin-left: 1em; +} + +.ticket_menu { + text-align: center; +} + +.ticket_menu ul { + display: block; + margin: 0; + padding: 0; +} + +.ticket_menu ul li { + + display: inline-block; + text-align: center; + padding-bottom: 0.25em; + padding-top: 0.25em; + font-size: 1em; + width: 28%; + padding-right: 0.3em; + padding-left: 0.2em; + border-right: 1px solid #000; +} +.ticket_menu ul li:last-child { + padding-right: 0; + border-right: 0; +} + +.ticket-info-reminders table { + + width: 100%; +} + +#ticket-create .label:after { + content: ": "; + padding-right: 0.25em; + +} + +#ticket-create .content-label { + width: auto; + display: block; + text-align: left; + +} + +#ticket-show .label, .login-body .label { + display: inline-block; + text-align: right; + width: 6em; + padding-right: 0.25em; + font-size: 0.8em; +} + +.login-body .value { + width: auto; +} + +.history ul.history-list { + padding: 0; + margin: 0; + padding-bottom: 2em; +} + + +.history ul.history-list li:first-child { + border-top: 1px solid #ccc; +} + +.history ul.history-list li { + list-style: none; + border-bottom: 1px solid #ccc; + padding: 0.5em; +} + +.history .age { + display: inline-block; + min-width: 8em; + text-align: right; + +} + +div#login-box div.titlebox { + width: 100%; + margin-left:auto; + margin-right: auto; +} + +div#login-box input[type=text], div#login-box input[type=password] { + width: 100%; +} + +#bpscredits img { + padding-bottom: 1em; +} + + + +#bpscredits { + float: right; + text-align: right; + width: auto; + font-size: 0.8em; + padding: 1em; +} + + +:focus { + background-color: #ffc; + border-color: #000; + border-weight: 3px; +} + +input[type=submit], input[type=button], button, #paging a { + border: 2px outset; + margin: 0.3em; + padding: 0.3em; + padding-left: 0.6em; + padding-right: 0.6em; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; + background-color: #006699; + color: #fff; +} + +form { + + margin:0; +} + +#gohome { + position: absolute; + top: 0; + right: 0; + border-left: 1px solid black; + border-bottom: 1px solid black; + -moz-border-radius-bottomleft: 1em; + -webkit-border-bottom-left-radius: 1em; + padding: 0.5em; + background-color: #fff; +} + +#gohome a { + font-size: 1em; + padding: 0.25em; + color: #000; +} + +div.txn-content { + + font-size:0.8em; + padding-left:1em; + padding-top:0.5em; + margin-top: 0.5em; + margin-left: 2em; + padding-bottom: 0.5em; + border-left: 5px solid #00c; + +} + +.label { + text-align: left; + width: 10em; + color: #666; + display: block; + padding-bottom: 0.2em; + padding-right: 0.2em; + +} + +div.entry, tr.input-row { + margin-bottom: 0.25em; + padding-bottom: 0.25em; + border-bottom: 1px solid #ccc; + display: block; + width: 100%; + min-height: 1em; +} + + +input, input[type=text], input[type=password], select { + width: 100%; +} + +.timefield input { + width: 5em; +} + +.timefield select { + width: auto; +} + + +textarea { + width: 100%; +} + +a#fullsite { + padding-left: 1em; +} diff --git a/rt/share/html/m/_elements/ticket_list b/rt/share/html/m/_elements/ticket_list new file mode 100644 index 000000000..822efe8d6 --- /dev/null +++ b/rt/share/html/m/_elements/ticket_list @@ -0,0 +1,64 @@ +<%args> +$order => undef +$order_by => undef +$query => '' +$page => 1 +</%args> +<%init> +my $collection = RT::Tickets->new($session{'CurrentUser'}); +$collection->FromSQL($query); +$collection->RowsPerPage(10); +$collection->GotoPage($page-1); +# XXX: ->{'order_by'} is hacky, but there is no way to check if +# collection is ordered or not +if ( $order_by) { + my @order_by = split /\|/, $order_by; + my @order = split /\|/,$order; + $collection->OrderByCols( + map { { FIELD => $order_by[$_], ORDER => $order[$_] } } + ( 0 .. $#order_by ) + ); +} + + + +$collection->RedoSearch(); + +if ($page > 1 && ! @{$collection->ItemsArrayRef||[]}) { + RT::Interface::Web::Redirect( RT->Config->Get('WebURL')."m/tickets/search?page=".($page-1)."&query=".$query."&order=$order&order_by=$order_by"); +} + +</%init> +<&| /m/_elements/wrapper, title => +loc("Found [quant,_1,ticket]",$collection->Count) &> +<&|/Widgets/TitleBox, class => 'search' +&> +<ul class="ticketlist"> +% while (my $ticket = $collection->Next()) { +<li class="ticket"> +<a class="ticket" href="<%RT->Config->Get('WebPath')%>/m/ticket/show?id=<%$ticket->id%>"><%$ticket->id%>: <%$ticket->Subject%></a> +<div class="metadata"> +<%perl> + +</%perl> +<div class="requestors"><span class="label"><&|/l&>Requestors</&>:</span> <& /Ticket/Elements/ShowGroupMembers, Group => $ticket->Requestors, Ticket => $ticket &></div> +<div class="status"><span class="label"><&|/l&>Status</&>:</span> <%$ticket->Status%></div> +<div class="owner"><span class="label"><&|/l&>Owner</&>:</span> <& /Elements/ShowUser, User => $ticket->OwnerObj, Ticket => $ticket &></div> +<div class="created"><span class="label"><&|/l&>Created</&>:</span> <%$ticket->CreatedObj->AgeAsString()%></div> +% if ($ticket->Priority) { +<div class="priority"><span class="label"><&|/l&>Priority</&>:</span> <%$ticket->Priority%></div> +% } +</div> +</li> +% } +</ul> +<div id="paging"> +% if ($page > 1) { +<a href="<%RT->Config->Get('WebPath')%>/m/tickets/search?page=<%$page-1%>&query=<%$query%>&order=<%$order%>&order_by=<%$order_by%>">Back</a> +% } +Page <%$page%> + +<a href="<%RT->Config->Get('WebPath')%>/m/tickets/search?page=<%$page+1%>&query=<%$query%>&order=<%$order%>&order_by=<%$order_by%>">Next</a> +</div> +</&> +</&> diff --git a/rt/share/html/m/_elements/ticket_menu b/rt/share/html/m/_elements/ticket_menu new file mode 100644 index 000000000..257b066bc --- /dev/null +++ b/rt/share/html/m/_elements/ticket_menu @@ -0,0 +1,31 @@ +<%args> +$ticket +</%args> +<div class="ticket_menu"> +<ul> +% for my $item (@menu) { +<li><a href="<%RT->Config->Get('WebPath')%><%$item->{url}%>"><%$item->{label}%></a></li> +% } +</ul> +</div> +<%init> +my @menu = ( +{ label => loc("Basics"), + url => '/m/ticket/show?id='.$ticket->id +}, + { + label => loc("History"), + url => '/m/ticket/history?id='.$ticket->id + }, + #{ label => loc("Modify"), url => '/m/ticket/modify?id='.$ticket->id }, +{ + label => loc("Reply"), + url => '/m/ticket/reply?id='.$ticket->id +} + + +); + +my $width = int(100/ ($#menu +1))-5; + +</%init> diff --git a/rt/share/html/m/_elements/wrapper b/rt/share/html/m/_elements/wrapper new file mode 100644 index 000000000..794385db4 --- /dev/null +++ b/rt/share/html/m/_elements/wrapper @@ -0,0 +1,15 @@ +<%args> +$title => '' +$show_home_button => 1 +</%args> +<%init> +if ($m->request_args->{'NotMobile'}) { + $session{'NotMobile'} = 1; + RT::Interface::Web::Redirect(RT->Config->Get('WebURL')); + $m->abort(); +} +$m->comp('header', title => $title, show_home_button => $show_home_button); +$m->out($m->content); +$m->comp('footer'); +$m->abort(); +</%init> diff --git a/rt/share/html/m/dhandler b/rt/share/html/m/dhandler new file mode 100644 index 000000000..627ec22fa --- /dev/null +++ b/rt/share/html/m/dhandler @@ -0,0 +1,5 @@ +<%init> +# deal with users who don't have options indexes set right +RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."m/index.html"); +$m->abort(); +</%init> diff --git a/rt/share/html/m/index.html b/rt/share/html/m/index.html new file mode 100644 index 000000000..5b3812506 --- /dev/null +++ b/rt/share/html/m/index.html @@ -0,0 +1,4 @@ +<&| _elements/wrapper, title => loc("RT for [_1]",RT->Config->Get('rtname'))&> +<& _elements/menu &> +<& _elements/full_site_link &> +</&> diff --git a/rt/share/html/m/login b/rt/share/html/m/login new file mode 100644 index 000000000..af7f67a6d --- /dev/null +++ b/rt/share/html/m/login @@ -0,0 +1,84 @@ +<%INIT> + +my $req_uri; + +if (UNIVERSAL::can($r, 'uri') and $r->uri =~ m{.*/m/(.*)}) { + $req_uri = '/m/'.$1; +} + +my $default_path = RT->Config->Get('WebPath') ."/m/"; + +my $form_action = defined $goto ? $goto + : defined $req_uri ? $req_uri + : $default_path + ; + +# sanitize $form_action +my $uri = URI->new($form_action); + +# You get undef scheme with a relative uri like "/Search/Build.html" +unless (!defined($uri->scheme) || $uri->scheme eq 'http' || $uri->scheme eq 'https') { + $form_action = $default_path; +} + +# Make sure we're logging in to the same domain +# You can get an undef authority with a relative uri like "index.html" +my $uri_base_url = URI->new(RT->Config->Get('WebURL')."m/"); +unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority) { + $form_action = $default_path; +} +</%INIT> +<&| /m/_elements/wrapper, show_home_button => 0 &> +<style> +<& /m/_elements/raw_style &> +</style> +<h1><&|/l, RT->Config->Get('rtname') &>RT for [_1]</&></h1> +<div id="body" class="login-body"> +% if ($Error) { +<&| "/Widgets/TitleBox", title => loc('Error'), hideable => 0, class => 'error' &> +<% $Error %> +</&> +% } + + +<div id="login-box"> +<&| /Widgets/TitleBox, title => loc('Login'), hideable => 0 &> + +% unless (RT->Config->Get('WebExternalAuth') and !RT->Config->Get('WebFallbackToInternalAuth')) { +<form id="login" name="login" method="post" action="<% $form_action %>"> + +<div class="entry"> + <span class="label"><&|/l&>Username</&>:</span><span class="value"><input name="user" value="<%$user%>" id="user" /></span> +</div> + +<div class="entry"> + <span class="label"><&|/l&>Password</&>:</span><span class="value"><input type="password" name="pass"/></span> +</div> + +<& /Elements/Submit, Label => loc('Login')&> + +% foreach my $key (keys %ARGS) { +% if (($key ne 'user') and ($key ne 'pass')) { +% if (ref($ARGS{$key}) =~ /ARRAY/) { +% foreach my $val (@{$ARGS{$key}}) { +<input type="hidden" class="hidden" name="<%$key %>" value="<% $val %>" /> +% } +% } +% else { +<input type="hidden" class="hidden" name="<% $key %>" value="<% $ARGS{$key} %>" /> +% } +% } +% } +</form> +% } +</&> +</div><!-- #login-box --> +</div><!-- #login-body --> +<& _elements/full_site_link &> +</&> +<%ARGS> +$user => "" +$pass => undef +$goto => undef +$Error => undef +</%ARGS> diff --git a/rt/share/html/m/logout b/rt/share/html/m/logout new file mode 100644 index 000000000..3006ea8eb --- /dev/null +++ b/rt/share/html/m/logout @@ -0,0 +1,7 @@ +<%init> +if (keys %session) { + tied(%session)->delete; + $session{'CurrentUser'} = RT::CurrentUser->new; +} +RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."m/"); +</%init> diff --git a/rt/share/html/m/style.css b/rt/share/html/m/style.css new file mode 100644 index 000000000..22be0a9dd --- /dev/null +++ b/rt/share/html/m/style.css @@ -0,0 +1,5 @@ +<%init> + $HTML::Mason::Commands::r->content_type('text/css'); + $m->comp('/m/_elements/raw_style'); + $m->abort(); +</%init> diff --git a/rt/share/html/m/ticket/create b/rt/share/html/m/ticket/create new file mode 100644 index 000000000..7c23194c4 --- /dev/null +++ b/rt/share/html/m/ticket/create @@ -0,0 +1,400 @@ +<%ARGS> +$QuoteTransaction => undef +$CloneTicket => undef +</%ARGS> +<%init> +$m->callback( CallbackName => "Init", ARGSRef => \%ARGS ); +my $Queue = $ARGS{Queue}; + + +my $showrows = sub { + my @pairs = @_; + + while (@pairs) { + my $key = shift @pairs; + my $val = shift @pairs; + + $m->out("<div class=\"entry\"><span class=\"label\">$key</span><div class=\"value\">$val</div></div>"); + + } + +}; + + +my $CloneTicketObj; +if ($CloneTicket) { + $CloneTicketObj = RT::Ticket->new( $session{CurrentUser} ); + $CloneTicketObj->Load($CloneTicket) + or Abort( loc("Ticket could not be loaded") ); + + my $clone = { + Requestors => join( ',', $CloneTicketObj->RequestorAddresses ), + Cc => join( ',', $CloneTicketObj->CcAddresses ), + AdminCc => join( ',', $CloneTicketObj->AdminCcAddresses ), + InitialPriority => $CloneTicketObj->Priority, + }; + + $clone->{$_} = $CloneTicketObj->$_() + for qw/Owner Subject FinalPriority TimeEstimated TimeWorked + Status TimeLeft/; + + $clone->{$_} = $CloneTicketObj->$_->AsString + for grep { $CloneTicketObj->$_->Unix } + map { $_ . "Obj" } qw/Starts Started Due Resolved/; + + my $members = $CloneTicketObj->Members; + my ( @members, @members_of, @refers, @refers_by, @depends, @depends_by ); + my $refers = $CloneTicketObj->RefersTo; + while ( my $refer = $refers->Next ) { + push @refers, $refer->LocalTarget; + } + $clone->{'new-RefersTo'} = join ' ', @refers; + + my $refers_by = $CloneTicketObj->ReferredToBy; + while ( my $refer_by = $refers_by->Next ) { + push @refers_by, $refer_by->LocalBase; + } + $clone->{'RefersTo-new'} = join ' ', @refers_by; + if (0) { # Temporarily disabled + my $depends = $CloneTicketObj->DependsOn; + while ( my $depend = $depends->Next ) { + push @depends, $depend->LocalTarget; + } + $clone->{'new-DependsOn'} = join ' ', @depends; + + my $depends_by = $CloneTicketObj->DependedOnBy; + while ( my $depend_by = $depends_by->Next ) { + push @depends_by, $depend_by->LocalBase; + } + $clone->{'DependsOn-new'} = join ' ', @depends_by; + + while ( my $member = $members->Next ) { + push @members, $member->LocalBase; + } + $clone->{'MemberOf-new'} = join ' ', @members; + + my $members_of = $CloneTicketObj->MemberOf; + while ( my $member_of = $members_of->Next ) { + push @members_of, $member_of->LocalTarget; + } + $clone->{'new-MemberOf'} = join ' ', @members_of; + + } + + my $cfs = $CloneTicketObj->QueueObj->TicketCustomFields(); + while ( my $cf = $cfs->Next ) { + my $cf_id = $cf->id; + my $cf_values = $CloneTicketObj->CustomFieldValues( $cf->id ); + my @cf_values; + while ( my $cf_value = $cf_values->Next ) { + push @cf_values, $cf_value->Content; + } + $clone->{"Object-RT::Ticket--CustomField-$cf_id-Value"} = join "\n", + @cf_values; + } + + for ( keys %$clone ) { + $ARGS{$_} = $clone->{$_} if not defined $ARGS{$_}; + } + +} + +my @results; + +my $title = loc("Create a ticket"); + +my $QueueObj = new RT::Queue($session{'CurrentUser'}); +$QueueObj->Load($Queue) || Abort(loc("Queue could not be loaded.")); + +$m->callback( QueueObj => $QueueObj, title => \$title, results => \@results, ARGSRef => \%ARGS ); + +$QueueObj->Disabled && Abort(loc("Cannot create tickets in a disabled queue.")); + +my $CFs = $QueueObj->TicketCustomFields(); + +my $ValidCFs = $m->comp( + '/Elements/ValidateCustomFields', + CustomFields => $CFs, + ARGSRef => \%ARGS +); + +# {{{ deal with deleting uploaded attachments +foreach my $key (keys %ARGS) { + if ($key =~ m/^DeleteAttach-(.+)$/) { + delete $session{'Attachments'}{$1}; + } + $session{'Attachments'} = { %{$session{'Attachments'} || {}} }; +} +# }}} + +# {{{ store the uploaded attachment in session +if ($ARGS{'Attach'}) { # attachment? + my $attachment = MakeMIMEEntity( + AttachmentFieldName => 'Attach' + ); + + my $file_path = Encode::decode_utf8("$ARGS{'Attach'}"); + $session{'Attachments'} = { + %{$session{'Attachments'} || {}}, + $file_path => $attachment, + }; +} +# }}} + +# delete temporary storage entry to make WebUI clean +unless (keys %{$session{'Attachments'}} and $ARGS{'id'} eq 'new') { + delete $session{'Attachments'}; +} + +my $checks_failure = 0; + +my $gnupg_widget = $m->comp('/Elements/GnuPG/SignEncryptWidget:new', Arguments => \%ARGS ); +$m->comp( '/Elements/GnuPG/SignEncryptWidget:Process', + self => $gnupg_widget, + QueueObj => $QueueObj, +); + + +if ( !exists $ARGS{'AddMoreAttach'} && ($ARGS{'id'}||'') eq 'new' ) { + my $status = $m->comp('/Elements/GnuPG/SignEncryptWidget:Check', + self => $gnupg_widget, + Operation => 'Create', + QueueObj => $QueueObj, + ); + $checks_failure = 1 unless $status; +} + +# check email addresses for RT's +{ + foreach my $field ( qw(Requestors Cc AdminCc) ) { + my $value = $ARGS{ $field }; + next unless defined $value && length $value; + + my @emails = Email::Address->parse( $value ); + foreach my $email ( grep RT::EmailParser->IsRTAddress($_->address), @emails ) { + push @results, loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email->format, loc($field =~ /^(.*?)s?$/) ); + $checks_failure = 1; + $email = undef; + } + $ARGS{ $field } = join ', ', map $_->format, grep defined, @emails; + } +} + +my $skip_create = 0; +$m->callback( CallbackName => 'BeforeCreate', ARGSRef => \%ARGS, skip_create => \$skip_create, + checks_failure => $checks_failure, results => \@results ); + +if ((!exists $ARGS{'AddMoreAttach'}) and (defined($ARGS{'id'}) and $ARGS{'id'} eq 'new')) { # new ticket? + if ( $ValidCFs && !$checks_failure && !$skip_create ) { + $m->comp('show', %ARGS); + $RT::Logger->crit("After display call; error is $@"); + $m->abort(); + } + elsif ( !$ValidCFs ) { + # Invalid CFs + while (my $CF = $CFs->Next) { + my $msg = $m->notes('InvalidField-' . $CF->Id) or next; + push @results, $CF->Name . ': ' . $msg; + } + } +} + + + + +</%init> +<&| /m/_elements/wrapper, title => $title &> +<& /Elements/ListActions, actions => \@results &> +<form action="<% RT->Config->Get('WebPath') %>/m/ticket/create" method="post" enctype="multipart/form-data" name="TicketCreate" id="ticket-create"> +<input type="hidden" class="hidden" name="id" value="new" /> +% $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS ); +% if ($gnupg_widget) { +<& /Elements/GnuPG/SignEncryptWidget:ShowIssues, self => $gnupg_widget &> +% } + + +<div id="ticket-create-simple"> +<&| /Widgets/TitleBox, title => $QueueObj->Name &> + +<%perl> +$showrows->( + loc("Subject") => '<input name="Subject" size="30" maxsize="200" value="'.($ARGS{Subject} || '').'" />'); +</%perl> + <span class="content-label label"><%loc("Describe the issue below")%></span> + <& /Elements/MessageBox, exists $ARGS{Content} ? (Default => $ARGS{Content}, IncludeSignature => 0 ) : ( QuoteTransaction => $QuoteTransaction ), Height => 5 &> + + +<&/Elements/Submit, Label => loc("Create") &> + + +</&> +</div> + +<div id="ticket-create-basics"> +<&| /Widgets/TitleBox &> + <input type="hidden" class="hidden" name="Queue" value="<%$QueueObj->id %>" /> +<%perl> + +$showrows->( + + # loc('Queue') => $m->scomp( '/Ticket/Elements/ShowQueue', QueueObj => $QueueObj ) , + + loc('Status') => + + $m->scomp( + "/Elements/SelectStatus", + Name => "Status", + Default => $ARGS{Status} || 'new', + DefaultValue => 0, + SkipDeleted => 1 + ), + + loc("Owner") => + + $m->scomp( + "/Elements/SelectOwner", + Name => "Owner", + QueueObj => $QueueObj, + Default => $ARGS{Owner} || $RT::Nobody->Id, + DefaultValue => 0 + ), + + loc("Requestors") => $m->scomp( + "/Elements/EmailInput", + Name => 'Requestors', + Size => '40', + Default => $ARGS{Requestors} || $session{CurrentUser}->EmailAddress + ), + + loc("Cc") => + + $m->scomp( "/Elements/EmailInput", Name => 'Cc', Size => '40', Default => $ARGS{Cc} ) + . '<span class="comment"><i><font size="-2">' + . loc( + "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <strong>will</strong> receive future updates.)" + ) + . '</font></i></span>', + + loc("Admin Cc") => + + $m->scomp( "/Elements/EmailInput", Name => 'AdminCc', Size => '40', Default => $ARGS{AdminCc} ) + . '<span class="comment" colspan="2"><i><font size="-2">' + . loc( + "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <strong>will</strong> receive future updates.)" + ) + . '</font></i></span>', + + +); + + +$m->scomp("/Ticket/Elements/EditCustomFields", %ARGS, QueueObj => $QueueObj ); + + +$m->scomp("/Ticket/Elements/EditTransactionCustomFields", %ARGS, QueueObj => $QueueObj ); + +</%perl> +% if (exists $session{'Attachments'}) { + +<%loc("Attached file") %> + +<%loc("Check box to delete")%><br /> +% foreach my $attach_name (keys %{$session{'Attachments'}}) { +<input type="checkbox" class="checkbox" name="DeleteAttach-<%$attach_name%>" value="1" /><%$attach_name%><br /> +% } # end of foreach + + +% } # end of if + +<%perl> +$showrows->( + loc("Attach file") => + + '<div class="value" colspan="5"> +<input type="file" name="Attach" /> +<input type="submit" class="button" name="AddMoreAttach" value="' . loc("Add More Files") . '" />' +); +</%perl> + + +% if ( $gnupg_widget ) { +%$m->scomp("/Elements/GnuPG/SignEncryptWidget", self => $gnupg_widget, QueueObj => $QueueObj ) +% } + + + <div class="ticket-info-basics"> + <&| /Widgets/TitleBox, title => loc('The Basics'), + title_class=> 'inverse', + color => "#993333" &> +<%perl> +$showrows->( + loc("Priority") => $m->scomp( + "/Elements/SelectPriority", + Name => "InitialPriority", + Default => $ARGS{InitialPriority} ? $ARGS{InitialPriority} : $QueueObj->InitialPriority, + ), + loc("Final Priority") => $m->scomp( + "/Elements/SelectPriority", + Name => "FinalPriority", + Default => $ARGS{FinalPriority} ? $ARGS{FinalPriority} : $QueueObj->FinalPriority, + ), + + loc("Time Estimated") => '<span class="timefield">'.$m->scomp( + "/Elements/EditTimeValue", + Name => 'TimeEstimated', + Default => $ARGS{TimeEstimated} || '', + InUnits => $ARGS{'TimeEstimated-TimeUnits'} + ).'</span>', + + loc("Time Worked") => '<span class="timefield">'.$m->scomp( + "/Elements/EditTimeValue", + Name => 'TimeWorked', + Default => $ARGS{TimeWorked} || '', + InUnits => $ARGS{'TimeWorked-TimeUnits'} + ). '</span>', + + loc("Time Left") => '<span class="timefield">'.$m->scomp( + "/Elements/EditTimeValue", + Name => 'TimeLeft', + Default => $ARGS{TimeLeft} || '', + InUnits => $ARGS{'TimeLeft-TimeUnits'} + ).'</span>', +); + +</%perl> +</&> +<&|/Widgets/TitleBox, title => loc("Dates"), + title_class=> 'inverse', + color => "#663366" &> + +<%perl> +$showrows->( + loc("Starts") => $m->scomp( "/Elements/SelectDate", Name => "Starts", Default => ( $ARGS{Starts} || '' )), + loc("Due") => $m->scomp( "/Elements/SelectDate", Name => "Due", Default => ($ARGS{Due} || '' )) +); + +</%perl> +</&> + +<&|/Widgets/TitleBox, title => loc('Links'), title_class=> 'inverse' &> + +<em><%loc("(Enter ticket ids or URLs, separated with spaces)")%></em> + +<%perl> +$showrows->( + loc("Depends on") => '<input size="10" name="new-DependsOn" value="' . ($ARGS{'new-DependsOn'} || '' ). '" />', + loc("Depended on by") => '<input size="10" name="DependsOn-new" value="' . ($ARGS{'DependsOn-new'} || '' ) . '" />', + loc("Parents") => '<input size="10" name="new-MemberOf" value="' . ($ARGS{'new-MemberOf'} || '') . '" />', + loc("Children") => '<input size="10" name="MemberOf-new" value="' . ($ARGS{'MemberOf-new'} || '') . '" />', + loc("Refers to") => '<input size="10" name="new-RefersTo" value="' . ($ARGS{'new-RefersTo'} || '') . '" />', + loc("Referred to by") => '<input size="10" name="RefersTo-new" value="' . ($ARGS{'RefersTo-new'} || ''). '" />' +); +</%perl> + +</&> + + +<& /Elements/Submit, Label => loc("Create") &> +</form> +</&> +</&> diff --git a/rt/share/html/m/ticket/history b/rt/share/html/m/ticket/history new file mode 100644 index 000000000..a49945d77 --- /dev/null +++ b/rt/share/html/m/ticket/history @@ -0,0 +1,31 @@ +<%args> +$id => undef +</%args> +<%init> +my $t = RT::Ticket->new($session{CurrentUser}); +$t->Load($id); +my $history = $t->Transactions()->ItemsArrayRef; +</%init> +<&| /m/_elements/wrapper, title => $t->Subject &> +<div class="history"> +<& /m/_elements/ticket_menu, ticket => $t &> +<&|/Widgets/TitleBox &> +<ul class="history-list"> +% for my $entry (reverse @$history) { +<li> +<span class="age"><% $entry->CreatedObj->AgeAsString() %></span> - +<& /Elements/ShowUser, User => $entry->CreatorObj &> - +<%$entry->BriefDescription%> +% if ($entry->Type !~ /EmailRecord/) { +% if ($entry->ContentObj) { +<div class="txn-content"> +<%$entry->Content%> +</div> +%} +% } +</li> +% } +</ul> +</&> +</div> +</&> diff --git a/rt/share/html/m/ticket/modify b/rt/share/html/m/ticket/modify new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/rt/share/html/m/ticket/modify diff --git a/rt/share/html/m/ticket/reply b/rt/share/html/m/ticket/reply new file mode 100644 index 000000000..ea2a6cad4 --- /dev/null +++ b/rt/share/html/m/ticket/reply @@ -0,0 +1,171 @@ +<&|/m/_elements/wrapper, title => loc('Update ticket #[_1]', $t->id) &> +<& /m/_elements/ticket_menu, ticket => $t &> +<& /Elements/ListActions, actions => \@results &> +<div class="ticket-reply"> +<&|/Widgets/TitleBox &> +<form action="reply" id="update" + method="post" enctype="multipart/form-data"> +<input type="hidden" class="hidden" name="DefaultStatus" value="<% $DefaultStatus ||''%>" /> +<input type="hidden" class="hidden" name="Action" value="<% $ARGS{Action}||'' %>" /> + +<div class="entry"><span class="label"><&|/l&>Status</&>:</span> +<div class="value"> +<& /Elements/SelectStatus, Name=>"Status", DefaultLabel => loc("[_1] (Unchanged)", loc($t->Status)), Default => $ARGS{'Status'} || ($t->Status eq $DefaultStatus ? undef : $DefaultStatus)&> +</div></div> + +<div class="entry"><span class="label"><&|/l&>Owner</&>:</span> +<div class="value"> +<& /Elements/SelectOwner, + Name => "Owner", + TicketObj => $t, + QueueObj => $t->QueueObj, + DefaultLabel => loc("[_1] (Unchanged)", $t->OwnerObj->Name), + Default => $ARGS{'Owner'} +&> +</div></div> +<div class="entry timefield"><span class="label"><&|/l&>Worked</&>:</span><span class="value"> +<& /Elements/EditTimeValue, + Name => 'UpdateTimeWorked', + Default => $ARGS{UpdateTimeWorked}||'', + InUnits => $ARGS{'UpdateTimeWorked-TimeUnits'}||'minutes', +&> +</span></div> +<input type="hidden" class="hidden" name="id" value="<%$t->Id%>" /><br /> +<div class="entry"><span class="label"><&|/l&>Update Type</&>:</span> +<div class="value"><select name="UpdateType"> +% if ($CanComment) { +<option value="private" <% ($ARGS{'UpdateType'} && $ARGS{'UpdateType'} eq "private") ? qq[ selected="selected"] : !$ARGS{'UpdateType'}&&$CommentDefault |n %>><&|/l&>Comments (Not sent to requestors)</&></option> +% } +% if ($CanRespond) { +<option value="response" <% ($ARGS{'UpdateType'} && $ARGS{'UpdateType'} eq "response") ? qq[ selected="selected"] : !$ARGS{'UpdateType'}&&$ResponseDefault |n %>><&|/l&>Reply to requestors</&></option> +% } +</select> +</div></div> +<div class="entry"><span class="label"><&|/l&>Subject</&>:</span><div class="value"> <input name="UpdateSubject" size="60" value="<% $ARGS{UpdateSubject} || $t->Subject()%>" /> +% $m->callback( %ARGS, CallbackName => 'AfterSubject' ); +</div></div> + +<div class="entry"><span class="label"><&|/l&>One-time Cc</&>:</span><span class="value"><& /Elements/EmailInput, Name => 'UpdateCc', Size => '60', Default => $ARGS{UpdateCc} &></span></div> + +<div class="entry"><span class="label"><&|/l&>One-time Bcc</&>:</span><span class="value"><& /Elements/EmailInput, Name => 'UpdateBcc', Size => '60', Default => $ARGS{UpdateBcc} &></span></div> + +<div class="entry"><span class="label" ><&|/l&>Message</&>:</span><div class="value"> +% if (exists $ARGS{UpdateContent}) { +% # preserve QuoteTransaction so we can use it to set up sane references/in/reply to +% my $temp = $ARGS{'QuoteTransaction'}; +% delete $ARGS{'QuoteTransaction'}; +<& /Elements/MessageBox, Name=>"UpdateContent", Default=>$ARGS{UpdateContent}, IncludeSignature => 0, %ARGS&> +% $ARGS{'QuoteTransaction'} = $temp; +% } else { +% my $IncludeSignature = 1; +% $IncludeSignature = 0 if $Action ne 'Respond' && !RT->Config->Get('MessageBoxIncludeSignatureOnComment'); +<& /Elements/MessageBox, Name=>"UpdateContent", IncludeSignature => $IncludeSignature, %ARGS &> +% } +</div></div> +<& /Elements/Submit, Label => loc('Update Ticket'), Name => 'SubmitTicket' &> +</form> +</&> +</div> +</&> +<%INIT> +my $CanRespond = 0; +my $CanComment = 0; +my $checks_failure = 0; +my $title; + +my $t = LoadTicket($id); + +my @results; + +$m->callback( Ticket => $t, ARGSRef => \%ARGS, results => \@results, CallbackName => 'Initial' ); + +unless($DefaultStatus){ + $DefaultStatus=($ARGS{'Status'} ||$t->Status()); +} + +if ($DefaultStatus eq 'new'){ + $DefaultStatus='open'; +} + +if ($DefaultStatus eq 'resolved') { + $title = loc("Resolve ticket #[_1] ([_2])", $t->id, $t->Subject); +} else { + $title = loc("Update ticket #[_1] ([_2])", $t->id, $t->Subject); +} + +# Things needed in the template - we'll do the processing here, just +# for the convenience: + +my ($CommentDefault, $ResponseDefault); +if ($Action ne 'Respond') { + $CommentDefault = qq[ selected="selected"]; + $ResponseDefault = ""; +} else { + $CommentDefault = ""; + $ResponseDefault = qq[ selected="selected"]; +} + + + +$CanRespond = 1 if ( $t->CurrentUserHasRight('ReplyToTicket') or + $t->CurrentUserHasRight('ModifyTicket') ); + +$CanComment = 1 if ( $t->CurrentUserHasRight('CommentOnTicket') or + $t->CurrentUserHasRight('ModifyTicket') ); + + +# {{{ deal with deleting uploaded attachments +foreach my $key (keys %ARGS) { + if ($key =~ m/^DeleteAttach-(.+)$/) { + delete $session{'Attachments'}{$1}; + } + $session{'Attachments'} = { %{$session{'Attachments'} || {}} }; +} +# }}} + +# {{{ store the uploaded attachment in session +if ($ARGS{'Attach'}) { # attachment? + my $attachment = MakeMIMEEntity( + AttachmentFieldName => 'Attach' + ); + + my $file_path = Encode::decode_utf8("$ARGS{'Attach'}"); + $session{'Attachments'} = { + %{$session{'Attachments'} || {}}, + $file_path => $attachment, + }; +} +# }}} + +# delete temporary storage entry to make WebUI clean +unless (keys %{$session{'Attachments'}} and $ARGS{'UpdateAttach'}) { + delete $session{'Attachments'}; +} +# }}} + +# check email addresses for RT's +{ + foreach my $field ( qw(UpdateCc UpdateBcc) ) { + my $value = $ARGS{ $field }; + next unless defined $value && length $value; + + my @emails = Email::Address->parse( $value ); + foreach my $email ( grep RT::EmailParser->IsRTAddress($_->address), @emails ) { + push @results, loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email->format, loc(substr($field, 6)) ); + $checks_failure = 1; + $email = undef; + } + $ARGS{ $field } = join ', ', map $_->format, grep defined, @emails; + } +} + +if ( !$checks_failure && exists $ARGS{SubmitTicket} ) { + return $m->comp('/m/ticket/show', TicketObj => $t, %ARGS); +} +</%INIT> + +<%ARGS> +$id => undef +$Action => 'reply' +$DefaultStatus => undef +</%ARGS> diff --git a/rt/share/html/m/ticket/select_create_queue b/rt/share/html/m/ticket/select_create_queue new file mode 100644 index 000000000..88cf2033b --- /dev/null +++ b/rt/share/html/m/ticket/select_create_queue @@ -0,0 +1,18 @@ +<%init> +my $queues = RT::Queues->new($session{'CurrentUser'}); +$queues->UnLimit(); + +</%init> +<&| /m/_elements/wrapper, title => loc("Create a ticket") &> +<div class="select_queue"> +<&|/Widgets/TitleBox, title => loc("Select a queue") &> +<ul class="menu"> +% while (my $q = $queues->Next()) { +% next if (! $q->CurrentUserHasRight('CreateTicket')); +<li><a href="<%RT->Config->Get('WebPath')%>/m/ticket/create?Queue=<%$q->id%>"><%$q->Name%></a></li> +% } +</ul> +</&> +</div> +</&> + diff --git a/rt/share/html/m/ticket/show b/rt/share/html/m/ticket/show new file mode 100644 index 000000000..e979da3e6 --- /dev/null +++ b/rt/share/html/m/ticket/show @@ -0,0 +1,454 @@ +<%args> +$id => undef +</%args> +<%init> +my $Ticket; +my @Actions; + +unless ($id) { + Abort('No ticket specified'); +} + +if ($ARGS{'id'} eq 'new') { + # {{{ Create a new ticket + + my $Queue = new RT::Queue( $session{'CurrentUser'} ); + $Queue->Load($ARGS{'Queue'}); + unless ( $Queue->id ) { + Abort('Queue not found'); + } + + unless ( $Queue->CurrentUserHasRight('CreateTicket') ) { + Abort('You have no permission to create tickets in that queue.'); + } + + ($Ticket, @Actions) = CreateTicket( + Attachments => delete $session{'Attachments'}, + %ARGS, + ); + unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) { + Abort("No permission to view newly created ticket #".$Ticket->id."."); + } + # }}} +} else { + $Ticket ||= LoadTicket($ARGS{'id'}); + + $m->callback( CallbackName => 'BeforeProcessArguments', + TicketObj => $Ticket, + ActionsRef => \@Actions, ARGSRef => \%ARGS ); + if ( defined $ARGS{'Action'} ) { + if ($ARGS{'Action'} =~ /^(Steal|Kill|Take|SetTold)$/) { + my $action = $1; + my ($res, $msg) = $Ticket->$action(); + push(@Actions, $msg); + } + } + + $m->callback(CallbackName => 'ProcessArguments', + Ticket => $Ticket, + ARGSRef => \%ARGS, + Actions => \@Actions); + + $ARGS{UpdateAttachments} = $session{'Attachments'}; + push @Actions, + ProcessUpdateMessage( + ARGSRef => \%ARGS, + Actions => \@Actions, + TicketObj => $Ticket, + ); + delete $session{'Attachments'}; + + #Process status updates + push @Actions, ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $Ticket ); + push @Actions, ProcessTicketBasics( ARGSRef => \%ARGS, TicketObj => $Ticket ); + push @Actions, ProcessTicketLinks( ARGSRef => \%ARGS, TicketObj => $Ticket ); + push @Actions, ProcessTicketDates( ARGSRef => \%ARGS, TicketObj => $Ticket ); + push @Actions, ProcessObjectCustomFieldUpdates(ARGSRef => \%ARGS, TicketObj => $Ticket ); + + # XXX: we shouldn't block actions here if user has no right to see the ticket, + # but we should allow him to see actions he has done + unless ($Ticket->CurrentUserHasRight('ShowTicket')) { + Abort("No permission to view ticket"); + } + if ( $ARGS{'MarkAsSeen'} ) { + $Ticket->SetAttribute( + Name => 'User-'. $Ticket->CurrentUser->id .'-SeenUpTo', + Content => $Ticket->LastUpdated, + ); + push @Actions, loc('Marked all messages as seen'); + } +} + +$m->callback( + CallbackName => 'BeforeDisplay', + TicketObj => \$Ticket, + Actions => \@Actions, + ARGSRef => \%ARGS, +); + +# This code does automatic redirection if any updates happen. + +if (@Actions) { + + # We've done something, so we need to clear the decks to avoid + # resubmission on refresh. + # But we need to store Actions somewhere too, so we don't lose them. + my $key = Digest::MD5::md5_hex( rand(1024) ); + push @{ $session{"Actions"}->{$key} ||= [] }, @Actions; + $session{'i'}++; + my $url = RT->Config->Get('WebURL') . "m/ticket/show?id=" . $Ticket->id . "&results=" . $key; + $url .= '#' . $ARGS{Anchor} if $ARGS{Anchor}; + RT::Interface::Web::Redirect($url); +} + +# If we haven't been passed in an Attachments object (through the precaching mechanism) +# then we need to find one +my $Attachments = $m->comp('/Ticket/Elements/FindAttachments', Ticket => $Ticket); + +my %documents; +while ( my $attach = $Attachments->Next() ) { + next unless ($attach->Filename()); + unshift( @{ $documents{ $attach->Filename } }, $attach ); +} + +my $Customers = $Ticket->Customers; +my @customers; +while ( my $customer = $Customers->Next() ) { + push @customers, $customer; +} + +my $CustomFields = $Ticket->CustomFields; +$m->callback( + CallbackName => 'MassageCustomFields', + Object => $Ticket, + CustomFields => $CustomFields, +); + +my $print_value = sub { + my ($cf, $value) = @_; + my $linked = $value->LinkValueTo; + if ( defined $linked && length $linked ) { + my $linked = $m->interp->apply_escapes( $linked, 'h' ); + $m->out('<a href="'. $linked .'" target="_new">'); + } + my $comp = "ShowCustomField". $cf->Type; + $m->callback( + CallbackName => 'ShowComponentName', + Name => \$comp, + CustomField => $cf, + Object => $Ticket, + ); + if ( $m->comp_exists( $comp ) ) { + $m->comp( $comp, Object => $value ); + } else { + $m->out( $m->interp->apply_escapes( $value->Content, 'h' ) ); + } + $m->out('</a>') if defined $linked && length $linked; + + # This section automatically populates a<div with the "IncludeContentForValue" for this custom + # field if it's been defined + if ( $cf->IncludeContentForValue ) { + my $vid = $value->id; + $m->out( '<div class="object_cf_value_include" id="object_cf_value_'. $vid .'">' ); + $m->print( loc("See also:") ); + $m->out( '<a href="'. $value->IncludeContentForValue .'">' ); + $m->print( $value->IncludeContentForValue ); + $m->out( qq{</a></div>\n} ); + $m->out( qq{<script><!--\nahah('} ); + $m->print( $value->IncludeContentForValue ); + $m->out( qq{', 'object_cf_value_$vid');\n--></script>\n} ); + } +}; + +</%init> +<&| /m/_elements/wrapper, title => $Ticket->Subject &> +<div id="ticket-show"> +<& /m/_elements/ticket_menu, ticket => $Ticket &> + + <&| /Widgets/TitleBox, title => loc('The Basics'), + class => 'ticket-info-basics', + &> + + + <div class="entry"> + <div class="label id"><&|/l&>Id</&>:</div> + <div class="value id"><%$Ticket->Id %></div> + </div> + <div class="entry"> + <div class="label status"><&|/l&>Status</&>:</div> + <div class="value status"><% loc($Ticket->Status) %></div> + </div> +% if ($Ticket->TimeEstimated) { + <div class="entry"> + <div class="label time estimated"><&|/l&>Estimated</&>:</div> + <div class="value time estimated"><& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeEstimated &></div> + </div> +% } +% if ($Ticket->TimeWorked) { + <div class="entry"> + <div class="label time worked"><&|/l&>Worked</&>:</div> + <div class="value time worked"><& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeWorked &></div> + </div> +% } +% if ($Ticket->TimeLeft) { + <div class="entry"> + <div class="label time left"><&|/l&>Left</&>:</div> + <div class="value time left"><& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeLeft &></div> + </div> +% } + <div class="entry"> + <div class="label priority"><&|/l&>Priority</&>:</div> + <div class="value priority"><& /Ticket/Elements/ShowPriority, Ticket => $Ticket &></div> + </div> + <div class="entry"> + <div class="label queue"><&|/l&>Queue</&>:</div> + <div class="value queue"><& /Ticket/Elements/ShowQueue, QueueObj => $Ticket->QueueObj &></div> + </div> + </&> + +% if ($Ticket->CustomFields->First) { + <&| /Widgets/TitleBox, title => loc('Custom Fields'), + class => 'ticket-info-cfs', + &> + +% while ( my $CustomField = $CustomFields->Next ) { +% my $Values = $Ticket->CustomFieldValues( $CustomField->Id ); +% my $count = $Values->Count; + <div class="entry" id="CF-<%$CustomField->id%>-ShowRow"> + <div class="label"><% $CustomField->Name %>:</div> + <div class="value"> +% unless ( $count ) { +<i><&|/l&>(no value)</&></i> +% } elsif ( $count == 1 ) { +% $print_value->( $CustomField, $Values->First ); +% } else { +<ul> +% while ( my $Value = $Values->Next ) { +<li> +% $print_value->( $CustomField, $Value ); +</li> +% } +</ul> +% } + </div> + </div> +% } + +</&> +% } + + <&| /Widgets/TitleBox, title => loc('People'), class => 'ticket-info-people' &> + + + <div class="entry"> + <div class="label"><&|/l&>Owner</&>:</div> + <div class="value"><& /Elements/ShowUser, User => $Ticket->OwnerObj, Ticket => $Ticket &> + </div> + </div> + <div class="entry"> + <div class="label"><&|/l&>Requestors</&>:</div> + <div class="value"><& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->Requestors, Ticket => $Ticket &></div> + </div> + <div class="entry"> + <div class="label"><&|/l&>Cc</&>:</div> + <div class="value"><& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->Cc, Ticket => $Ticket &></div> + </div> + <div class="entry"> + <div class="label"><&|/l&>AdminCc</&>:</div> + <div class="value"><& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->AdminCc, Ticket => $Ticket &></div> + </div> + + </&> + +% if (keys %documents) { +<&| /Widgets/TitleBox, title => loc('Attachments'), + title_class=> 'inverse', + class => 'ticket-info-attachments', + color => "#336699" &> + +% foreach my $key (keys %documents) { + +<%$key%><br /> +<ul> +% foreach my $rev (@{$documents{$key}}) { + +<%PERL> +my $size = $rev->ContentLength; + +if ($size) { + my $kb = int($size/102.4) / 10; + my $units = RT->Config->Get('AttachmentUnits'); + + if (!defined($units)) { + if ($size > 1024) { + $size = $kb . "k"; + } + else { + $size = $size . "b"; + } + } + elsif ($units eq 'k') { + $size = $kb . "k"; + } + else { + $size = $size . "b"; + } + +</%PERL> + +<li><font size="-2"> +<a href="<%RT->Config->Get('WebPath')%>/Ticket/Attachment/<%$rev->TransactionId%>/<%$rev->Id%>/<%$rev->Filename | u%>"> +<&|/l, $rev->CreatedAsString, $size, $rev->CreatorObj->Name &>[_1] ([_2]) by [_3]</&> +</a> +</font></li> +% } +% } +</ul> + +% } +</&> + +% } +% # too painful to deal with reminders +% if ( 0 && RT->Config->Get('EnableReminders') ) { + <&|/Widgets/TitleBox, title => loc("Reminders"), + class => 'ticket-info-reminders', + &> + <div class="entry"><div + <form action="<%RT->Config->Get('WebPath')%>/Ticket/Display.html" method="post"> + <& /Ticket/Elements/Reminders, Ticket => $Ticket, ShowCompleted => 0 &> + <div align="right"><input type="submit" class="button" value="<&|/l&>Save</&>" /></div> + </form> + </div></div> + </&> +% } + +% if ( @customers ) { + <&| /Widgets/TitleBox, title => loc("Customers"), + class => 'ticket-info-customers', + &> +% foreach my $customer ( @customers ) { +% my $resolver = $customer->TargetURI->Resolver or next; +<div class="entry"><a href="<% $resolver->HREF %>"><% $resolver->AsString |n%></A> +</div> +% } #foreach + </&> +% } # if @customers + + + <&| /Widgets/TitleBox, title => loc("Dates"), + class => 'ticket-info-dates', + &> + + + <div class="entry"> + <div class="label date created"><&|/l&>Created</&>:</div> + <div class="value date created"><% $Ticket->CreatedObj->AsString %></div> + </div> + <div class="entry"> + <div class="label date starts"><&|/l&>Starts</&>:</div> + <div class="value date starts"><% $Ticket->StartsObj->AsString %></div> + </div> + <div class="entry"> + <div class="label date started"><&|/l&>Started</&>:</div> + <div class="value date started"><% $Ticket->StartedObj->AsString %></div> + </div> + <div class="entry"> + <div class="label date told"><&|/l&>Last Contact</&>:</div> + <div class="value date told"><% $Ticket->ToldObj->AsString %></div> + </div> + <div class="entry"> + <div class="label date due"><&|/l&>Due</&>:</div> +% my $due = $Ticket->DueObj; +% if ( $due && $due->Unix > 0 && $due->Diff < 0 ) { + <div class="value date due"><span class="overdue"><% $due->AsString %></span></div> +% } else { + <div class="value date due"><% $due->AsString %></div> +% } + </div> + <div class="entry"> + <div class="label date resolved"><&|/l&>Closed</&>:</div> + <div class="value date resolved"><% $Ticket->ResolvedObj->AsString %></div> + </div> + <div class="entry"> + <div class="label date updated"><&|/l&>Updated</&>:</div> +% my $UpdatedString = $Ticket->LastUpdated ? loc("[_1] by [_2]", $Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name) : loc("Never"); + <div class="value date updated"><% $UpdatedString | h %></div> + </div> + + </&> + + <&| /Widgets/TitleBox, title => loc('Links'), class => 'ticket-info-links' &> + + <div class="entry"> + <div class="label"><% loc('Depends on')%>:</div> + <div class="value"> + +<%PERL> +my ( @active, @inactive, @not_tickets ); +for my $link ( @{ $Ticket->DependsOn->ItemsArrayRef } ) { + my $target = $link->TargetObj; + if ( $target && $target->isa('RT::Ticket') ) { + if ( $target->QueueObj->IsInactiveStatus( $target->Status ) ) { + push( @inactive, $link->TargetURI ); + } + else { + push( @active, $link->TargetURI ); + } + } + else { + push( @not_tickets, $link->TargetURI ); + } +} +</%PERL> + + +<ul> +% for my $Link (@not_tickets, @active, @inactive) { +<li><& /Elements/ShowLink, URI => $Link &></li> +% } +</ul> + </div> + </div> + <div class="entry"> + <div class="label"><% loc('Depended on by')%>:</div> + <div class="value"> +<ul> +% while (my $Link = $Ticket->DependedOnBy->Next) { +<li><& /Elements/ShowLink, URI => $Link->BaseURI &></li> +% } +</ul> + </div> + </div> + <div class="entry"> + <div class="label"><% loc('Parents') %>:</div> + <div class="value"><& /Ticket/Elements/ShowParents, Ticket => $Ticket &></div> + </div> + <div class="entry"> + <div class="label"><% loc('Children')%>:</div> + <div class="value"><& /Ticket/Elements/ShowMembers, Ticket => $Ticket &></div> + </div> + <div class="entry"> + <div class="label"><% loc('Refers to')%>:</div> + <div class="value"> +<ul> +% while (my $Link = $Ticket->RefersTo->Next) { +<li><& /Elements/ShowLink, URI => $Link->TargetURI &></li> +% } +</ul> + </div> + </div> + <div class="entry"> + <div class="label"><% loc('Referred to by')%>:</div> + <div class="value"> + <ul> +% while (my $Link = $Ticket->ReferredToBy->Next) { +% next if (UNIVERSAL::isa($Link->BaseObj, 'RT::Ticket') && $Link->BaseObj->Type eq 'reminder'); +<li><& /Elements/ShowLink, URI => $Link->BaseURI &></li> +% } +</ul> + </div> + </div> + </&> +</div> +</&> diff --git a/rt/share/html/m/tickets/requested b/rt/share/html/m/tickets/requested new file mode 100644 index 000000000..3043e05ab --- /dev/null +++ b/rt/share/html/m/tickets/requested @@ -0,0 +1,4 @@ +<%init> + $m->comp('../_elements/ticket_list', %ARGS, query => 'Requestors.EmailAddress = "'.$session{CurrentUser}->EmailAddress.'" AND (Status != "resolved" AND Status != "rejected" AND Status != "stalled")'); +$m->abort(); +</%init> diff --git a/rt/share/html/m/tickets/search b/rt/share/html/m/tickets/search new file mode 100644 index 000000000..16864b4d3 --- /dev/null +++ b/rt/share/html/m/tickets/search @@ -0,0 +1,64 @@ +<%args> +$page => 1 +$order_by => 'id' +$order => 'desc' +$name => undef +</%args> +<%init> +use RT::Search::Googleish; +my $query = $ARGS{'query'}; +if ($ARGS{'q'}) { + my $tickets = RT::Tickets->new( $session{'CurrentUser'} ); + my %args = ( + Argument => $ARGS{q}, + TicketsObj => $tickets, + ); + my $search = RT::Search::Googleish->new(%args); + $query = $search->QueryToSQL(); + +} + +elsif ($ARGS{'name'}) { +my $search_arg; + +my $search; + + if ($name) { + ($search) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named( 'Search - ' . $name ); + unless ( $search && $search->Id ) { + my (@custom_searches) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named('SavedSearch'); + foreach my $custom (@custom_searches) { + if ( $custom->Description eq $name ) { $search = $custom; last } + } + unless ( $search && $search->id ) { + $m->out("Predefined search $name not found"); + return; + } + } + + $search_arg = $session{'CurrentUser'}->UserObj->Preferences( $search, $search->Content ); + } + + foreach ($search_arg) { + 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'; + $_->{'Query'} =~ s/__Bookmarks__/( $query )/g; + } + } + + $query = $search_arg->{Query}; + $order_by = $search_arg->{OrderBy}; + $order = $search_arg->{Order}; + +} + + +$m->comp('../_elements/ticket_list', query => $query, page => $page, order_by => $order_by, order => $order); +$m->abort(); +</%init> |
