Initial Redmine tooling and local plugin forks

This commit is contained in:
Jason Thistlethwaite
2026-04-24 22:01:18 +00:00
commit 9f682af0eb
683 changed files with 56878 additions and 0 deletions
@@ -0,0 +1,2 @@
<%= contact_tag(contact, :id => "customer_send_tag") %>
(<%= contact_email %>)
@@ -0,0 +1,87 @@
<% if !@issue.blank? && User.current.allowed_to?(:view_helpdesk_tickets, @project) %>
<span id="cusomer_profile_and_issues">
<div id="customer_profile">
<div class="contextual">
<%= link_to l(:button_update),
{:controller => 'helpdesk_tickets',
:action => 'edit',
:issue_id => @issue},
:remote => true if User.current.allowed_to?(:edit_helpdesk_tickets, @project) %>
</div>
<h3><%= l(:label_helpdesk_contact) %></h3>
<% unless !(@show_form == "true") %>
<%= form_for @helpdesk_ticket, :url => {:controller => 'helpdesk_tickets',
:action => 'update',
:issue_id => @issue},
:html => {:id => 'ticket_data_form',
:method => :put} do |f| %>
<% unless @helpdesk_ticket.new_record? %>
<div class="contextual">
<%= link_to image_tag('link_break.png'),
{:controller => 'helpdesk_tickets', :action => 'destroy', :id => @helpdesk_ticket},
:method => :delete,
:data => {:confirm => l(:text_are_you_sure)},
:title => l(:label_relation_delete) %>
</div>
<% end %>
<p class="contact_auto_complete"><%= label_tag :helpdesk_ticket_contact_id, l(:label_helpdesk_contact)%><br>
<%= select_contact_tag('helpdesk_ticket[contact_id]', @helpdesk_ticket.customer, :is_select => Contact.visible.by_project(ContactsSetting.cross_project_contacts? ? nil : @project).count < 50, :include_blank => false, :add_contact => true, :display_field => @helpdesk_ticket.customer.blank?) %>
</p>
<p><%= label_tag :helpdesk_ticket_source, l(:label_helpdesk_ticket_source)%><br>
<%= f.select :source, helpdesk_tickets_source_for_select %></p>
<p><%= f.text_field :ticket_date, :size => 12, :required => true, :value => @helpdesk_ticket.ticket_date.to_date, :label => l(:label_helpdesk_ticket_date) %> <%= f.text_field :ticket_time, :value => @helpdesk_ticket.ticket_date.to_s(:time), :size => 5 %><%= calendar_for('helpdesk_ticket_ticket_date') %> </p>
<p>
<%= label_tag :helpdesk_ticket_cc_address, l(:label_helpdesk_cc_address) %><br>
<% @cc_address = @helpdesk_ticket.cc_address.try(:split, ',') || [] %>
<%= f.select :cc_address, options_for_select(@cc_address.map { |email|[email, email] }, @cc_address), {}, {:multiple => true } %>
<br>
</p>
<%= submit_tag l(:button_update) %>
<%= link_to l(:button_cancel), {}, :onclick => "$('#ticket_data_form').hide(); return false;" %>
<% end %>
<% end %>
<span class="small-card">
<%= render :partial => 'contacts/contact_card', :object => @issue.customer if @issue.customer %>
</span>
</div>
<% if @issue.customer && (customer_issues = @issue.customer.all_tickets.preload(:status, :tracker, :helpdesk_ticket).visible.order_by_status.to_a).count - 1 > 0 %>
<div id="customer_previous_issues">
<div class="contextual">
<%= link_to l(:label_helpdesk_all) + " (#{customer_issues.count})", {:controller => 'issues',
:action => 'index',
:set_filter => 1,
:f => [:customer, :status_id],
:v => {:customer => [@issue.customer.id]},
:op => {:customer => '=', :status_id => '*'}} %>
</div>
<h3><%= l(:label_helpdesk_contact_activity) %> </h3>
<ul>
<% (customer_issues.first(5)).each do |issue| %>
<li title="<%= "#{issue.tracker} (#{issue.status})" if issue.tracker && issue.status %>" >
<span class="icon <%= helpdesk_ticket_source_icon(issue.helpdesk_ticket) %>"></span>
<span class="ticket-title <%= 'selected' if @issue == issue %>">
<%= link_to_issue(issue, :truncate => 60, :project => (@project != issue.project), :tracker => false) %>
</span>
<span class="ticket-meta">
<%= format_time(issue.created_on) %>
<%= "- #{issue.assigned_to.name}" if issue.assigned_to %>
</span>
</li>
<% end %>
</ul>
</div>
<% end %>
</span>
<% end %>
@@ -0,0 +1,6 @@
<% if @project && @project.module_enabled?(:contacts_helpdesk)%>
<h3><%= l(:label_helpdesk_reports) %></h3>
<%= link_to l(:label_helpdesk_first_response_time), project_helpdesk_reports_path(@project, :report => 'first_response_time') %>
<br>
<%= link_to l(:label_helpdesk_busiest_time_of_day), project_helpdesk_reports_path(@project, :report => 'busiest_time_of_day') %>
<% end %>
@@ -0,0 +1,154 @@
<% if authorize_for(:issues, :send_helpdesk_response) && @issue.customer && @issue.customer.primary_email %>
<script type="text/javascript" charset="utf-8">
function emailTagResult (opt) {
if (opt.name){
var formated_tag = $('<span>' + opt.avatar + '&nbsp;' + opt.text + '</span>');;
} else {
var formated_tag = opt.text;
}
return formated_tag
};
function emailTagSelection (opt) {
if (opt.name){
var formated_tag = opt.name + ' <' + opt.email + '>';
} else {
var formated_tag = opt.text;
}
return formated_tag
};
$(document).ready(function() {
toggleSendMail($('#helpdesk_is_send_mail').get(0));
var select2_fields = ['#helpdesk_to', '#helpdesk_cc', '#helpdesk_bcc'];
$.each(select2_fields, function(index, field){
$(field).select2({
ajax: {
url: '<%= auto_complete_contacts_path(:project_id => (ContactsSetting.cross_project_contacts? ? nil : @project), :is_company => nil) %>',
dataType: 'json',
delay: 250,
data: function (params) {
return { q: params.term };
},
processResults: function (data, params) {
return { results: $.grep(data, function(elem){ elem.id = elem.email; return elem }) };
},
cache: true
},
tags: true,
placeholder: ' ',
minimumInputLength: 1,
width: '60%',
templateResult: emailTagResult,
templateSelection: emailTagSelection,
}).on('select2:open', function (e) {
$(field).closest('.cc-list-edit').find('.select2-search__field').val(' ').trigger($.Event('input', { which: 13 })).val('');
});
});
$('#email_footer').insertAfter($('#helpdesk_is_send_mail').parents().eq(1).find('.jstEditor'));
$('#email_header').insertBefore($('#helpdesk_is_send_mail').parents().eq(1).find('>legend'));
$('#email_canned_responses').insertBefore($('#helpdesk_is_send_mail').parents().eq(1).find('>legend'));
});
var issue_status = $('#issue_status_id').val();
function toggleSendMail(element) {
if (element.checked) {
$('.email-template').show();
<% unless HelpdeskSettings["helpdesk_answered_status", @project].blank? %>
issue_status = $('#issue_status_id').val();
$('#issue_status_id').val("<%= HelpdeskSettings["helpdesk_answered_status", @project] %>");
<% end %>
<% if @issue.assigned_to.blank? %>
$('#issue_assigned_to_id').val("<%= User.current.id %>");
<% end %>
} else {
$('.email-template').hide();
$('#cc_list_edit').hide();
$('#helpdesk_is_cc').val("");
<% unless HelpdeskSettings["helpdesk_answered_status", @project].blank? %>
$('#issue_status_id').val(issue_status);
<% end %>
}
}
function updateCannedResposeFrom(url, value) {
$.ajax({
url: url,
type: 'post',
data: {id: value}
});
}
</script>
<% canned_responses = CannedResponse.visible.in_project_or_public(@project) %>
<% if canned_responses.any? %>
<span id="email_canned_responses" style="display: none; float:right;" class="email-template">
<%= select_tag 'helpdesk[canned_response]', options_for_select([[ "--- #{l(:label_helpdesk_canned_response_plural)} ---", '' ]] + canned_responses.order("#{CannedResponse.table_name}.name").map{|cr| [cr.name, cr.id]}), :onchange => "updateCannedResposeFrom('#{escape_javascript add_canned_responses_path(:project_id => @project, :issue_id => @issue, :format => 'js')}', $(this).val())" %>
</span>
<% end %>
<% unless HelpdeskSettings["helpdesk_emails_header", @project].blank? %>
<div id="email_header" style="display: none;" class="email-template">
<%= textilizable(HelpdeskMailer.apply_macro(HelpdeskSettings["helpdesk_emails_header", @project], @issue.customer, @issue, User.current)).html_safe %>
</div>
<% end %>
<% unless HelpdeskSettings["helpdesk_emails_footer", @project].blank? %>
<div id="email_footer" style="display: none;" class="email-template">
<%= textilizable(HelpdeskMailer.apply_macro(HelpdeskSettings["helpdesk_emails_footer", @project], @issue.customer, @issue, User.current)).html_safe %>
</div>
<% end %>
<p id="helpdesk_send_response">
<%= check_box_tag 'helpdesk[is_send_mail]', 1, HelpdeskSettings["send_note_by_default", @project], :onclick => "toggleSendMail(this);" %>
<%= label_tag :helpdesk_is_send_mail, l(:label_is_send_mail), :class => "icon icon-email-to", :style => "" %>
<span id="journal_contacts" style="display: none;" class="email-template">
<span id="customer_to_email" class="email-template">
<%= render :partial => "issues/customer_to_email", :locals => {:contact => Contact.where(:email => @issue.helpdesk_ticket.default_to_address).first || @issue.customer, :contact_email => @issue.helpdesk_ticket.default_to_address } %>
</span>
<a href="#" class="inline-edit email-template" onclick="$('#customer_to_email').hide(); $(this).hide(); $('#cc_list_edit').show(); return false;"><img alt="Edit" src="/images/edit.png" style="vertical-align:middle;" ></a>
</span>
</p>
<div id="cc_list_edit" style="display:none;">
<% contact_emails = ((@issue.helpdesk_ticket.cc_address.to_s.split(',') + @issue.helpdesk_ticket.response_addresses).uniq.map { |email| [email, email] } + (@issue.contacts + [@issue.customer]).uniq.map{ |contact| [contact.email_name, contact.primary_email] }).uniq(&:last) %>
<%= hidden_field_tag 'helpdesk[is_cc]' %>
<p class="cc-list-edit">
<span class="is-cc">
<%= label_tag l(:label_helpdesk_to) %>
</span>
<%= select_tag 'journal_message[to_address]', options_for_select(contact_emails, @issue.helpdesk_ticket.default_to_address), :id => "helpdesk_to", :multiple => true %>
</p>
<p class="cc-list-edit">
<span class="is-cc">
<%= label_tag l(:label_helpdesk_cc) %>
</span>
<%= select_tag 'journal_message[cc_address]', options_for_select(contact_emails, @issue.customer.primary_email != @issue.helpdesk_ticket.default_to_address ? @issue.customer.primary_email : @issue.helpdesk_ticket.cc_addresses), :id => "helpdesk_cc", :multiple => true %>
</p>
<div style="clear: both;"></div>
<p class="cc-list-edit">
<span class="is-cc">
<%= label_tag l(:label_helpdesk_bcc) %>
</span>
<%= select_tag 'journal_message[bcc_address]', nil, :id => "helpdesk_bcc", :multiple => true %>
</p>
<div style="clear: both;"></div>
</div>
<% end %>
<% content_for :header_tags do %>
<%= javascript_include_tag :redmine_helpdesk, :plugin => 'redmine_contacts_helpdesk' %>
<%= stylesheet_link_tag :helpdesk, :plugin => 'redmine_contacts_helpdesk' %>
<% end %>
<% if authorize_for(:issues, :send_helpdesk_response) && @issue.customer && @issue.customer.primary_email %>
<%= javascript_tag do %>
$('#content .contextual:first a:first').before('<%= helpdesk_reply_link %>')
$('#content .contextual:last a:first').before('<%= helpdesk_reply_link %>')
<% end %>
<% end %>
@@ -0,0 +1,56 @@
<% if User.current.allowed_to?(:view_helpdesk_tickets, @project) && @issue.is_ticket? %>
<div id="ticket_data">
<div class="contextual">
<%= link_to l(:label_helpdesk_public_link), public_ticket_path(@issue.helpdesk_ticket, :hash => @issue.helpdesk_ticket.token), :class => "icon icon-public-link" if RedmineHelpdesk.public_tickets? && !@issue.is_private %>
<%# link_to l(:label_helpdesk_spam),
{:controller => 'helpdesk',
:action => 'delete_spam',
:project_id => @project,
:issue_id => @issue},
:method => :delete,
:data => {:confirm => l(:text_are_you_sure)},
:class => "icon icon-email-spam" if @issue.helpdesk_ticket.source == HelpdeskTicket::HELPDESK_EMAIL_SOURCE && @issue.customer.primary_email && User.current.allowed_to?(:send_response, @project) && User.current.allowed_to?(:delete_issues, @project) && User.current.allowed_to?(:delete_contacts, @project) %>
</div>
<span class="icon <%= helpdesk_ticket_source_icon(@issue.helpdesk_ticket) %>", title="<%= l(:label_helpdesk_to_address) %>: <%= @issue.helpdesk_ticket.to_address %>">
<%= @issue.helpdesk_ticket.is_incoming? ? l(:label_helpdesk_from) : l(:label_sent_to) %>
</span>
<span class="ticket_customer" style="white-space: nowrap;display: inline-block;">
<%= contact_tag(@issue.customer, :type => "plain") %>
(<%= @issue.helpdesk_ticket.from_address %>)
</span>
<% if attachment = @issue.helpdesk_ticket.message_file %>
<span class="attachment" style="white-space: nowrap;display: inline-block;">
<%= link_to_attachment attachment, :text => l(:label_helpdesk_original), :download => true, :class => 'icon icon-attachment' -%>
<%= h(" - #{attachment.description}") unless attachment.description.blank? %>
<span class="size">(<%= number_to_human_size attachment.filesize %>)</span>
<%= link_to_if_authorized image_tag('magnifier.png', :plugin => "redmine_contacts_helpdesk"),
:controller => 'helpdesk', :action => 'show_original',
:id => attachment, :project_id => @project %>
</span>
<% end %>
<span class="helpdesk-message-date"><%= format_time(@issue.helpdesk_ticket.ticket_date) %></span>
<br>
<% unless @issue.helpdesk_ticket.cc_address.blank? %>
<span class="helpdesk-message-date"><%= l(:label_helpdesk_cc) %>: <%= @issue.helpdesk_ticket.cc_address.split(',').join(', ') %></span>
<% end %>
</div>
<% if Redmine::VERSION.to_s > '3.2' %>
<script type="text/javascript">
$(document).ready(function() {
$(".attributes").first().prepend($("#ticket_data"));
$("#ticket_data").after("<hr>");
});
</script>
<% else %>
<hr />
<% end %>
<%= issue_fields_rows do |rows|
rows.left l(:label_helpdesk_ticket_reaction_time), distance_of_time_in_words(@issue.helpdesk_ticket.reaction_time, 0, :include_seconds => true) if @issue.helpdesk_ticket.reaction_time
rows.left l(:label_helpdesk_ticket_resolve_time), distance_of_time_in_words(@issue.helpdesk_ticket.resolve_time, 0, :include_seconds => true) if @issue.helpdesk_ticket.resolve_time
rows.right l(:label_helpdesk_contact_vote), show_customer_vote(@issue.helpdesk_ticket.vote, @issue.helpdesk_ticket.vote_comment) if @issue.helpdesk_ticket.vote && @issue.helpdesk_ticket.vote >= 0
rows.right l(:label_helpdesk_ticket_first_response_time), distance_of_time_in_words(@issue.helpdesk_ticket.first_response_time, 0, :include_seconds => true) if @issue.helpdesk_ticket.first_response_time
rows.right l(:label_helpdesk_ticket_last_response_time), distance_of_time_in_words(@issue.helpdesk_ticket.last_response_time, Time.now.utc) if @issue.helpdesk_ticket.last_response_time
end %>
<% end %>
@@ -0,0 +1,26 @@
<% if @issue.new_record? && !@copy_from && User.current.allowed_to?(:edit_helpdesk_tickets, @project) && (@issue.tracker_id.to_s == HelpdeskSettings["helpdesk_tracker", @project.id] || HelpdeskSettings["helpdesk_tracker", @project.id] == 'all') %>
<div class="email-template">
<% @issue.build_helpdesk_ticket if @issue.helpdesk_ticket.blank? %>
<%= form.fields_for :helpdesk_ticket do |f| %>
<div class="splitcontentleft">
<p><%= f.label_for_field("issue_helpdesk_ticket_attributes_contact_id_selected_contact", :label => l(:label_helpdesk_contact), :required => true) %>
<%= select_contact_tag('issue[helpdesk_ticket_attributes][contact_id]', @issue.helpdesk_ticket.try(:customer), :is_select => Contact.visible.by_project(ContactsSetting.cross_project_contacts? ? nil : @project).count < 50, :include_blank => true, :add_contact => true, :display_field => @issue.helpdesk_ticket.try(:customer).blank?) %>
</p>
<p class="required">
<%= f.text_field :ticket_date, :label => l(:label_helpdesk_ticket_date), :size => 12, :value => @issue.helpdesk_ticket.ticket_date.to_date %>
<%= f.text_field :ticket_time, :size => 5, :no_label => true, :value => @issue.helpdesk_ticket.ticket_date.to_s(:time) %><%= calendar_for('issue_helpdesk_ticket_attributes_ticket_date') %>
</p>
</div>
<div class="splitcontentright">
<p><%= f.select :source, helpdesk_tickets_source_for_select, :label => l(:label_helpdesk_ticket_source) %></p>
<p><%= label_tag :helpdesk_send_as, l(:label_helpdesk_send_as)%>
<%= select_tag :helpdesk_send_as, options_for_select(helpdesk_send_as_for_select, params[:helpdesk_send_as]) %> </p>
</div>
<div style="clear:both;"></div>
<% end %>
</div>
<% end %>