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,41 @@
<%= back_url_hidden_field_tag %>
<%= error_messages_for 'canned_response' %>
<div class="box tabular">
<p><%= f.text_field :name, :size => 80, :required => true %></p>
<% if User.current.admin? || User.current.allowed_to?(:manage_public_canned_responses, @project) %>
<% if @canned_response.user %>
<p>
<label><%= l(:field_author) %></label>
<%= @canned_response.user.name %>
</p>
<% end %>
<p>
<%= f.check_box :is_public,
:label => l(:field_is_public),
:onchange => (User.current.admin? ? nil : 'if (this.checked) {$("#canned_response_is_for_all").removeAttr("checked"); $("#canned_response_is_for_all").attr("disabled", true);} else {$("#canned_response_is_for_all").removeAttr("disabled");}') %>
</p>
<% end %>
<p><label for="canned_response_is_for_all"><%=l(:field_is_for_all)%></label>
<%= check_box_tag 'canned_response_is_for_all', 1, @canned_response.project.nil?,
:disabled => (!@canned_response.new_record? && (@canned_response.project.nil? || (@canned_response.is_public? && !User.current.admin?))) %></p>
<p><%= f.text_area :content, :required => true, :class => 'wiki-edit', :rows => 5 %>
<em class="info"><%= l(:text_helpdesk_answer_macros, :macro => HelpdeskSettings::MACRO_LIST.map{|m| link_to m, "#", :class => "mail-macro"}.join(', ')).html_safe %></em>
<%= wikitoolbar_for 'canned_response_content' %>
</p>
</div>
<script type="text/javascript" charset="utf-8">
$(".info a.mail-macro").bind("click", function() {
$('#canned_response_content').insertAtCaret($(this).html());
return false;
});
</script>
<% content_for :header_tags do %>
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
<% end %>
@@ -0,0 +1,37 @@
<div class="contextual">
<%= link_to l(:label_helpdesk_new_canned_response), {:controller => "canned_responses", :action => 'new'}, :class => 'icon icon-add' %>
</div>
<h3><%= l(:label_helpdesk_canned_response_plural) %></h3>
<% if @canned_responses.any? %>
<table class="list">
<thead><tr>
<th><%= l(:field_name) %></th>
<th><%= l(:field_content) %></th>
<th><%= l(:field_is_public) %></th>
<th><%= l(:field_author) %></th>
<th><%= l(:field_project) %></th>
<th></th>
</tr></thead>
<tbody>
<% @canned_responses.each do |canned_response| %>
<tr class="<%= cycle 'odd', 'even' %>">
<td class="name"><%= canned_response.name %></td>
<td class="name"><em class="info"><%= canned_response.content.gsub(/$/, ' ').truncate(250) %></em></td>
<td class="tick"><%= checked_image canned_response.is_public? %></td>
<td class="project"><%= canned_response.user.try(:name) %></td>
<td class="project"><%= canned_response.project ? canned_response.project.name : l(:field_is_for_all) %></td>
<td class="buttons">
<%= link_to l(:button_edit), edit_canned_response_path(canned_response), :class => 'icon icon-edit' %>
<%= delete_link canned_response_path(canned_response, :project_id => canned_response.project) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% if @canned_response_pages %>
<p class="pagination"><%= pagination_links_full @canned_response_pages %></p>
<% end %>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>
@@ -0,0 +1,43 @@
(function($) {
$.fn.insertAtCaret = function (myValue) {
return this.each(function() {
//IE support
if (document.selection) {
this.focus();
sel = document.selection.createRange();
sel.text = myValue;
this.focus();
} else if (this.selectionStart || this.selectionStart == '0') {
//MOZILLA / NETSCAPE support
var startPos = this.selectionStart;
var endPos = this.selectionEnd;
var scrollTop = this.scrollTop;
this.value = this.value.substring(0, startPos)+ myValue+ this.value.substring(endPos,this.value.length);
this.focus();
this.selectionStart = startPos + myValue.length;
this.selectionEnd = startPos + myValue.length;
this.scrollTop = scrollTop;
} else {
this.value += myValue;
this.focus();
}
});
};
})(jQuery);
$('#issue_notes').insertAtCaret("<%= raw escape_javascript(@content) %>")
$('#helpdesk_canned_response').val("");
if ($('#cke_issue_notes').length > 0) {
CKEDITOR.instances['issue_notes'].insertHtml("<%= raw escape_javascript(@content) %>");
}
@@ -0,0 +1,6 @@
<h2><%=l(:label_helpdesk_canned_response)%></h2>
<%= labelled_form_for :canned_response, @canned_response, :url => { :action => 'update', :project_id => @project } do |f| %>
<%= render :partial => 'canned_responses/form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
<% end %>
@@ -0,0 +1 @@
<%= render :partial => 'index' %>
@@ -0,0 +1,6 @@
<h2><%=l(:label_helpdesk_new_canned_response)%></h2>
<%= labelled_form_for :canned_response, @canned_response, :url => { :action => 'create', :project_id => @project } do |f| %>
<%= render :partial => 'canned_responses/form', :locals => { :f => f } %>
<%= submit_tag l(:button_create) %>
<% end %>
@@ -0,0 +1,54 @@
<% tickets_scope = @contact.all_tickets.visible.order_by_status %>
<% tickets = tickets_scope %>
<div id="helpdesk_tickets" class="contact-issues">
<div class="contextual">
<%= link_to l(:label_helpdesk_ticket_new), {:controller => 'issues',
:action => 'new',
:customer_id => @contact,
:tracker_id => HelpdeskSettings["helpdesk_tracker", @project.id],
:project_id => @project} if User.current.allowed_to?(:add_issues, @project) && User.current.allowed_to?(:send_response, @project) && HelpdeskSettings["helpdesk_tracker", @project.id] %>
</div>
<h3><%= link_to(l(:label_helpdesk_ticket_plural), {:controller => 'issues',
:action => 'index',
:set_filter => 1,
:customer => [@contact.id],
:status_id => "*",
:c => ["project", "tracker", "status", "subject", "customer", "customer_company", "last_message"],
:sort => 'priority:desc,updated_on:desc'}) %> </h3>
<% if tickets && tickets.any? %>
<%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do %>
<table class="list tickets">
<tbody>
<% for ticket in tickets %>
<tr id="ticket-<%= h(ticket.id) %>" class="issue hascontextmenu <%= cycle('odd', 'even') %> <%= ticket.css_classes %>">
<td>
<%= check_box_tag("ids[]", ticket.id, false, :style => 'display:none;', :id => nil) %>
<span class="icon <%= ticket.helpdesk_ticket.ticket_source_icon %>"></span>
</td>
<td class="subject">
<%= link_to "##{ticket.id} - #{truncate(ticket.subject, :length => 60)} (#{ticket.status})", issue_path(ticket), :class => ticket.css_classes %>
</td>
<% if @contact.is_company %>
<td class="customer"><%= contact_tag(ticket.customer, :type => 'plain') %></td>
<% end %>
<td class="last_message"><small>
<%= ticket.description.truncate(250) %>
</small></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% if Redmine::VERSION.to_s >= '3.4' || RedmineContacts.unstable_branch? %>
<%= context_menu %>
<% else %>
<%= context_menu issues_context_menu_path %>
<% end %>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>
</div>
@@ -0,0 +1,13 @@
<% if @contact && User.current.allowed_to?(:view_helpdesk_tickets, @project) && User.current.allowed_to?(:add_issues, @project) && HelpdeskSettings["helpdesk_tracker", @project.id] %>
<li><%= context_menu_link l(:label_helpdesk_ticket_new), {:controller => 'issues',
:action => 'new',
:customer_id => @contact,
:tracker_id => HelpdeskSettings["helpdesk_tracker", @project.id],
:project_id => @project,
:back_url => @back},
:method => :get,
:class => 'icon-support' %>
</li>
<% end %>
@@ -0,0 +1,81 @@
<div class="contextual">
<% if !@query.new_record? && @query.editable_by?(User.current) %>
<%= link_to l(:button_edit), edit_query_path(@query), :class => 'icon icon-edit' %>
<%= delete_link query_path(@query) %>
<% end %>
</div>
<h2><%= @query.new_record? ? l(:label_issue_plural) : h(@query.name) %></h2>
<% html_title(@query.new_record? ? l(:label_issue_plural) : @query.name) %>
<%= form_tag({ :controller => 'issues', :action => 'index', :project_id => @project },
:method => :get, :id => 'query_form') do %>
<%= hidden_field_tag 'set_filter', '1' %>
<div id="query_form_content" class="hide-when-print">
<fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
<div style="<%= @query.new_record? ? "" : "display: none;" %>">
<%= render :partial => 'queries/filters', :locals => {:query => @query} %>
</div>
</fieldset>
<fieldset class="collapsible collapsed">
<legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
<div style="display: none;">
<table>
<tr style="display: none;">
<td><%= l(:field_column_names) %></td>
<td><%= render :partial => 'queries/columns', :locals => {:query => @query} %></td>
</tr>
<tr>
<td><label for='group_by'><%= l(:field_group_by) %></label></td>
<td><%= select_tag('group_by',
options_for_select(
[[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]},
@query.group_by)
) %></td>
</tr>
<tr>
<td><label for='sort'><%= l(:label_sort) %></label></td>
<td><%= select_tag('sort',
options_for_select(
[[]] + @query.available_columns.select(&:sortable?).collect {|c| [c.caption, "#{c.name.to_s}:desc,id:desc"]},
params[:sort])
) %></td>
</tr>
</table>
</div>
</fieldset>
</div>
<p class="buttons hide-when-print">
<%= link_to_function l(:button_apply), 'submit_query_form("query_form")', :class => 'icon icon-checked' %>
<%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload' %>
<% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %>
<%= link_to_function l(:button_save),
"$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }'); submit_query_form('query_form')",
:class => 'icon icon-save' %>
<% end %>
</p>
<% end %>
<%= error_messages_for 'query' %>
<% if @query.valid? %>
<% if @issues.empty? %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% else %>
<%= render :partial => 'helpdesk/list', :locals => {:issues => @issues, :query => @query} %>
<span class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></span>
<% end %>
<% end %>
<% content_for :sidebar do %>
<%= render :partial => 'issues/sidebar' %>
<% end %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
<%= stylesheet_link_tag :helpdesk, :plugin => 'redmine_contacts_helpdesk' %>
<% end %>
<%= context_menu issues_context_menu_path %>
@@ -0,0 +1,66 @@
<%= form_tag({}) do -%>
<%= hidden_field_tag 'back_url', url_for(params) %>
<%= hidden_field_tag 'project_id', @project.id if @project %>
<table class="contacts tickets index">
<tbody>
<% previous_group = false %>
<% @issues.each do |issue| %>
<% if @query.grouped? && (group = @query.group_by_column.value(issue)) != previous_group %>
<% reset_cycle %>
<tr class="group open">
<td colspan="<%= @query.columns.size + 2 %>">
<span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
<%= group.blank? ? 'None' : column_content(@query.group_by_column, issue) %> <span class="count">(<%= @issue_count_by_group[group] %>)</span>
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", "toggleAllRowGroups(this)", :class => 'toggle-all') %>
</td>
</tr>
<% previous_group = group %>
<% end %>
<tr class="hascontextmenu">
<td class="checkbox">
<%= check_box_tag("ids[]", issue.id, false, :id => nil) %>
</td>
<% if Setting.gravatar_enabled? %>
<td class="avatar">
<% if issue.customer %>
<%= link_to avatar_to(issue.customer, :size => "32"), {:controller => 'contacts', :action => 'show', :project_id => @project, :id => issue.customer.id}, :id => "avatar" %>
<% else %>
<%= avatar(issue.author, :size => "32x32", :height => 32, :width => 32) %>
<% end %>
</td>
<% end %>
<td class="name ticket-name">
<h1 class="ticket_name"><%= link_to "#{issue.subject}", {:controller => :issues, :action => :show, :id => issue.id} %> <span id="ticket-id">#<%= issue.id %></span></h1>
<p class="ticket-description" >
<small><%= issue.description.gsub("(\n|\r)", "").strip.truncate(100) unless issue.description.blank? %></small>
</p>
<p class="contact-info">
<%= issue.customer ? "#{content_tag('span', '', :class => "icon icon-email", :title => l(:label_note_type_email))} #{l(:label_helpdesk_from)}: #{link_to_source(issue.customer)}, ".html_safe : "#{l(:label_helpdesk_from)}: #{link_to_user issue.author}, ".html_safe %>
<%= l(:label_updated_time, time_tag(issue.updated_on)).html_safe %>
</p>
</td>
<td class="status">
<%= content_tag(:span, issue.status.name, :class => "deal-status ticket-status tags status-#{issue.status.id}") %>
</td>
<td class="info ticket-info">
<% if issue.assigned_to %>
<div class="ticket-sum"><%= l(:field_assigned_to) %>: <strong><%= link_to_user issue.assigned_to %><%# "#{issue.currency} " if issue.currency %><%# issue_price(issue.amount) %></strong>
</div>
<% end %>
<div class="ticket-priority"><%= l(:field_priority) %>: <strong><%= issue.priority.name %><%# "#{issue.currency} " if issue.currency %><%# issue_price(issue.amount) %></strong>
</div>
<% if issue.due_date %>
<div class="ticket-due-date"><%= l(:field_due_date) %>: <strong><%= format_date issue.due_date %><%# "#{issue.currency} " if issue.currency %><%# issue_price(issue.amount) %></strong>
</div>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
@@ -0,0 +1 @@
$('#test_connection_messages').html('<%= escape_javascript @message.html_safe %>')
@@ -0,0 +1,7 @@
api.message do
api.journal_id @journal.id
api.content @journal.notes
api.to_address @journal_message.to_address
api.message_date format_date(@journal_message.message_date)
api.customer(:id => @issue.customer.id, :name => @issue.customer.name) unless @issue.customer.nil?
end
@@ -0,0 +1,332 @@
<%= render :partial => 'issues/action_menu' %>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
toggleSendMail($('#is_send_mail').get(0));
});
function toggleSendMail(element) {
if (element.checked) {
$('#journal_contacts').show();
$('#helpdesk_cc').show();
<% unless HelpdeskSettings["helpdesk_emails_footer", @project].blank? %>
$('#email_footer').insertAfter($('#is_send_mail').parents().eq(1).find('.jstEditor .wiki-edit'));
$('#email_footer').show();
<% end %>
<% unless HelpdeskSettings["helpdesk_emails_header", @project].blank? %>
$('#email_header').insertBefore($('#is_send_mail').parents().eq(1).find('.jstElements'));
$('#email_header').show();
<% end %>
$('#issue_status_id').val("<%= HelpdeskSettings["helpdesk_answered_status", @project] %>");
} else {
$('#journal_contacts').hide();
$('#helpdesk_cc').hide();
<% unless HelpdeskSettings["helpdesk_emails_footer", @project].blank? %>
$('#email_footer').hide();;
<% end %>
<% unless HelpdeskSettings["helpdesk_emails_header", @project].blank? %>
$('#email_header').hide();;
<% end %>
}
}
</script>
<% 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.contact, @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.contact, @issue, User.current)).html_safe %>
</div>
<% end %>
<h2><%= issue_heading(@issue) %></h2>
<div class="<%= @issue.css_classes %> details">
<% if @prev_issue_id || @next_issue_id %>
<div class="next-prev-links contextual">
<%= link_to_if @prev_issue_id,
"\xc2\xab #{l(:label_previous)}",
(@prev_issue_id ? issue_path(@prev_issue_id) : nil),
:title => "##{@prev_issue_id}" %> |
<% if @issue && @issue %>
<span class="position"><%= l(:label_item_position, :position => @issue, :count => @issue) %></span> |
<% end %>
<%= link_to_if @next_issue_id,
"#{l(:label_next)} \xc2\xbb",
(@next_issue_id ? issue_path(@next_issue_id) : nil),
:title => "##{@next_issue_id}" %>
</div>
<% end %>
<div class="subject">
<%= render_issue_subject_with_tree(@issue) %>
</div>
<p class="author icon icon-email">
<%= l(:label_added_time_by, :author => @issue.author.instance_of?(AnonymousUser) ? link_to_source(@issue.contacts.first) : link_to_user(@issue.author), :age => time_tag(@issue.created_on)).html_safe %>
<% if @issue.created_on != @issue.updated_on %>
<%= l(:label_updated_time, time_tag(@issue.updated_on)).html_safe %>.
<% end %>
</p>
<% if @issue.description? || @issue.attachments.any? -%>
<hr />
<% if @issue.description? %>
<div class="contextual">
<%= link_to l(:button_quote),
{:controller => 'journals', :action => 'new', :id => @issue},
:remote => true,
:method => 'post',
:class => 'icon icon-comment' if authorize_for('issues', 'edit') %>
</div>
<div class="wiki">
<%= textilizable @issue, :description, :attachments => @issue.attachments %>
</div>
<% end %>
<%= link_to_attachments @issue, :thumbnails => true %>
<% end -%>
</div>
<% if @journals.present? %>
<div id="ticket-history">
<h3><%=l(:label_history)%></h3>
<% reply_links = authorize_for('issues', 'edit') -%>
<% for journal in @journals.select{|j| !j.notes.blank? } %>
<div id="change-<%= journal.id %>" class="<%= journal.css_classes %> ticket-note">
<% if journal.is_incoming? %>
<%= link_to avatar_to(journal.contacts.first, :size => "32"), {:controller => 'contacts', :action => 'show', :project_id => @project, :id => journal.contacts.first.id}, :id => "avatar", :class => "ticket-avatar gravatar" unless journal.contacts.blank? %>
<% else %>
<%= avatar(journal.user, :size => "32x32", :height => 32, :width => 32, :class => "ticket-avatar gravatar") %>
<% end %>
<div id="note-<%= journal.indice %>" class="ticket-note-content">
<h4>
<%= link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes",
{ :controller => 'journals', :action => 'edit', :id => journal, :format => 'js' },
:title => l(:button_edit),
:class => "journal-link") if reply_links %>
<%= link_to(image_tag('comment.png'),
{:controller => 'journals', :action => 'new', :id => @issue, :journal_id => journal},
:remote => true,
:method => 'post',
:title => l(:button_quote),
:class => "journal-link") %>
<% if journal.contacts && journal.contacts.any? && User.current.allowed_to?(:view_helpdesk_tickets, @project) %>
<span class="icon <%= journal.is_incoming? ? 'icon-email' : 'icon-email-to' %>">
<% if journal.is_incoming? %>
<%= "#{link_to_source journal.contacts.first} (#{journal.journal_messages.first.email})".html_safe unless journal.contacts.blank? %>
<% if journal.journal_messages.first.attachments.any? %>
<% attachment = journal.journal_messages.first.attachments.first %>
<span class="attachment" style="white-space: nowrap;display: inline-block;">
<%= link_to_attachment attachment, :text => l(:label_helpdesk_original), :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 %>
<% else %>
<%= link_to_user journal.user %>
<span class="sent-to">
<%= l(:label_sent_to) %>
<% journal.journal_messages.each do |journal_message| %>
<span class="contact" style="white-space: nowrap;display: inline-block;">
<%= link_to_source(journal_message.contact) %>
(<%= journal_message.email %>)
</span>
<% end %>
</span>
<% end %>
- <%= format_time(@issue.updated_on).html_safe %>.
</span>
<%# authoring journal.created_on, journal.user, :label => :label_updated_time_by %>
<% end %>
</h4>
<div class="wiki editable" id="journal-<%= journal.id %>-notes">
<%= textilizable(journal, :notes) %>
</div>
</div>
</div>
<% end %>
<% heads_for_wiki_formatter if User.current.allowed_to?(:edit_issue_notes, @issue.project) || User.current.allowed_to?(:edit_own_issue_notes, @issue.project) %>
</div>
<% end %>
<div style="clear: both;"></div>
<%= render :partial => 'issues/action_menu' %>
<div style="clear: both;"></div>
<% if authorize_for('issues', 'edit') %>
<div id="update" style="display:none;">
<h3><%= l(:button_update) %></h3>
<%# labelled_form_for @issue, :html => {:id => 'issue-form', :multipart => true} do |f| %>
<%= form_tag({:controller => "helpdesk_tickets", :action => "update"}, :id => 'issue-form', :multipart => true, :method => :put) do |f| %>
<%= error_messages_for 'issue' %>
<%= render :partial => 'issues/conflict' if @conflict %>
<div class="box">
<p>
<%= label_tag :is_send_mail, l(:label_is_send_mail), :class => "icon icon-email-to", :style => "" %>
<%= check_box_tag 'is_send_mail', 1, HelpdeskSettings["send_note_by_default", @project], :onclick => "toggleSendMail(this);" %>
<span id="journal_contacts" style="display: none;">
<% @issue.contacts.each do |contact| %>
<%= contact_tag(contact) %>
(<%= contact.emails.first %>)
<% end %>
<div id="helpdesk_cc" style="display: none;">
<p>
<%= label_tag :email_cc, l(:label_email_cc) %>
<%= text_field_tag :email_cc, '', :size => "80%" %>
</p>
<p>
<%= label_tag :email_bcc, l(:label_email_bcc) %>
<%= text_field_tag :email_bcc, '', :size => "80%" %>
</p>
</div>
</span>
</p>
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
<%= wikitoolbar_for 'notes' %>
<p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
</div>
<%# f.hidden_field :lock_version %>
<%# hidden_field_tag 'last_journal_id', params[:last_journal_id] || @issue.last_journal_id %>
<%= submit_tag l(:button_submit) %>
<%= preview_link preview_edit_issue_path(:project_id => @project, :id => @issue), 'issue-form' %>
<% end %>
<div id="preview" class="wiki"></div>
</div>
<% end %>
<% other_formats_links do |f| %>
<%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
<%= f.link_to 'PDF' %>
<% end %>
<% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
<% content_for :sidebar do %>
<div id="ticket_attributes">
<% if authorize_for('issues', 'edit') %>
<div class="contextual">
<%= link_to l(:button_update), :onclick => '#' %>
</div>
<% end %>
<h3><%= l(:label_helpdesk_ticket_attributes) %></h3>
<table class="attributes">
<%= issue_fields_rows do |rows|
rows.left l(:field_status), h(@issue.status.name), :class => 'status'
rows.left l(:field_priority), h(@issue.priority.name), :class => 'priority'
unless @issue.disabled_core_fields.include?('assigned_to_id')
rows.left l(:field_assigned_to), avatar(@issue.assigned_to, :size => "14").to_s.html_safe + (@issue.assigned_to ? link_to_user(@issue.assigned_to) : "-"), :class => 'assigned-to'
end
unless @issue.disabled_core_fields.include?('category_id') || @issue.category.blank?
rows.left l(:field_category), h(@issue.category ? @issue.category.name : "-"), :class => 'category'
end
unless @issue.disabled_core_fields.include?('fixed_version_id') || @issue.fixed_version.blank?
rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version'
end
unless @issue.disabled_core_fields.include?('start_date') || @issue.start_date.blank?
rows.left l(:field_start_date), format_date(@issue.start_date), :class => 'start-date'
end
unless @issue.disabled_core_fields.include?('due_date') || @issue.due_date.blank?
rows.left l(:field_due_date), format_date(@issue.due_date), :class => 'due-date'
end
unless @issue.disabled_core_fields.include?('done_ratio')
rows.left l(:field_done_ratio), progress_bar(@issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%"), :class => 'progress'
end
unless @issue.disabled_core_fields.include?('estimated_hours')
unless @issue.estimated_hours.nil?
rows.left l(:field_estimated_hours), l_hours(@issue.estimated_hours), :class => 'estimated-hours'
end
end
if User.current.allowed_to?(:view_time_entries, @project) && @issue.total_spent_hours > 0
rows.left l(:label_spent_time), (@issue.total_spent_hours > 0 ? (link_to l_hours(@issue.total_spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-"), :class => 'spent-time'
end
end %>
<%= render_custom_fields_rows(@issue) %>
</table>
</div>
<div id="contacts_previous_issues">
<style type="text/css">
#contacts_previous_issues ul {margin: 0; padding: 0;}
#contacts_previous_issues li {list-style-type:none; margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
</style>
<% if RedmineHelpdesk.settings[:show_contact_card] %>
<h3><%= l(:label_helpdesk_contact) %></h3>
<% @issue.contacts.each do |contact| %>
<span class="small-card">
<%= render :partial => 'contacts/contact_card', :object => contact %>
</span>
<% end %>
<% end %>
<% if (issues_count = Issue.count(:include => :contacts, :conditions => ["#{Contact.table_name}.id IN (#{@issue.contact_ids.join(', ')})"]) - 1) > 0 %>
<h3><%= "#{l(:label_helpdesk_contact_activity)} (#{issues_count})" %> </h3>
<ul>
<% (Issue.visible.find(:all, :include => :contacts, :conditions => ["#{Contact.table_name}.id IN (#{@issue.contact_ids.join(', ')})"], :order => "#{Issue.table_name}.status_id, #{Issue.table_name}.due_date DESC, #{Issue.table_name}.updated_on DESC", :limit => RedmineHelpdesk.settings[:last_message_count].to_i > 0 ? RedmineHelpdesk.settings[:last_message_count].to_i : 11) - [@issue]).each do |issue| %>
<li>
<%= link_to_issue(issue, :truncate => 60, :project => (@project != issue.project)) %>
</li>
<% end %>
</ul>
<div class="contextual">
<%= link_to l(:label_issue_view_all), {:controller => 'issues',
:action => 'index',
:set_filter => 1,
:f => [:contacts, :status_id],
:v => {:contacts => @issue.contact_ids},
:op => {:contacts => '=', :status_id => '*'}} %>
</div>
<% end %>
</div>
<% if User.current.allowed_to?(:add_issue_watchers, @project) ||
(@issue.watchers.present? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
<div id="watchers">
<%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
</div>
<% end %>
<% end %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
<%= stylesheet_link_tag :helpdesk, :plugin => 'redmine_contacts_helpdesk' %>
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
<% end %>
<%= context_menu issues_context_menu_path %>
@@ -0,0 +1,9 @@
$('#customer_to_email').html('<%= escape_javascript(render :partial => "issues/customer_to_email", :locals => {:contact => @contact, :contact_email => @email}) %>')
$("#helpdesk_to").val("<%= @email %>");
$("#helpdesk_to").attr("value", "<%= @email %>")
$("#helpdesk_to").trigger('change');
$("#helpdesk_cc").val("<%= @cc_emails.join(',') %>")
$("#helpdesk_cc").trigger('change');
@@ -0,0 +1 @@
$('#cusomer_profile_and_issues').html('<%= escape_javascript(render :partial => "issues/helpdesk_customer_profile") %>')
@@ -0,0 +1,21 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css" media="screen">
<%= @email_stylesheet %>
</style>
</head>
<body>
<div id="email_header">
<%= textile(@email_header.to_s).html_safe unless @email_header.blank? %>
</div>
<div class="wrapper" id="email_body">
<%= textile(@email_body.to_s).html_safe %>
</div>
<div id="email_footer">
<%= textile(@email_footer.to_s).html_safe unless @email_footer.blank? %>
</div>
</body>
</html>
@@ -0,0 +1,3 @@
<%= @email_header %>
<%= @email_body %>
<%= @email_footer %>
@@ -0,0 +1,25 @@
<tr class="metrics">
<td colspan="<%= @collector.columns.count / 2 %>">
<p><%= l(:label_helpdesk_busiest_time_of_day_new_tickets) %></p>
<div class="num"><%= @collector.new_issues_count %></div>
<div class="change" title="<%= process_deviation(@collector.previous_new_issues_count, @collector.new_issues_count, false) %>">
<%= progress_in_percents(-@collector.new_issue_count_progress) %>
</div>
</td>
<td colspan="<%= @collector.columns.count / 2 %>">
<p><%= l(:label_helpdesk_busiest_time_of_day_new_contacts) %></p>
<div class="num"><%= @collector.contacts_count %></div>
<div class="change" title="<%= process_deviation(@collector.previous_contacts_count, @collector.contacts_count, false) %>">
<%= progress_in_percents(-@collector.total_contacts_count_progress) %>
</div>
</td>
</tr>
<tr class="metrics">
<td colspan="<%= @collector.columns.count / 2 %>">
<p><%= l(:label_helpdesk_busiest_time_of_day_total_incoming) %></p>
<div class="num"><%= @collector.issues_count %></div>
<div class="change" title="<%= process_deviation(@collector.previous_issues_count, @collector.issues_count, false) %>">
<%= progress_in_percents(-@collector.issue_count_progress) %>
</div>
</td>
</tr>
@@ -0,0 +1,33 @@
<% if @collector.issues_count.zero? %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% else %>
<div class="helpdesk_chart">
<table class="chart_table">
<tr class="header">
<% @collector.columns.each do |column| %>
<td class="column_data">
<p class="issues_count"><%= column[:issues_count] %></p>
<p><%= [column[:issues_percent], '%'].join %></p>
</td>
<% end %>
</tr>
<tr class="main_block">
<% @collector.columns.each do |column| %>
<td class="column_data">
<% if column[:issues_count] > 0 %>
<div class="percents" style='height: <%= (column[:issues_count] * @collector.issue_weight).ceil %>px'></div>
<% end %>
</td>
<% end %>
</tr>
<tr class="footer">
<% @collector.columns.each do |column| %>
<td class="column_data">
<%= l("label_helpdesk_#{@report}_interval_#{column[:name]}") %>
</td>
<% end %>
</tr>
<%= render :partial => "#{@report}_metrics" %>
</table>
</div>
<% end %>
@@ -0,0 +1,33 @@
<tr class="metrics">
<td colspan="<%= @collector.columns.count / 2 %>">
<p><%= l(:label_helpdesk_average_first_response_time) %></p>
<div class="num"><%= helpdesk_time_label(@collector.average_response_time) %></div>
<div class="change" title="<%= process_deviation(@collector.previous_average_response_time, @collector.average_response_time) %>">
<%= mirror_progress_in_percents(@collector.average_response_time_progress) %>
</div>
</td>
<td colspan="<%= @collector.columns.count / 2 %>">
<p><%= l(:label_helpdesk_average_time_to_close) %></p>
<div class="num"><%= helpdesk_time_label(@collector.average_close_time) %></div>
<div class="change" title="<%= process_deviation(@collector.previous_average_close_time, @collector.average_close_time) %>">
<%= mirror_progress_in_percents(@collector.average_close_time_progress) %>
</div>
</td>
</tr>
<tr class="metrics">
<td colspan="<%= @collector.columns.count / 2 %>">
<p><%= l(:label_helpdesk_average_responses_count) %></p>
<div class="num"><%= @collector.average_response_count %></div>
<div class="change" title="<%= process_deviation(@collector.previous_average_response_count, @collector.average_response_count, false) %>">
<%= progress_in_percents(@collector.average_response_count_progress) %>
</div>
</td>
<td colspan="<%= @collector.columns.count / 2 %>">
<p><%= l(:label_helpdesk_total_replies) %></p>
<div class="num"><%= @collector.total_response_count %></div>
<div class="change" title="<%= process_deviation(@collector.previous_total_response_count, @collector.total_response_count, false) %>">
<%= progress_in_percents(-@collector.total_response_count_progress) %>
</div>
</td>
</td>
</tr>
@@ -0,0 +1,29 @@
<h2><%= l("label_helpdesk_report_names_#{@report}") %></h2>
<% html_title(l("label_helpdesk_report_names_#{@report}")) %>
<%= form_tag({ :controller => 'helpdesk_reports', :action => 'show', :project_id => @project },
:method => :get, :id => 'query_form') do %>
<div id="query_form_with_buttons" class="hide-when-print">
<%= hidden_field_tag 'set_filter', '1' %>
<div id="query_form_content">
<fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
<div style="<%= @query.new_record? ? "" : "display: none;" %>">
<%= render :partial => 'queries/filters', :locals => {:query => @query} %>
</div>
</fieldset>
</div>
<p class="buttons">
<%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %>
<%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload' %>
</p>
</div>
<% end %>
<%= error_messages_for 'query' %>
<%= render :partial => 'chart' %>
<% content_for :sidebar do %>
<%= render :partial => 'issues/helpdesk_reports' %>
<% end %>
@@ -0,0 +1 @@
<h2>HelpdeskTicketsController#destroy</h2>
@@ -0,0 +1,23 @@
$('#cusomer_profile_and_issues').html('<%= escape_javascript(render :partial => "issues/helpdesk_customer_profile") %>')
$('#helpdesk_ticket_cc_address').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: '100%',
templateResult: ccEmailTagResult,
templateSelection: ccEmailTagSelection,
}).on('select2:open', function (e) {
$('#helpdesk_ticket_cc_address').closest('p').find('.select2-search__field').val(' ').trigger($.Event('input', { which: 13 })).val('');
});
@@ -0,0 +1 @@
<h2>HelpdeskTicketsController#update</h2>
@@ -0,0 +1,28 @@
<div class="vote_form">
<h2><%= l(:label_helpdesk_mark) %></h2>
<%= form_tag helpdesk_votes_vote_path(:id => @ticket.id, :hash => @ticket.token) do %>
<p>
<%= label_tag :vote_2, nil, :class => 'vote-value' do %>
<%= radio_button_tag('vote', 2, @ticket.vote == 2 || @ticket.vote == nil ? true : false) %>
<span class="icon icon-awesome"><%= t(:label_helpdesk_mark_awesome) %></span>
<% end %>
<%= label_tag :vote_1, nil, :class => 'vote-value' do %>
<%= radio_button_tag('vote', 1, @ticket.vote == 1 ? true : false) %>
<span class="icon icon-justok"><%= t(:label_helpdesk_mark_justok) %></span>
<% end %>
<%= label_tag :vote_0, nil, :class => 'vote-value' do %>
<%= radio_button_tag('vote', 0, @ticket.vote == 0 ? true : false) %>
<span class="icon icon-notgood"><%= t(:label_helpdesk_mark_notgood) %></span>
<% end %>
</p>
<%- if RedmineHelpdesk.vote_comment_allow? %>
<%= text_area_tag('vote_comment', nil, { :size => '60x12', :placeholder => t(:label_helpdesk_vote_comment_placeholder) }) %>
<% end %>
<div class='submit'>
<%= submit_tag(t(:label_helpdesk_submit)) %>
</div>
<% end %>
</div>
@@ -0,0 +1,3 @@
<div class="vote_form">
<h2><%= t(:label_helpdesk_vote_thank) %></h2>
</div>
@@ -0,0 +1 @@
<%= avatar(@user, :size => 54, :id => 'avatar') %>
@@ -0,0 +1,226 @@
function getXmlHttp(){
var xmlhttp;
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
xmlhttp = false;
}
}
if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
xmlhttp = new XMLHttpRequest();
}
return xmlhttp;
}
function serialize(form){
var boundary = String(Math.random()).slice(2);
var boundaryMiddle = '--' + boundary + '\r\n';
var boundaryLast = '--' + boundary + '--\r\n'
var cont_start = 'Content-Disposition: form-data; name="';
var cont_middle = '"\r\n\r\n';
var cont_end = '\r\n';
var field = '';
var body = ['\r\n'];
if (typeof form == 'object' && form.nodeName == "FORM") {
for (index = form.elements.length - 1; index >= 0; index--) {
field = form.elements[index];
if (field.type == 'select-multiple') {
for (option = form.elements[index].options.length - 1; option >= 0; option--) {
if (field.options[option].selected) { body.push(cont_start + field.name + cont_middle + field.options[option].value + cont_end); }
}
} else {
if (field.type != 'submit' && field.type != 'file' && field.type != 'button') {
if ((field.type != 'checkbox' && field.type != 'radio') || field.checked) {
body.push(cont_start + field.name + cont_middle + field.value + cont_end);
}
} else {
if (field.type == 'file'){
if (field.files.length > 0) {
body.push(cont_start + field.name + cont_middle + field.attributes['data-value'] + cont_end);
body.push(cont_start + field.name + '_name' + cont_middle + field.files[0].name + cont_end);
}
}
}
}
}
}
return [boundary, body.join(boundaryMiddle) + boundaryLast];
}
function translation(field){
return RedmineHelpdeskIframe.configuration['translation'] ? RedmineHelpdeskIframe.configuration['translation'][field] : null;
}
function ticketCreated(){
success_div = document.createElement('div');
success_div.id = 'submit_button';
success_div.className = 'success-message';
success_div.style.textAlign = 'center';
success_div.style.margin = '15%';
success_div.style.font = '20px Arial';
success_div.innerHTML = translation('createSuccessLabel') || '<%= t(:label_helpdesk_widget_ticket_created) %>';
success_desc_div = document.createElement('div');
success_desc_div.style.textAlign = 'center';
success_desc_div.style.margin = '5%';
success_desc_div.style.font = '14px Arial';
success_desc_div.innerHTML = translation('createSuccessDescription');
document.getElementById('widget_form').innerHTML = '';
document.getElementById('widget_form').appendChild(success_div);
document.getElementById('widget_form').appendChild(success_desc_div);
}
function ticketErrors(errors){
errors_div = document.createElement('div');
errors_div.id = 'ticket-error-details';
errors_div.className = 'ticket-error-details';
error_p = document.createElement('div');
error_p.innerHTML = translation('createErrorLabel') || '<%= t(:label_helpdesk_widget_ticket_errors) %>';
errors_div.appendChild(error_p);
errors_link = document.createElement('a');
errors_link.id = 'ticket-errors-link';
errors_link.href = 'javascript:void(0)';
errors_link.style.paddingLeft = '10px';
errors_link.addEventListener('click', function(){ toggleErrorsList() });
errors_link.innerHTML = '<%= t(:label_helpdesk_widget_ticket_error_details) %>';
error_p.appendChild(errors_link);
ul = document.createElement('ul');
ul.id = 'ticket-errors';
ul.className = 'ticket-errors';
ul.style.display = 'none';
errors_div.appendChild(ul);
for (var key in errors) {
if (key != 'base') {
processErrorForField(ul, key, errors[key])
} else {
errors[key].forEach(function(error_text) {
processErrorForCustomField(ul, key, error_text);
});
}
}
document.getElementById('flash').appendChild(errors_div);
}
function createErrorLi(target, text){
li = document.createElement('li');
li.id = 'ticket-error';
li.className = 'ticket-error';
li.innerHTML = text;
target.appendChild(li);
}
function markFieldAsError(element){
element.style.border = '';
element.classList.add('error_field');
element.addEventListener('keyup', checkFieldContent);
}
function markRequireFieldsAsError(){
fields = document.querySelectorAll("[data-require='true'] > input, [data-require='true'] > select, [data-require='true'] > textarea, .required-field");
required_fields = Array.from(fields);
var respose = false;
required_fields.forEach(function(field) {
if (field.value.length == 0) {
markFieldAsError(field);
respose = true;
}
});
return respose;
}
function unmarkFieldsAsError(){
error_fields = Array.from(document.getElementsByClassName('error_field'));
error_fields.forEach(function(field) {
field.classList.remove('error_field');
field.removeEventListener('keyup', checkFieldContent);
});
}
function checkFieldContent(){
if (this.value.length > 0) {
this.style.border = '1px solid #d9d9d9';
} else {
this.style.border = '1px solid red';
}
}
function processErrorForField(ul, key, error_text) {
createErrorLi(ul, error_text);
field = document.getElementById(key);
if (field != null) { markFieldAsError(field); }
}
function processErrorForCustomField(ul, key, error_text) {
checkCustomFieldsOnError(error_text);
createErrorLi(ul, error_text);
}
function checkCustomFieldsOnError(error_text){
custom_fields = Array.from(document.getElementsByClassName('custom_field'));
custom_fields.forEach(function(cfield) {
cfield_regex = new RegExp(cfield.attributes['data-error-key'].value);
if (cfield_regex.test(error_text)){
markFieldAsError(cfield.getElementsByTagName('input')[0]);
}
});
}
function toggleErrorsList(){
errors_list = document.getElementById('ticket-errors');
if (errors_list == null) { return true }
if (errors_list && errors_list.style.display == 'block') {
errors_list.style.display = 'none';
} else {
errors_list.style.display = 'block';
}
}
function processResponse(response){
if (response['result']) {
ticketCreated();
parent.postMessage(JSON.stringify({ reload: true }), "*");
} else {
ticketErrors(response['errors']);
}
var formSubmitBtn = document.getElementById('form-submit-btn');
if (formSubmitBtn){
formSubmitBtn.disabled = false;
}
}
function needReloadProjectData(){
parent.postMessage(JSON.stringify({ project_reload: true }), "*");
}
function submitTicketForm(){
document.getElementById('flash').innerHTML = '';
unmarkFieldsAsError();
if (markRequireFieldsAsError()){ return false; }
var base_url = '<%= Setting.protocol %>://<%= Setting.host_name %>'
var xmlhttp = getXmlHttp();
var serialize_result = serialize(document.getElementById('widget_form'));
var boundary = serialize_result[0];
var form_params = serialize_result[1];
document.getElementById('form-submit-btn').disabled = true;
xmlhttp.open('POST', base_url + '/helpdesk_widget/create_ticket.js', true);
xmlhttp.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200 || xmlhttp.status == 304) {
processResponse(JSON.parse(xmlhttp.responseText));
} else {
processResponse({result: false, errors: []});
}
}
};
xmlhttp.send(form_params);
}
@@ -0,0 +1,6 @@
<% @issue.editable_custom_field_values.each do |value| %>
<% if @enabled_cf && @enabled_cf.include?(value.custom_field_id.to_s) %>
<% required = value.custom_field.is_required? %>
<p class="custom_field" data-error-key="<%= value.custom_field.name %>" data-require="<%= required %>"><%= custom_field_tag_with_label :issue, value, :required => required %></p>
<% end %>
<% end %>
@@ -0,0 +1,135 @@
#widget_form {
padding: 10px;
font-family: helvetica, arial, sans-serif;
}
#widget_form .form-control,
#widget_form .custom_fields input,
#widget_form .custom_fields select,
#widget_form .custom_fields textarea {
background: 0 0;
border: 1px solid #d9d9d9;
border-radius: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
color: #333;
display: block;
font-size: 12px;
font-weight: 400;
height: 36px;
line-height: 16px;
width: 100%;
padding: 8px 12px;
margin-bottom: 12px;
-webkit-appearance: none;
-moz-appearance: none;
}
#widget_form select.form-control,
#widget_form .custom_fields select {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAAAXNSR0IArs4c6QAAATNJREFUKBW1kc1KxDAUhW/CxM2M4EO4m7UwrzDDDBUXRRDxPbpw4cL3EHFhYcTSEh/BhevufAhhOptJabwnmhIjI+JPIDS5P1/OPSX64yW01o/GmNMkSZ5/wy6KYl8pdS0F0UQNBk8Mnv4UiF4wwJLW2qUQYo+srTiRMZTj316YMEMvGI4FgK6qjIS44KDk5N1qvT5L07T5Cpvn+Wh3OLziviMGddx3PpvPL3s1D2U5s1LeOLVE9caYw22+wq8dpe75wTHDXkTXnUwXCw0B0qtAwLTtAd9r3mN4gkd83n8RQw41vGv0eBhqeiAuULRqmgnGdp5IWQa+Or94itJNAWu4Np6iHxnAYH3y1eUivzhmg563kjgQ3iNfWfhHv8Jaf96m0Ofp/QfcgrZp2+N4xL7wvw6vkGme5fEw/bwAAAAASUVORK5CYII=) 97% 14px no-repeat;
background-size: 12px 9px;
}
#widget_form .custom_fields textarea,
#widget_form textarea.form-control {
height: 120px;
}
#widget_form .custom_fields select[multiple] {
height: 120px;
}
#widget_form .title {
color: #333;
padding-bottom: 3px;
font-size: 12px;
}
#widget_form .custom_fields label > span {
color: #333;
padding-bottom: 3px;
font-size: 12px;
}
#widget_form #flash {
color: red;
padding-bottom: 3px;
font-size: 12px;
}
#widget_form input.error_field,
#widget_form select.error_field,
#widget_form textarea.error_field,
#widget_form .custom_fields input.error_field,
#widget_form .custom_fields select.error_field,
#widget_form .custom_fields textarea.error_field {
border: 1px solid red;
}
#widget_form .submit_button{
height: 40px;
}
#widget_form .attach_div{
position: relative;
float: right;
margin: 10px;
min-width: 80px;
text-align: right;
}
#widget_form .attach_link{
position: relative;
font: 14px Arial;
color: #3699ca;
}
#widget_form .attach_field{
position: absolute;
top: 0;
left: -80px;
width: 160px;
opacity: 0;
cursor: pointer;
}
#widget_form .btn {
float: right;
background: #3699ca;
color: #fff;
height: 36px;
padding: 0 14px;
line-height: 30px;
font-weight: 500;
text-align: center;
border: 0px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
#widget_from .attach {
display: inline-block;
float: right;
font-size: 12px;
margin: 10px 16px 0 0;
max-width: 210px;
text-decoration: underline;
color: #2996cc;
}
#helpdesk_widget.open {
border-radius: 0 100% 100% !important;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
}
@@ -0,0 +1,556 @@
function getXmlHttp(){
var xmlhttp;
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
xmlhttp = false;
}
}
if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
xmlhttp = new XMLHttpRequest();
}
return xmlhttp;
}
var RedmineHelpdeskWidget = {
widget: document.getElementById('helpdesk_widget'),
widget_button: null,
width: 400,
height: 500,
margin: 20,
iframe: null,
form: null,
schema: null,
reload: false,
configuration: {},
attachment: null,
base_url: '<%= Setting.protocol %>://<%= Setting.host_name %>',
config: function(configuration){
this.configuration = configuration;
this.apply_config();
},
apply_config: function(){
if (this.configuration['color']) {
this.widget_button.style.backgroundColor = this.configuration['color'];
}
switch (this.configuration['position']) {
case 'topLeft':
this.widget.style.top = '20px';
this.widget.style.left = '20px';
break;
case 'topRight':
this.widget.style.top = '20px';
this.widget.style.right = '20px';
break;
case 'bottomLeft':
this.widget.style.bottom = '20px';
this.widget.style.left = '20px';
break;
case 'bottomRight':
this.widget.style.bottom = '20px';
this.widget.style.right = '20px';
break;
default:
widget.style.bottom = '20px';
widget.style.right = '20px';
}
},
translation: function(field){
return this.configuration['translation'] && this.configuration['translation'][field] ? this.configuration['translation'][field] : null;
},
identify: function(field){
return this.configuration['identify'] && this.configuration['identify'][field] ? this.configuration['identify'][field] : null
},
load: function() {
this.widget.addEventListener('click', function(){ RedmineHelpdeskWidget.toggle() });
this.create_widget_button();
this.decorate_widget_button();
this.create_iframe();
this.decorate_iframe();
this.load_schema();
this.created = true;
},
load_schema: function() {
var xmlhttp = getXmlHttp();
xmlhttp.open('GET', this.base_url + '/helpdesk_widget/load_form.json', true);
xmlhttp.responseType = 'json';
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200 || xmlhttp.status == 304) {
RedmineHelpdeskWidget.schema = xmlhttp.response;
RedmineHelpdeskWidget.fill_form();
} else {
RedmineHelpdeskWidget.schema = {};
}
}
};
xmlhttp.send(null);
},
create_widget_button: function(){
button = document.createElement('div');
button.id = 'widget_button';
button.className = 'widget_button';
button.innerHTML = this.configuration['icon'] || '?';
button.setAttribute('name', 'helpdesk_widget_button');
button.style.backgroundColor = '#7E8387';
button.style.backgroundSize = '15px 15px';
button.style.cursor = 'pointer';
button.style.color = 'white';
button.style.textAlign = 'center';
button.style.fontSize = '32px';
button.style.verticalAlign = 'middle';
button.style.lineHeight = '54px';
button.style.borderRadius = '30px';
button.style.boxShadow = 'rgba(0, 0, 0, 0.258824) 0px 2px 5px 0px';
button.style.display = 'none';
button.style.webkitTransition = "transform 0.2s ease";
this.widget_button = button;
this.widget.appendChild(button);
},
decorate_widget_button: function(){
widget = this.widget;
widget.style.position = 'fixed';
widget.style.bottom = '20px';
widget.style.right = '20px';
widget.style.width = '54px';
widget.style.height = '54px';
widget.style.zIndex = 9999;
},
create_iframe: function(){
this.iframe = document.createElement('iframe');
this.widget.appendChild(this.iframe);
},
decorate_iframe: function(){
iframe = this.iframe;
iframe.setAttribute('id', 'helpdesk_ticket_container');
iframe.setAttribute('width', this.width);
iframe.setAttribute('height', 0);
iframe.setAttribute('frameborder', 0);
iframe.style.visibility = 'hidden';
iframe.style.position = 'absolute';
iframe.style.opacity = '0';
iframe.style.width = this.width;
iframe.style.backgroundColor = 'white';
iframe.style.webkitTransition = "opacity 0.2s ease";
iframe.style.boxShadow = 'rgba(0, 0, 0, 0.258824) 0px 1px 4px 0px';
iframe.setAttribute('name', 'helpdesk_widget_iframe');
},
fill_form: function(){
if (Object.keys(this.schema.projects).length > 0) {
this.apply_avatar();
this.create_form();
this.create_form_title();
this.create_error_flash();
if (this.identify('redmineUserID')) {
this.create_form_hidden(this.form, 'redmine_user', 'redmine_user', 'form-control', this.identify('redmineUserID'));
}
if (this.identify('nameValue')) {
this.create_form_hidden(this.form, 'username', 'username', 'form-control', this.identify('nameValue'));
} else {
this.create_form_text(this.form, 'username', 'username', this.translation('nameLabel') || '<%= t(:label_helpdesk_widget_name) %>', 'form-control', this.identify('nameValue'), true);
}
if (this.identify('emailValue')) {
this.create_form_hidden(this.form, 'email', 'email', 'form-control', this.identify('emailValue'));
} else {
this.create_form_text(this.form, 'email', 'email' , this.translation('emailLabel') || '<%= t(:label_helpdesk_widget_email) %>', 'form-control', this.identify('emailValue'), true);
}
if (this.identify('subjectValue')) {
this.create_form_hidden(this.form, 'subject', 'issue[subject]', 'form-control', this.identify('subjectValue'));
} else {
this.create_form_text(this.form, 'subject', 'issue[subject]' , this.translation('subjectLabel') || '<%= t(:label_helpdesk_widget_subject) %>', 'form-control', this.identify('subjectValue'), true);
}
this.create_projects_selector();
this.create_form_area(this.form, 'description', 'issue[description]' , this.translation('descriptionLabel') || '<%= t(:label_helpdesk_widget_description) %>', 'form-control', true);
var project_id = null;
var tracker_id = null;
if (RedmineHelpdeskWidget.configuration['identify']){
project_id = RedmineHelpdeskWidget.schema.projects[RedmineHelpdeskWidget.configuration['identify']['projectValue']];
if (project_id) {
tracker_id = RedmineHelpdeskWidget.schema.projects_data[project_id].trackers[RedmineHelpdeskWidget.configuration['identify']['trackerValue']];
}
}
this.load_project_data(project_id || this.schema.projects[Object.keys(this.schema.projects)[0]], tracker_id);
this.iframe.contentWindow.document.body.appendChild(this.form);
this.append_stylesheets();
this.append_scripts();
this.create_message_listener();
} else {
this.widget.style.display = 'none';
}
},
apply_avatar: function(){
button = document.getElementById('widget_button');
avatar = RedmineHelpdeskWidget.configuration['user_avatar'];
if (avatar && avatar.length > 0) {
var xmlhttp = getXmlHttp();
xmlhttp.open('GET', RedmineHelpdeskWidget.base_url + '/helpdesk_widget/avatar/' + avatar, true);
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200 || xmlhttp.status == 304) {
button.style.backgroundSize = 'cover';
button.style.backgroundImage = 'url(' + RedmineHelpdeskWidget.base_url + '/helpdesk_widget/avatar/' + avatar + ')' ;
button.style.border = '2px solid';
button.innerHTML = '&nbsp;';
} else {
button.style.backgroundSize = '15px 15px';
button.innerHTML = '?';
}
button.style.display = 'block';
button.style.lineHeight = '50px';
}
};
xmlhttp.send(null);
} else {
button.style.lineHeight = '54px';
button.style.backgroundSize = '15px 15px';
button.innerHTML = '?';
button.style.display = 'block';
}
},
append_stylesheets: function(){
if (this.configuration['styles']) {
styles_css = document.createElement('style');
styles_css.innerHTML = this.configuration['styles'];
styles_css.type = "text/css";
}
widget_css = document.createElement('link');
widget_css.href = this.base_url + '/helpdesk_widget/widget.css';
widget_css.rel = "stylesheet";
widget_css.type = "text/css";
this.iframe.contentWindow.document.head.appendChild(widget_css);
if (this.configuration['styles']) {
this.iframe.contentWindow.document.head.appendChild(styles_css);
}
},
append_scripts: function(){
script = document.createElement('script');
script.type = 'text/javascript';
script.src = this.base_url + '/helpdesk_widget/iframe.js';
this.iframe.contentWindow.document.head.appendChild(script);
config_script = document.createElement('script');
config_script.innerHTML = "var RedmineHelpdeskIframe = {configuration: "+ JSON.stringify(this.configuration) +"}";
this.iframe.contentWindow.document.head.appendChild(config_script);
},
create_form: function(){
this.form = document.createElement('form');
this.form.action = this.base_url + '/helpdesk_widget/create_ticket';
this.form.acceptCharset = 'UTF-8';
this.form.method = 'post';
this.form.id = 'widget_form';
this.form.setAttribute('onSubmit', 'submitTicketForm(); return false;');
this.form.style.marginBottom = 0;
},
create_form_title: function(){
if (this.configuration['title']) {
title_div = document.createElement('div');
title_div.id = 'title';
title_div.className = 'title';
title_div.innerHTML = this.configuration['title'];
this.form.appendChild(title_div);
}
},
create_error_flash: function(){
flash_div = document.createElement('div');
flash_div.id = 'flash';
flash_div.className = 'flash';
this.form.appendChild(flash_div);
},
create_projects_selector: function(){
var project_id = null;
if (RedmineHelpdeskWidget.configuration['identify']){
project_id = RedmineHelpdeskWidget.schema.projects[RedmineHelpdeskWidget.configuration['identify']['projectValue']];
}
if (project_id) {
this.create_form_hidden(this.form, 'project_id', 'project_id', 'form-control projects', project_id);
} else {
this.create_form_select(this.form, 'project_id', 'project_id', RedmineHelpdeskWidget.schema.projects, project_id, 'form-control projects');
}
},
load_project_data: function(project_id, tracker_id){
container_div = this.form.getElementsByClassName('container')[0]
if (container_div) { container_div.remove() };
container_div = document.createElement('div');
container_div.id = 'container';
container_div.className = 'container';
custom_div = document.createElement('div');
custom_div.id = 'custom_fields';
custom_div.className = 'custom_fields';
submit_div = document.createElement('div');
submit_div.id = 'submit_button';
submit_div.className = 'submit_button';
container_div.appendChild(custom_div);
container_div.appendChild(submit_div);
if (RedmineHelpdeskWidget.configuration['identify'] && RedmineHelpdeskWidget.schema.projects_data[project_id].trackers[RedmineHelpdeskWidget.configuration['identify']['trackerValue']]){
tracker_id = RedmineHelpdeskWidget.schema.projects_data[project_id].trackers[RedmineHelpdeskWidget.configuration['identify']['trackerValue']]
this.create_form_hidden(custom_div, 'tracker_id', 'tracker_id', 'form-control trackers', tracker_id);
} else {
this.create_form_select(custom_div, 'tracker_id', 'tracker_id', this.schema.projects_data[project_id].trackers, tracker_id, 'form-control trackers');
tracker_id = custom_div.getElementsByClassName('trackers')[0].value;
}
this.load_custom_fields(custom_div, project_id, tracker_id);
this.create_form_submit(submit_div, this.translation('createButtonLabel') || '<%= l(:label_helpdesk_widget_create_ticket) %>');
this.create_attch_link(submit_div);
this.form.appendChild(container_div);
},
reload_project_data: function(){
project_id = this.form.getElementsByClassName('projects')[0].value;
tracker_id = container_div.getElementsByClassName('trackers')[0].value;
this.load_project_data(project_id, tracker_id);
this.positionate_iframe();
},
create_form_select: function(target, field_id, field_name, values, selected, field_class){
if (Object.keys(values).length == 1) {
field = document.createElement('input');
field.type = 'hidden';
field.id = field_id;
field.name = field_name;
field.className = field_class;
field.value = values[Object.keys(values)[0]];
} else {
field = document.createElement('select');
field.id = field_id;
field.name = field_name;
field.className = field_class;
for (var project in values) {
option = document.createElement('option');
option.value = values[project]
if(values[project] == selected) { option.selected = 'selected'; }
option.innerHTML = project;
field.appendChild(option);
}
}
field.setAttribute('onChange', 'needReloadProjectData();');
target.appendChild(field);
},
create_form_hidden: function(target, field_id, field_name, field_class, value){
field = document.createElement('input');
field.type = 'hidden';
field.id = field_id;
field.name = field_name;
field.value = value;
field.className = field_class;
target.appendChild(field);
},
create_form_text: function(target, field_id, field_name, field_placeholder, field_class, value, required){
field = document.createElement('input');
field.type = 'text';
field.id = field_id;
field.name = field_name;
field.value = value;
field.placeholder = field_placeholder;
field.className = required ? field_class + ' required-field' : field_class;
target.appendChild(field);
},
create_form_area: function(target, field_id, field_name, field_placeholder, field_class, required){
field = document.createElement('textarea');
field.cols = 55;
field.rows = 10;
field.id = field_id;
field.name = field_name;
field.placeholder = field_placeholder;
field.className = required ? field_class + ' required-field' : field_class;
target.appendChild(field);
},
create_form_submit: function(target, label){
field = document.createElement('input');
field.id = 'form-submit-btn';
field.type = 'submit';
field.name = 'submit';
field.className = 'btn';
field.value = label;
field.title = this.translation('buttomLabel') || '';
if (RedmineHelpdeskWidget.configuration['color']) {
field.style.background = RedmineHelpdeskWidget.configuration['color'];
}
target.appendChild(field);
},
create_attch_link: function(target){
if (this.configuration['attachment'] != false ) {
attach_div = document.createElement('div');
attach_div.className = 'attach_div';
attach_link = document.createElement('a');
attach_link.className = 'attach_link';
attach_link.href = 'javascript:void(0)';
attach_link.innerHTML = this.translation('attachmentLinkLabel') || 'Attach a file';
attach_div.appendChild(attach_link);
attach_field = document.createElement('input');
attach_field.type = 'file';
attach_field.id = 'attachment';
attach_field.className = 'attach_field';
attach_field.name = 'attachment';
attach_field.attributes['data-max-size'] = <%= Setting[:attachment_max_size].to_i * 1024 %>;
attach_field.addEventListener('change', function(){ RedmineHelpdeskWidget.upload_file() });
attach_div.appendChild(attach_field);
this.attachment = attach_field;
target.appendChild(attach_div);
}
},
upload_file: function(){
if (this.attachment.attributes['data-max-size'] > this.attachment.files[0].size) {
this.read_file(this.attachment.files[0], function(e){
attach_field = RedmineHelpdeskWidget.form.getElementsByClassName('attach_field')[0]
attach_field.attributes['data-value'] = e.target.result;
displayed_name = (attach_field.files[0].name.length <= 20) ? attach_field.files[0].name : attach_field.files[0].name.substring(0, 20) + '...';
RedmineHelpdeskWidget.form.getElementsByClassName('attach_link')[0].innerHTML = displayed_name;
});
} else {
this.attachment.attributes['data-value'] = '';
RedmineHelpdeskWidget.form.getElementsByClassName('attach_link')[0].innerHTML = '<%= t(:label_helpdesk_widget_file_large) %>';
}
},
read_file: function(file, callback){
var reader = new FileReader();
reader.onload = callback
reader.readAsDataURL(file);
},
load_custom_fields: function(target, project_id, tracker_id){
var xmlhttp = getXmlHttp();
var params = 'project_id=' + encodeURIComponent(project_id) + '&tracker_id=' + encodeURIComponent(tracker_id);
custom_div = document.createElement('div');
xmlhttp.open('GET', this.base_url + '/helpdesk_widget/load_custom_fields?' + params, true);
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200 || xmlhttp.status == 304) {
custom_div.innerHTML = xmlhttp.responseText;
target.appendChild(custom_div);
RedmineHelpdeskWidget.set_custom_values();
RedmineHelpdeskWidget.positionate_iframe();
}
}
};
xmlhttp.send(null);
},
set_custom_values: function(){
if (this.configuration['identify'] && this.configuration['identify']['customFieldValues']){
for(var cf in this.configuration['identify']['customFieldValues']) {
custom_field = this.form.querySelector('#issue_custom_field_values_' + this.schema.custom_fields[cf])
if (custom_field){
switch (custom_field.tagName){
case 'INPUT':
custom_field.type = 'hidden';
custom_field.value = this.configuration['identify']['customFieldValues'][cf];
this.form.querySelector("[data-error-key='" + cf + "']").style.display = 'none';
break;
case 'SELECT':
options = custom_field.options;
for(var option, index = 0; option = options[index]; index++) {
if(option.value == this.configuration['identify']['customFieldValues'][cf]) {
this.create_form_hidden(custom_field.parentElement, custom_field.id, custom_field.name, custom_field.classList.toString(), this.configuration['identify']['customFieldValues'][cf]);
custom_field.remove();
this.form.querySelector("[data-error-key='" + cf + "']").style.display = 'none';
break;
}
}
break;
}
}
}
}
},
positionate_iframe: function(){
widget_height = this.form.offsetHeight > this.height ? this.height : this.form.offsetHeight;
this.iframe.setAttribute('height', this.margin + widget_height);
switch (this.configuration['position']) {
case 'topLeft':
this.iframe.style.top = (this.margin + this.widget_button.offsetWidth) + 'px';
iframe.style.left = (this.margin - this.widget_button.offsetWidth / 2) + 'px';
break;
case 'topRight':
this.iframe.style.top = (this.margin + this.widget_button.offsetWidth) + 'px';
iframe.style.left = (this.margin + this.widget_button.offsetWidth - this.width - 20) + 'px';
break;
case 'bottomLeft':
this.iframe.style.top = (- this.margin * 2 - widget_height) + 'px';
iframe.style.left = (this.margin - this.widget_button.offsetWidth / 2) + 'px';
break;
case 'bottomRight':
this.iframe.style.top = (- this.margin * 2 - widget_height) + 'px';
iframe.style.left = (this.margin + this.widget_button.offsetWidth - this.width - 20) + 'px';
break;
default:
this.iframe.style.top = (- this.margin * 2 - widget_height) + 'px';
iframe.style.left = (this.margin + this.widget_button.offsetWidth - this.width - 20) + 'px';
}
},
create_message_listener: function(){
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
eventer(messageEvent,function(e) {
data = JSON.parse(e.data);
if (data['reload'] == true) {
RedmineHelpdeskWidget.reload = true;
}
if (data['project_reload'] == true) {
RedmineHelpdeskWidget.reload_project_data();
}
},false);
},
reload_form: function(){
this.iframe.remove();
this.create_iframe();
this.fill_form();
this.decorate_iframe();
this.reload = false;
},
show: function() {
this.iframe.style.visibility = 'visible';
this.iframe.style.opacity = '1';
this.positionate_iframe();
switch (this.configuration['position']) {
case 'topLeft':
case 'topRight':
this.widget_button.style.borderRadius = '50% 50% 0%';
break;
case 'bottomLeft':
case 'bottomRight':
this.widget_button.style.borderRadius = '0 100% 100%';
break;
default:
this.widget_button.style.borderRadius = '0 100% 100%';
}
this.widget_button.style.webkitTransform = 'rotate(45deg)';
this.widget_button.style.mozTransform = 'rotate(45deg)';
this.widget_button.style.msTransform = 'rotate(45deg)';
this.widget_button.style.oTransform = 'rotate(45deg)';
},
hide: function() {
if (this.reload == true) {
this.reload_form();
}
body = this.iframe.contentWindow.document.body;
this.iframe.style.visibility = 'hidden';
this.iframe.style.opacity = '0';
this.widget_button.style.borderRadius = '30px';
this.widget_button.style.webkitTransform = '';
this.widget_button.style.mozTransform = '';
this.widget_button.style.msTransform = '';
this.widget_button.style.oTransform = '';
},
toggle: function() {
(this.iframe.style.visibility == 'visible') ? this.hide() : this.show();
}
}
RedmineHelpdeskWidget.load();
@@ -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 %>
@@ -0,0 +1,41 @@
<% if User.current.allowed_to?(:view_helpdesk_tickets, @project) && @issue.journal_messages && journal_message = @issue.journal_messages.detect{|j| j.journal_id == journal.id} %>
<p class="journal_message">
<span class="icon <%= journal_message.is_incoming? ? 'icon-email' : 'icon-email-to' %>">
<%= journal_message.is_incoming? ? l(:label_received_from) : l(:label_sent_to) %>
</span>
<span class="contact" style="white-space: nowrap;display: inline-block;">
<% if journal_message.is_incoming? %>
<%= "#{contact_tag(journal_message.contact)} (#{journal_message.from_address})".html_safe %>
<% else %>
<%= journal_message.contact.emails.include?(journal_message.to_address) ? "#{contact_tag(journal_message.contact)} (#{journal_message.to_address})".html_safe : journal_message.to_address %>
<% end %>
</span>
<% if attachment = journal_message.message_file %>
<span class="attachment" style="white-space: nowrap;display: inline-block;">
<%= link_to_attachment attachment, :text => l(:label_helpdesk_original), :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(journal_message.message_date) %></span>
<% unless journal_message.bcc_address.blank? && journal_message.cc_address.blank? %>
<br>
<span class="heldesk_cc helpdesk-message-date">
<%= "#{l(:label_helpdesk_cc)}: #{journal_message.cc_address}" unless journal_message.cc_address.blank? %><%= ", #{l(:label_helpdesk_bcc)}: #{journal_message.bcc_address}" unless journal_message.bcc_address.blank? %>
</span>
<% end %>
<% if false && journal_message.is_incoming? %>
<span class="actions" style="float: right;">
<%= link_to "Split", "", :class => "icon icon-split" %>
</span>
<% end %>
</p>
<% end %>
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="robots" content="noindex, nofollow">
<%= csrf_meta_tag %>
<%= favicon %>
<%= stylesheet_link_tag 'jquery/jquery-ui-1.9.2', 'application', :media => 'all' %>
<%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %>
<%= javascript_heads %>
<%= heads_for_theme %>
<%= call_hook :view_layouts_base_html_head %>
<!-- page specific tags -->
<%= yield :header_tags -%>
</head>
<body class="<%=h body_css_classes %>">
<div id="wrapper">
<div id="wrapper2">
<div id="wrapper3">
<div id="header">
<h1><%= RedmineHelpdesk.public_title.blank? ? Setting.app_title : RedmineHelpdesk.public_title %></h1>
</div>
<div id="main" class="<%= sidebar_content? ? '' : 'nosidebar' %>">
<div id="sidebar">
<%= yield :sidebar %>
<%= view_layouts_base_sidebar_hook_response %>
</div>
<div id="content">
<%= render_flash_messages %>
<%= yield %>
<%= call_hook :view_layouts_base_content %>
<div style="clear:both;"></div>
</div>
</div>
</div>
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
<div id="ajax-modal" style="display:none;"></div>
<div id="footer">
<div class="bgl"><div class="bgr">
</div></div>
</div>
</div>
</div>
<%= call_hook :view_layouts_base_body_bottom %>
</body>
</html>
@@ -0,0 +1,56 @@
<% tickets_scope = Issue.visible.open.joins(:helpdesk_ticket).where(:assigned_to_id => User.current.id) %>
<% tickets = tickets_scope.limit(10) %>
<h3><%= l(:my_helpdesk_tickets) %> (<%= tickets_scope.count %>)</h3>
<% if tickets && tickets.any? %>
<%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do %>
<table class="list tickets">
<thead><tr>
<th><%=l(:field_subject)%></th>
<th><%=l(:field_project)%></th>
<th><%=l(:label_helpdesk_contact)%></th>
<th><%=l(:label_helpdesk_last_message)%></th>
</tr></thead>
<tbody>
<% for ticket in tickets %>
<tr id="ticket-<%= h(ticket.id) %>" class="issue hascontextmenu <%= cycle('odd', 'even') %> <%= ticket.css_classes %>">
<td class="subject">
<%= check_box_tag("ids[]", ticket.id, false, :style => 'display:none;', :id => nil) %>
<span class="icon <%= ticket.helpdesk_ticket.ticket_source_icon %>"></span>
<%= link_to "##{ticket.id} - #{truncate(ticket.subject, :length => 60)}", issue_path(ticket) %> (<%=h ticket.status %>)
</td>
<td class="project"><%= link_to_project(ticket.project) %></td>
<td class="customer"><%= contact_tag(ticket.customer) + (ticket.customer_company.blank? ? "" : " (#{ticket.customer_company})") if ticket.customer %></td>
<td class="last_message"><small>
<%= (ticket.last_message.blank? ? ticket.description : ticket.last_message).truncate(250) %>
</small></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>
<% if tickets.length > 0 %>
<p class="small"><%= link_to l(:label_helpdesk_view_all_tickets), :controller => 'issues',
:action => 'index',
:set_filter => 1,
:assigned_to_id => 'me',
:customer => "*",
:status_id => "o",
:c => ["project", "tracker", "status", "subject", "customer", "customer_company", "last_message"],
# :op => {:assigned_to_id => "=", :customer => "*", :status_id => "o"},
:sort => 'priority:desc,updated_on:desc' %></p>
<% end %>
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom,
{:controller => 'issues', :action => 'index', :set_filter => 1,
:assigned_to_id => 'me', :format => 'atom', :key => User.current.rss_key},
{:title => l(:label_assigned_to_me_issues)}) %>
<% end %>
@@ -0,0 +1,10 @@
<% if User.current.allowed_to?(:view_helpdesk_tickets, @project) %>
<% if tickets = HelpdeskTicket.includes(:issue => [:project]).where(:projects => {:id => @project}) %>
<% customers = Contact.includes(:tickets => :project).where(:projects => {:id => @project}) %>
<h3><%= l(:label_helpdesk_ticket_plural) %></h3>
<p><span class="icon icon-helpdesk"><%= l(:text_helpdesk_ticket_count, :count => tickets.count) %></span></p>
<p><span class="icon icon-company-contact"><%= l(:text_helpdesk_customer_count, :count => customers.count) %> </span></p>
<p><%# link_to(l(:label_report), {:controller => "helpdesk_reports", :action => "tickets_report", :project_id => @project}) %></p>
<%= call_hook(:view_projects_show_helpdesk_sidebar_bottom, :project => @project) %>
<% end %>
<% end %>
@@ -0,0 +1,43 @@
<%= error_messages_for 'helpdesk_settings' %>
<% if @project.module_enabled?(:contacts) && @project.module_enabled?(:issue_tracking) %>
<% if canned_responses = CannedResponse.visible.in_project_or_public(@project).order("#{CannedResponse.table_name}.name") %>
<table class="list">
<thead><tr>
<th><%= l(:field_name) %></th>
<th><%= l(:field_content) %></th>
<th><%= l(:field_is_public) %></th>
<th><%= l(:field_is_for_all) %></th>
<th></th>
</tr></thead>
<tbody>
<% canned_responses.each do |canned_response| %>
<tr class="<%= cycle 'odd', 'even' %>">
<td class="name"><%= canned_response.name %></td>
<td class="name"><em class="info"><%= canned_response.content.gsub(/$/, ' ').truncate(250) %></em></td>
<td class="tick"><%= checked_image canned_response.is_public? %></td>
<td class="tick"><%= checked_image canned_response.project.blank? %></td>
<td class="buttons">
<% if User.current.allowed_to?(:manage_canned_responses, @project) %>
<%= link_to l(:button_edit), edit_canned_response_path(canned_response), :class => 'icon icon-edit' %>
<%= delete_link canned_response_path(canned_response, :project_id => @project) %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>
<p><%= link_to l(:label_helpdesk_new_canned_response), new_project_canned_response_path(@project), :class => 'icon icon-add' if User.current.allowed_to?(:manage_canned_responses, @project) %></p>
<% else %>
<p class="nodata"><%= l(:label_helpdesk_enable_modules) %></p>
<% end %>
@@ -0,0 +1,58 @@
<div class="box" >
<p>
<label><%= l(:field_mail_from) %></label>
<%= text_field_tag "helpdesk_answer_from", HelpdeskSettings["helpdesk_answer_from", @project.id], :size => "60", :placeholder => RedmineHelpdesk.settings["helpdesk_answer_from"] %>
<em class="info"><%= l(:text_helpdesk_answer_macros, :macro => HelpdeskSettings::FROM_MACRO_LIST.join(', ')) %></em>
</p>
<p>
<label><%= l(:label_helpdesk_answered_status) %></label>
<%= select_tag "helpdesk_answered_status", ("<option value=\"\">#{l(:label_no_change_option)}</option>" + options_for_select(IssueStatus.all.collect {|p| [p.name, p.id.to_s]}, HelpdeskSettings["helpdesk_answered_status", @project.id])).html_safe %>
</p>
<p>
<label><%= l(:label_helpdesk_reopen_status) %></label>
<%= select_tag "helpdesk_reopen_status", ("<option value=\"\">#{l(:label_no_change_option)}</option>" + options_for_select(IssueStatus.all.collect {|p| [p.name, p.id.to_s]}, HelpdeskSettings["helpdesk_reopen_status", @project.id])).html_safe %>
</p>
<p>
<label><%= l(:label_helpdesk_tracker) %></label>
<%= select_tag "helpdesk_tracker", options_for_select([[l(:label_all), "all"]] + @project.trackers.collect {|t| [t.name, t.id.to_s]}, HelpdeskSettings["helpdesk_tracker", @project.id]), :include_blank => true %>
</p>
<p>
<label><%= l(:label_helpdesk_assigned_to) %></label>
<%= select_tag "helpdesk_assigned_to", ("<option value=\"\">#{l(:label_no_change_option)}</option>" + options_for_select(@project.assignable_users.collect {|t| [t.name, t.id.to_s]}, HelpdeskSettings["helpdesk_assigned_to", @project.id])).html_safe %>
</p>
<p>
<label><%= l(:label_helpdesk_lifetime) %></label>
<%= text_field_tag "helpdesk_lifetime", HelpdeskSettings["helpdesk_lifetime", @project.id], :size => "5" %> <%= l(:label_day_plural) %>
</p>
<hr/>
<p>
<label><%= l(:label_not_create_contacts) %></label>
<%= hidden_field_tag("helpdesk_is_not_create_contacts", 0) %>
<%= check_box_tag "helpdesk_is_not_create_contacts", 1, HelpdeskSettings["helpdesk_is_not_create_contacts", @project.id].to_i > 0, :onclick => '$("#add_tags").toggle();' %>
</p>
<p>
<label><%= l(:label_helpdesk_blacklist) %></label>
<%= text_area_tag "helpdesk_blacklist", HelpdeskSettings["helpdesk_blacklist", @project.id].blank? ? '' : HelpdeskSettings["helpdesk_blacklist", @project.id].split("\n").map{|u| u.strip}.join("\n"), :rows => 10 %>
<br /><em class="info"><%= l(:text_custom_field_possible_values_info) %></em> </p>
<div id="add_tags" class="contacts-tags-edit" <%= "style=\"display: none;\"" if HelpdeskSettings["helpdesk_is_not_create_contacts", @project.id].to_i > 0 %>>
<p>
<label><%= l(:field_created_contact_tags) %></label>
<%= text_field_tag "helpdesk_created_contact_tag", HelpdeskSettings["helpdesk_created_contact_tag", @project.id], :size => 10, :class => 'hol' %><%= tagsedit_for('#helpdesk_created_contact_tag', Contact.available_tags(:project => @project).map(&:name).join("\',\'").html_safe ) %>
</p>
</div>
</div>
@@ -0,0 +1,184 @@
<fieldset class="box tabular"><legend><%= l(:label_helpdesk_incoming_mail_server) %></legend>
<script type="text/javascript" charset="utf-8">
function changeServerSettings(element) {
$('#helpdesk_use_ssl_field').show();
if (element.value == 'pop3') {
$('#server_settings').show();
$('#imap_settings').hide();
$('#pop3_settings').show();
$('#host_settings').show();
}
if (element.value == 'imap') {
$('#server_settings').show();
$('#pop3_settings').hide();
$('#imap_settings').show();
$('#host_settings').show();
}
if (element.value == '') {
$('#server_settings').hide();
}
if (element.value == 'gmail' || element.value == 'yahoo' || element.value == 'yandex' ) {
$('#server_settings').show();
$('#host_settings').hide();
$('#imap_settings').show();
$('#pop3_settings').hide();
$('#helpdesk_use_ssl_field').hide();
}
}
</script>
<p>
<label><%= l(:label_helpdesk_protocol) %></label>
<%= select_tag :helpdesk_protocol, options_for_select([['', ""], ["pop3", "pop3"], ["imap", "imap"], ["Gmail", "gmail"], ["Yahoo", "yahoo"], ["Yandex", "yandex"]] , HelpdeskSettings[:helpdesk_protocol, @project.id]), :onchange => "changeServerSettings(this)" %>
</p>
<span id="server_settings" <%= "style=\"display: none;\"".html_safe if HelpdeskSettings[:helpdesk_protocol, @project.id].blank? %>>
<span id="host_settings" <%= "style=\"display: none;\"".html_safe if ["gmail","yahoo", "yandex"].include?(HelpdeskSettings[:helpdesk_protocol, @project.id]) %>>
<p>
<label><%= l(:label_helpdesk_host) %></label>
<%= text_field_tag :helpdesk_host, HelpdeskSettings[:helpdesk_host, @project.id] %>
</p>
<p>
<label><%= l(:label_helpdesk_port) %></label>
<%= text_field_tag :helpdesk_port, HelpdeskSettings[:helpdesk_port, @project.id] %>
</p>
</span>
<p>
<label><%= l(:label_helpdesk_username) %></label>
<%= text_field_tag :helpdesk_username, HelpdeskSettings[:helpdesk_username, @project.id] %>
</p>
<p>
<label><%= l(:label_helpdesk_password) %></label>
<%= link_to_function image_tag('edit.png'), '$(this).hide(); $("#helpdesk_password_field").show()' unless HelpdeskSettings[:helpdesk_username, @project.id].blank? %>
<%= content_tag 'span', :id => "helpdesk_password_field", :style => (HelpdeskSettings[:helpdesk_username, @project.id].blank? ? nil : 'display:none') do %>
<%= password_field_tag :helpdesk_password, '' %>
<% end %>
</p>
<p <%= "style=\"display: none;\"".html_safe if ["gmail", "yahoo", "yandex"].include?(HelpdeskSettings[:helpdesk_protocol, @project.id]) %> id="helpdesk_use_ssl_field">
<label><%= l(:label_helpdesk_ssl) %></label>
<%= hidden_field_tag(:helpdesk_use_ssl, 0) %>
<%= check_box_tag :helpdesk_use_ssl, 1, HelpdeskSettings[:helpdesk_use_ssl, @project.id].to_i > 0 %>
</p>
<span id="imap_settings" <%= "style=\"display: none;\"".html_safe if !["gmail", "yahoo", "yandex", "imap"].include?(HelpdeskSettings[:helpdesk_protocol, @project.id]) %>>
<p>
<label><%= l(:label_helpdesk_imap_folder) %></label>
<%= text_field_tag :helpdesk_imap_folder, HelpdeskSettings[:helpdesk_imap_folder, @project.id] %>
</p>
<p>
<label><%= l(:label_helpdesk_move_on_success) %></label>
<%= text_field_tag :helpdesk_move_on_success, HelpdeskSettings[:helpdesk_move_on_success, @project.id] %>
</p>
<p>
<label><%= l(:label_helpdesk_move_on_failure) %></label>
<%= text_field_tag :helpdesk_move_on_failure, HelpdeskSettings[:helpdesk_move_on_failure, @project.id] %>
</p>
</span>
<span id="pop3_settings" <%= "style=\"display: none;\"".html_safe if HelpdeskSettings[:helpdesk_protocol, @project.id] != "pop3" %>>
<p>
<label><%= l(:label_helpdesk_apop) %></label>
<%= hidden_field_tag(:helpdesk_apop, 0) %>
<%= check_box_tag :helpdesk_apop, 1, HelpdeskSettings[:helpdesk_apop, @project.id].to_i > 0 %>
</p>
<p>
<label><%= l(:label_helpdesk_delete_unprocessed) %></label>
<%= hidden_field_tag(:helpdesk_delete_unprocessed, 0) %>
<%= check_box_tag :helpdesk_delete_unprocessed, 1, HelpdeskSettings[:helpdesk_delete_unprocessed, @project.id].to_i > 0 %>
</p>
</span>
<div id="test_connection_messages">
</div>
<%= link_to l(:label_helpdesk_get_mail),
{},
:remote => true,
:onclick => "updateCustomForm('#{url_for(:controller => 'helpdesk', :action => 'get_mail', :project_id => @project)}', $('#helpdesk_settings'))" %>
<br/>
</span> <!-- Server settings -->
</fieldset>
<fieldset class="box tabular"><legend><%= l(:label_helpdesk_outgoing_mail_server) %> (experimental)</legend>
<p>
<label><%= l(:label_helpdesk_smtp_use_default_settings) %></label>
<%= hidden_field_tag(:helpdesk_smtp_use_default_settings, 1) %>
<%= check_box_tag :helpdesk_smtp_use_default_settings, 0, HelpdeskSettings[:helpdesk_smtp_use_default_settings, @project.id].to_i == 0, :onchange => "$('.smtp-settings').toggle(); return false;" %>
</p>
<span class="smtp-settings" <%= "style=\"display: none;\"".html_safe unless HelpdeskSettings[:helpdesk_smtp_use_default_settings, @project.id].to_i > 0 %>>
<p>
<label><%= l(:label_helpdesk_smtp_server) %></label>
<%= text_field_tag :helpdesk_smtp_server, HelpdeskSettings[:helpdesk_smtp_server, @project.id] %>
</p>
<p>
<label><%= l(:label_helpdesk_port) %></label>
<%= text_field_tag :helpdesk_smtp_port, HelpdeskSettings[:helpdesk_smtp_port, @project.id] %>
</p>
<p>
<label><%= l(:label_helpdesk_smtp_domain) %></label>
<%= text_field_tag :helpdesk_smtp_domain, HelpdeskSettings[:helpdesk_smtp_domain, @project.id] %>
</p>
<p>
<label><%= l(:label_helpdesk_authentication) %></label>
<%= select_tag :helpdesk_smtp_authentication, options_for_select([[l(:label_helpdesk_authentication_plain), "plain"], [l(:label_helpdesk_authentication_login), "login"], [l(:label_helpdesk_authentication_cram_md5), "cram_md5"]] , HelpdeskSettings[:helpdesk_smtp_authentication, @project.id]) %>
</p>
<p>
<label><%= l(:label_helpdesk_username) %></label>
<%= text_field_tag :helpdesk_smtp_username, HelpdeskSettings[:helpdesk_smtp_username, @project.id] %>
</p>
<p>
<label><%= l(:label_helpdesk_password) %></label>
<%= link_to_function image_tag('edit.png'), '$(this).hide(); $("#helpdesk_smtp_password_field").show()' unless HelpdeskSettings[:helpdesk_smtp_username, @project.id].blank? %>
<%= content_tag 'span', :id => "helpdesk_smtp_password_field", :style => (HelpdeskSettings[:helpdesk_smtp_username, @project.id].blank? ? nil : 'display:none') do %>
<%= password_field_tag :helpdesk_smtp_password, '' %>
<% end %>
</p>
<p>
<label><%= l(:label_helpdesk_ssl) %></label>
<%= hidden_field_tag(:helpdesk_smtp_ssl, 0) %>
<%= check_box_tag :helpdesk_smtp_ssl, 1, HelpdeskSettings[:helpdesk_smtp_ssl, @project.id].to_i > 0 %>
</p>
<p>
<label><%= l(:label_helpdesk_smtp_tls) %></label>
<%= hidden_field_tag(:helpdesk_smtp_tls, 0) %>
<%= check_box_tag :helpdesk_smtp_tls, 1, HelpdeskSettings[:helpdesk_smtp_tls, @project.id].to_i > 0 %>
</p>
</span> <!-- Server settings -->
</fieldset>
@@ -0,0 +1,28 @@
<%= error_messages_for 'helpdesk_settings' %>
<% errors = [] %>
<% errors << l(:label_helpdesk_enable_modules) unless @project.module_enabled?(:contacts) && @project.module_enabled?(:issue_tracking) %>
<% if errors.empty? %>
<%= form_tag({:controller => :helpdesk, :action => :save_settings, :project_id => @project, :tab => 'helpdesk'}, :method => :put, :class => "tabular", :multipart => true, :id => 'helpdesk_settings') do %>
<div class="splitcontentleft">
<h3><%=l(:label_helpdesk)%></h3>
<%= render :partial => 'projects/settings/helpdesk_general' %>
</div>
<div class="splitcontentright">
<h3><%=l(:label_helpdesk_server_settings)%></h3>
<%= render :partial => 'projects/settings/helpdesk_server' %>
</div>
<div style="clear:both;"></div>
<%= submit_tag l(:button_save) %>
<% end %>
<% else %>
<p class="nodata"><%= errors.join("<br/>").html_safe %></p>
<% end %>
@@ -0,0 +1,64 @@
<%= error_messages_for 'helpdesk_settings' %>
<% if @project.module_enabled?(:contacts) && @project.module_enabled?(:issue_tracking) %>
<%= form_tag({:controller => :helpdesk, :action => :save_settings, :project_id => @project, :tab => 'helpdesk_template'}, :method => :put, :class => "tabular", :multipart => true, :id => 'helpdesk_template') do %>
<fieldset class="box tabular"><legend><%= l(:label_helpdesk_answer_template) %></legend>
<p>
<label><%= l(:field_subject) %></label>
<%= text_field_tag "helpdesk_answer_subject", HelpdeskSettings["helpdesk_answer_subject", @project.id], :style => "width:100%" %>
</p>
<p>
<label><%= l(:setting_emails_header) %></label>
<%= text_area_tag "helpdesk_emails_header", HelpdeskSettings["helpdesk_emails_header", @project.id], :class => 'wiki-edit', :rows => 5 %>
</p>
<p>
<label><%= l(:setting_emails_footer) %></label>
<%= text_area_tag "helpdesk_emails_footer", HelpdeskSettings["helpdesk_emails_footer", @project.id], :class => 'wiki-edit', :rows => 5 %>
</p>
</fieldset>
<fieldset class="box" style="background-color: #FFD;"><legend><%= l(:label_helpdesk_auto_answer_template) %></legend>
<p>
<label><%= l(:label_send_auto_answer) %></label>
<%= hidden_field_tag("helpdesk_send_notification", 0) %>
<%= check_box_tag "helpdesk_send_notification", 1, ContactsSetting["helpdesk_send_notification", @project.id].to_i > 0 %>
</p>
<p>
<label><%= l(:field_subject) %></label>
<%= text_field_tag "helpdesk_first_answer_subject", HelpdeskSettings["helpdesk_first_answer_subject", @project.id], :style => "width:100%" %>
</p>
<p>
<label><%= l(:label_helpdesk_first_answer_template) %></label>
<%= text_area_tag "helpdesk_first_answer_template", HelpdeskSettings["helpdesk_first_answer_template", @project.id], :class => 'wiki-edit', :rows => 15 %>
<%= wikitoolbar_for 'helpdesk_first_answer_template' %>
</p>
</fieldset>
<div> <em class="info"><%= l(:text_helpdesk_answer_macros, :macro => HelpdeskSettings::MACRO_LIST.join(', ')) %></em></div>
<br/>
<%= submit_tag l(:button_save) %>
<% end %>
<% else %>
<p class="nodata"><%= l(:label_helpdesk_enable_modules) %></p>
<% end %>
<% content_for :header_tags do %>
<%= javascript_include_tag :"tag-it", :plugin => 'redmine_contacts' %>
<%= stylesheet_link_tag :"jquery.tagit.css", :plugin => 'redmine_contacts' %>
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
<% end %>
@@ -0,0 +1,8 @@
<%= labelled_form_for @journal, :url => public_ticket_add_comment_path(:id => @ticket.id, :hash => @ticket.token) , :html => {:id => 'add_comment_form', :multipart => true} do |f| %>
<%= error_messages_for 'journal', 'journal_message' %>
<div class="box">
<%= f.text_area 'notes', :cols => 60, :rows => 10, :class => 'wiki-edit' %>
<%= wikitoolbar_for 'journal_notes' %>
</div>
<%= submit_tag l(:button_submit) %>
<% end %>
@@ -0,0 +1,13 @@
<div class="attachments">
<% for attachment in attachments %>
<p><%= link_to_attachment_with_hash attachment, :class => 'icon icon-attachment', :download => true -%>
<% if attachment.is_text? %>
<%= link_to image_tag('magnifier.png'),
:controller => 'attachments', :action => 'show',
:id => attachment, :filename => attachment.filename %>
<% end %>
<%= h(" - #{attachment.description}") unless attachment.description.blank? %>
<span class="size">(<%= number_to_human_size attachment.filesize %>)</span>
</p>
<% end %>
</div>
@@ -0,0 +1,11 @@
<% for journal in journals %>
<div id="change-<%= journal.id %>" class="<%= journal.css_classes %> <%= journal.journal_message.is_incoming? ? 'incoming' : 'outgoing' %>">
<div id="note-<%= journal.indice %>">
<h4><%= link_to "##{journal.indice}", {:anchor => "note-#{journal.indice}"}, :class => "journal-link" %>
<%= authoring_public journal, :label => :label_updated_time_by %></h4>
<div class="wiki" id="journal-<%= journal.id %>-notes">
<%= textilizable(journal, :notes) unless journal.notes.blank? %>
</div>
</div>
</div>
<% end %>
@@ -0,0 +1,19 @@
<% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
<% if (RedmineHelpdesk.public_spent_time? && @total_spent_hours.to_i > 0) || !@previous_tickets.blank? %>
<% content_for :sidebar do %>
<% if RedmineHelpdesk.public_spent_time? && @total_spent_hours.to_i > 0 %>
<h3><%= l(:label_spent_time) %></h3>
<p><span class="icon icon-time"><%= l_hours(@total_spent_hours) %></span></p>
<% end %>
<% unless @previous_tickets.empty? %>
<h3>
<%= l(:label_helpdesk_previous_tickets) %>
</h3>
<% @previous_tickets.each do |previous_ticket| %>
<p><%= link_to "##{previous_ticket.id} - #{previous_ticket.subject} (#{previous_ticket.status.name})", public_ticket_path(previous_ticket.helpdesk_ticket, :hash => previous_ticket.helpdesk_ticket.token), :class => previous_ticket.css_classes %></p>
<% end %>
<% end %>
<% end %>
<% end %>
@@ -0,0 +1,79 @@
<h2><%= issue_heading(@issue) %></h2>
<div class="<%= @issue.css_classes %> details">
<%= avatar(@ticket.from_address, :size => "50") %>
<div class="subject">
<h3><%= @issue.subject %></h3>
</div>
<p class="author">
<%= l(:label_added_time_by, :author => mail_to(@ticket.from_address), :age => content_tag('acronym', distance_of_time_in_words(Time.now, @issue.created_on), :title => format_time(@issue.created_on))).html_safe %>.
<%# authoring @issue.created_on, mail_to(@ticket.from_address) %>
<% if @issue.created_on != @issue.updated_on %>
<%= l(:label_updated_time, ticket_time_tag(@issue.updated_on)).html_safe %>.
<% end %>
</p>
<<%= Redmine::VERSION.to_s > '3.2' ? 'div' : 'table' %> class="attributes">
<%= issue_fields_rows do |rows|
rows.left l(:field_status), h(@issue.status.name), :class => 'status'
unless @issue.disabled_core_fields.include?('assigned_to_id')
rows.left l(:field_assigned_to), (@issue.assigned_to ? @issue.assigned_to.name : "-"), :class => 'assigned-to'
end
unless @issue.disabled_core_fields.include?('done_ratio')
rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%"), :class => 'progress'
end
if RedmineHelpdesk.public_spent_time?
unless @issue.disabled_core_fields.include?('estimated_hours')
if RedmineHelpdesk.public_spent_time? && !@issue.estimated_hours.blank?
rows.right l(:field_estimated_hours), l_hours(@issue.estimated_hours), :class => 'estimated-hours'
end
end
rows.right l(:label_spent_time), (@issue.total_spent_hours > 0 ? l_hours(@issue.total_spent_hours) : "-"), :class => 'spent-time'
end
end %>
<%# render_custom_fields_rows(@issue) %>
</<%= Redmine::VERSION.to_s > '3.2' ? 'div' : 'table' %>>
<% if @issue.description? || @issue.attachments.any? -%>
<hr />
<% if @issue.description? %>
<p><strong><%=l(:field_description)%></strong></p>
<div class="wiki">
<%= textilizable @issue, :description, :attachments => @issue.attachments %>
</div>
<% end %>
<% if @issue.attachments.any? %>
<fieldset class="collapsible collapsed">
<legend onclick="toggleFieldset(this);"><%= l(:label_attachment_plural) %></legend>
<div style="display: none;">
<%= link_to_attachments_with_hash @issue, :thumbnails => true %>
</div>
</fieldset>
<% end %>
<% end -%>
</div>
<% if @journals.present? %>
<div id="history" class="ticket-history">
<h3><%=l(:label_history)%></h3>
<%= render :partial => 'public_tickets/history', :locals => { :issue => @issue, :journals => @journals } %>
</div>
<% end %>
<div style="clear: both;"></div>
<% if RedmineHelpdesk.public_comments? %>
<p><%= toggle_link l(:label_comment_add), "update", :focus => "journal_notes" %></p>
<div id="update" style="display:none;">
<h3><%= l(:label_comment_add) %></h3>
<%= render :partial => 'add_comment' %>
</div>
<% end %>
<%= render :partial => 'sidebar_content' %>
@@ -0,0 +1,18 @@
<% helpdesk_tabs = [
{:name => 'general', :partial => 'settings/helpdesk_general', :label => :label_helpdesk_general},
{:name => 'public', :partial => 'settings/helpdesk_public', :label => :label_helpdesk_settings_public},
{:name => 'templates', :partial => 'settings/helpdesk_template', :label => :label_helpdesk_template},
{:name => 'votes', :partial => 'settings/helpdesk_vote', :label => :label_helpdesk_vote},
{:name => 'canned_responses', :partial => 'settings/helpdesk_canned_responses', :label => :label_helpdesk_canned_response_plural},
{:name => 'widget', :partial => 'settings/helpdesk_widget', :label => :label_helpdesk_widget}
] %>
<% helpdesk_tabs.push({:name => 'hidden', :partial => 'settings/helpdesk_hidden', :label => :label_crm_contacts_hidden}) if params[:hidden] %>
<%= render_tabs helpdesk_tabs %>
<% html_title(l(:label_settings), l(:label_helpdesk)) -%>
<% content_for(:header_tags) do %>
<%= javascript_include_tag :redmine_helpdesk, :plugin => 'redmine_contacts_helpdesk' %>
<% end %>
@@ -0,0 +1,2 @@
<% @canned_responses = CannedResponse.all %>
<%= render :partial => 'canned_responses/index' %>
@@ -0,0 +1,50 @@
<p>
<label><%= l(:label_helpdesk_from_address) %></label>
<%= text_field_tag 'settings[helpdesk_answer_from]', @settings["helpdesk_answer_from"], :size => "98%" %>
<em class="info"><%= l(:text_helpdesk_answer_macros, :macro => HelpdeskSettings::FROM_MACRO_LIST.join(', ')) %></em>
</p>
<p>
<label><%= l(:label_helpdesk_save_cc) %></label>
<%= check_box_tag 'settings[helpdesk_save_cc]', 1, @settings["helpdesk_save_cc"] %>
</p>
<p>
<label><%= l(:label_helpdesk_send_note_by_default) %></label>
<%= check_box_tag 'settings[send_note_by_default]', 1, @settings["send_note_by_default"] %>
</p>
<p>
<label><%= l(:label_helpdesk_add_contact_notes) %></label>
<%= check_box_tag 'settings[helpdesk_add_contact_notes]', 1, @settings["helpdesk_add_contact_notes"] %>
</p>
<p>
<label><%= l(:label_helpdesk_assign_contact_user) %></label>
<%= check_box_tag 'settings[helpdesk_assign_contact_user]', 1, @settings["helpdesk_assign_contact_user"], :class => 'assign_contact_user' %>
</p>
<p>
<label class='parent'><%= l(:label_helpdesk_create_private_tickets) %></label>
<%= check_box_tag 'settings[helpdesk_create_private_tickets]', 1, @settings["helpdesk_create_private_tickets"], :disabled => true, :class => 'private_tikets' %>
</p>
<p>
<label class='parent'><%= l(:label_helpdesk_autoclose_tickets_after) %></label>
<%= select_tag 'settings[helpdesk_autoclose_tickets_after]', options_for_select((1..24).map{ |h| [h, h] }, @settings["helpdesk_autoclose_tickets_after"]),
:onchange => "toggleStatusesForAutoclose(this); return false", :include_blank => true %>
<%= radio_button_tag 'settings[helpdesk_autoclose_tickets_time_unit]', 'day', RedmineHelpdesk.autoclose_time_unit_is?('day') || RedmineHelpdesk.autoclose_time_unit.nil? %>
<%= l(:label_helpdesk_days) %>
<%= radio_button_tag 'settings[helpdesk_autoclose_tickets_time_unit]', 'hour', RedmineHelpdesk.autoclose_time_unit_is?('hour') %>
<%= l(:label_helpdesk_hours) %>
</p>
<div id="statuses_autoclose" style="display:<%= !@settings["helpdesk_autoclose_tickets_after"].blank? ? 'block' : 'none' %>">
<p>
<label><%= l(:label_helpdesk_autoclose_from_status) %></label>
<%= select_tag 'settings[helpdesk_autoclose_from_status]', options_for_select(IssueStatus.all.map{|st| [st.name, st.id]}, @settings["helpdesk_autoclose_from_status"]), :style => "width:150px" %>
</p>
<p>
<label><%= l(:label_helpdesk_autoclose_to_status) %></label>
<%= select_tag 'settings[helpdesk_autoclose_to_status]', options_for_select(IssueStatus.all.map{|st| [st.name, st.id]}, @settings["helpdesk_autoclose_to_status"]), :style => "width:150px" %>
</p>
</div>
@@ -0,0 +1,9 @@
<p>
<label><%= l(:setting_plain_text_mail) %></label>
<%= check_box_tag 'settings[plain_text_mail]', 1, @settings["plain_text_mail"] %>
</p>
<p>
<label>Do not strip HTML tags</label>
<%= check_box_tag 'settings[helpdesk_do_not_strip_tags]', 1, @settings["helpdesk_do_not_strip_tags"] %>
</p>
@@ -0,0 +1,20 @@
<p>
<label><%= l(:label_helpdesk_public_tickets) %></label>
<%= check_box_tag 'settings[helpdesk_public_tickets]', 1, @settings["helpdesk_public_tickets"] %>
</p>
<p>
<label><%= l(:label_helpdesk_public_show_spent_time) %></label>
<%= check_box_tag 'settings[helpdesk_public_show_spent_time]', 1, @settings["helpdesk_public_show_spent_time"] %>
</p>
<p>
<label><%= l(:label_helpdesk_public_comments) %></label>
<%= check_box_tag 'settings[helpdesk_public_comments]', 1, @settings["helpdesk_public_comments"] %>
</p>
<p>
<label><%= l(:label_helpdesk_public_title) %></label>
<%= text_field_tag 'settings[helpdesk_public_title]', @settings["helpdesk_public_title"], :size => "98%" %>
</p>
@@ -0,0 +1,45 @@
<fieldset class="box tabular"><legend><%= l(:label_helpdesk_answer_template) %></legend>
<p>
<label><%= l(:field_subject) %></label>
<%= text_field_tag 'settings[helpdesk_answer_subject]', @settings["helpdesk_answer_subject"], :style => "width:100%" %>
</p>
<p>
<label><%= l(:setting_emails_header) %></label>
<%= text_area_tag 'settings[helpdesk_emails_header]', @settings["helpdesk_emails_header"], :class => 'wiki-edit', :rows => 5 %>
</p>
<p>
<label><%= l(:setting_emails_footer) %></label>
<%= text_area_tag 'settings[helpdesk_emails_footer]', @settings["helpdesk_emails_footer"], :class => 'wiki-edit', :rows => 5 %>
</p>
</fieldset>
<fieldset class="box" style="background-color: #FFD;"><legend><%= l(:label_helpdesk_auto_answer_template) %></legend>
<p>
<label><%= l(:field_subject) %></label>
<%= text_field_tag 'settings[helpdesk_first_answer_subject]', @settings["helpdesk_first_answer_subject"], :style => "width:100%" %>
</p>
<%= text_area_tag 'settings[helpdesk_first_answer_template]', @settings["helpdesk_first_answer_template"], :class => 'wiki-edit', :rows => 15 %>
</fieldset>
<fieldset class="box"><legend><%= l(:label_helpdesk_css) %></legend>
<%= text_area_tag 'settings[helpdesk_helpdesk_css]', @settings["helpdesk_helpdesk_css"], :class => 'wiki-edit', :rows => 10 %>
</fieldset>
<em class="info"><%= l(:text_helpdesk_answer_macros, :macro => HelpdeskSettings::MACRO_LIST.join(', ')) %></em>
<% if params[:show_hidden] %>
<fieldset id="hidden_settings">
<p>
<label>Show excerpt issues list</label>
<%= check_box_tag 'settings[show_excerpt_tickets_list]', 1, @settings["show_excerpt_tickets_list"] %>
</p>
</fieldset>
<% end %>
@@ -0,0 +1,9 @@
<p>
<label><%= l(:label_helpdesk_vote_settings) %></label>
<%= check_box_tag 'settings[helpdesk_vote_accept]', 1, @settings["helpdesk_vote_accept"] %>
</p>
<p>
<label><%= l(:label_helpdesk_vote_comment_settings) %></label>
<%= check_box_tag 'settings[helpdesk_vote_comment_accept]', 1, @settings["helpdesk_vote_comment_accept"] %>
</p>
@@ -0,0 +1,44 @@
<p>
<label><%= l(:label_helpdesk_widget_enable) %></label>
<%= check_box_tag 'settings[helpdesk_widget_enable]', 1, @settings["helpdesk_widget_enable"] %>
</p>
<% if @settings["helpdesk_widget_enable"].to_i > 0 %>
<p>
<label><%= l(:label_helpdesk_widget_available_projects) %></label>
<% @helpdesk_projects = Project.visible.has_module('contacts_helpdesk') %>
<% if @helpdesk_projects.count > 0 %>
<% @helpdesk_projects.each do |project| %>
<%= check_box_tag 'settings[helpdesk_widget_available_projects][]', project.id, @settings["helpdesk_widget_available_projects"].try(:include?, project.id.to_s) %>
<span><%= project.name %></span>
<br>
<% end %>
<% else %>
<span class="error-text"><%= l(:label_helpdesk_widget_no_available_projects) %></span>
<% end %>
</p>
<p>
<label><%= l(:label_helpdesk_widget_custom_fields) %></label>
<% IssueCustomField.visible.each do |cf| %>
<%= check_box_tag 'settings[helpdesk_widget_available_custom_fields][]', cf.id, @settings["helpdesk_widget_available_custom_fields"].try(:include?, cf.id.to_s) %>
<span><%= cf.name %></span>
<br>
<% end %>
</p>
<h4><%= l(:label_helpdesk_widget_activation_message) %></h4>
<pre style="background-color: #ddd;">
<code class="html syntaxhl">
<%= Redmine::SyntaxHighlighting.highlight_by_language(
"<span>
<div id=\"helpdesk_widget\"></div>
<script type=\"text/javascript\" src=\"#{Setting.protocol}://#{Setting.host_name}/helpdesk_widget/widget.js\"></script>
</span>", "html").html_safe %>
</code>
</pre>
<span>
<div id="helpdesk_widget"></div>
<script type="text/javascript" src="<%= "#{Setting.protocol}://#{Setting.host_name}" %>/helpdesk_widget/widget.js"></script>
</span>
<% end %>