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,135 @@
ActionDispatch::Callbacks.to_prepare do
require 'redmine_helpdesk/patches/issues_controller_patch'
require 'redmine_helpdesk/patches/journals_controller_patch'
require 'redmine_helpdesk/patches/attachments_controller_patch'
require 'redmine_helpdesk/patches/issue_patch'
require 'redmine_helpdesk/patches/journal_patch'
require 'redmine_helpdesk/patches/contact_patch'
require 'redmine_helpdesk/patches/issue_query_patch'
require 'redmine_helpdesk/patches/time_report_patch'
require 'redmine_helpdesk/patches/queries_helper_patch'
require 'redmine_helpdesk/patches/projects_helper_patch'
require 'redmine_helpdesk/patches/contacts_helper_patch'
require 'redmine_helpdesk/patches/application_helper_patch'
require 'redmine_helpdesk/patches/mail_handler_patch'
require 'redmine_helpdesk/patches/compatibility_patch'
require 'redmine_helpdesk/patches/contact_query_patch'
require 'redmine_helpdesk/hooks/view_layouts_hook'
require 'redmine_helpdesk/hooks/view_issues_hook'
require 'redmine_helpdesk/hooks/view_projects_hook'
require 'redmine_helpdesk/hooks/view_contacts_hook'
require 'redmine_helpdesk/hooks/view_journals_hook'
require 'redmine_helpdesk/hooks/controller_contacts_duplicates_hook'
require 'redmine_helpdesk/hooks/helper_issues_hook'
require 'redmine_helpdesk/hooks/issues_controller_hook'
require 'redmine_helpdesk/wiki_macros/helpdesk_wiki_macro'
end
class HelpdeskLogFormatter < Logger::Formatter
def call(severity, time, progname, msg)
"[%s] - %s - %s\n" % [severity, time.to_s(:short), msg2str(msg)] unless msg2str(msg).blank?
end
end
HelpdeskLogger = Logger.new(Rails.root.join('log/redmine_helpdesk.log'))
HelpdeskLogger.formatter = HelpdeskLogFormatter.new
class HelpdeskSettings
MACRO_LIST = %w({%contact.first_name%} {%contact.name%} {%contact.company%} {%contact.last_name%}
{%contact.middle_name%} {%date%} {%ticket.assigned_to%} {%ticket.id%} {%ticket.tracker%}
{%ticket.project%} {%ticket.subject%} {%ticket.quoted_description%} {%ticket.history%} {%ticket.status%}
{%ticket.priority%} {%ticket.estimated_hours%} {%ticket.done_ratio%} {%ticket.public_url%} {%ticket.closed_on%} {%ticket.due_date%}
{%ticket.start_date%} {%ticket.voting%} {%ticket.voting.good%} {%ticket.voting.okay%} {%ticket.voting.bad%}
{%response.author%} {%response.author.first_name%} {%response.author.last_name%})
FROM_MACRO_LIST = %w({%response.author%} {%response.author.first_name%})
# Returns the value of the setting named name
def self.[](name, project_id)
project_id = project_id.id if project_id.is_a?(Project)
ContactsSetting[name.to_s, project_id].present? ? ContactsSetting[name.to_s, project_id] : RedmineHelpdesk.settings[name.to_s]
end
end
module RedmineHelpdesk
module ContactUserMethods
def name(formatter = nil)
f = self.class.name_formatter(formatter)
if formatter
eval('"' + f[:string] + '"')
else
@name ||= eval('"' + f[:string] + '"')
end
end
end
def self.settings() Setting[:plugin_redmine_contacts_helpdesk] ? Setting[:plugin_redmine_contacts_helpdesk] : {} end
def self.public_title
universal_setting("helpdesk_public_title")
end
def self.public_tickets?
universal_setting("helpdesk_public_tickets").to_i > 0
end
def self.vote_allow?
universal_setting("helpdesk_vote_accept").to_i > 0
end
def self.vote_comment_allow?
universal_setting("helpdesk_vote_comment_accept").to_i > 0
end
def self.strip_tags?
universal_setting("helpdesk_do_not_strip_tags").to_i <= 0
end
def self.public_comments?
universal_setting("helpdesk_public_comments").to_i > 0
end
def self.public_spent_time?
universal_setting("helpdesk_public_show_spent_time").to_i > 0
end
def self.save_log?
universal_setting(:helpdesk_vote_save_log).to_i > 0
end
def self.autoclose_tickets_after
universal_setting("helpdesk_autoclose_tickets_after").to_i
end
def self.autoclose_from_status
universal_setting("helpdesk_autoclose_from_status").to_i
end
def self.autoclose_to_status
universal_setting("helpdesk_autoclose_to_status").to_i
end
def self.autoclose_time_unit
universal_setting("helpdesk_autoclose_tickets_time_unit")
end
def self.autoclose_time_unit_is?(value)
autoclose_time_unit == value
end
def self.autoclose_time_interval
case autoclose_time_unit
when 'day'
1.day * autoclose_tickets_after
when 'hour'
1.hour * autoclose_tickets_after
else
-1
end
end
def self.universal_setting(setting)
[settings[setting.to_sym], settings[setting.to_s]].compact.first
end
end
@@ -0,0 +1,10 @@
module RedmineHelpdesk
module Hooks
class ControllerContactsDuplicatesHook < Redmine::Hook::ViewListener
def controller_contacts_duplicates_merge(context={})
context[:duplicate].journal_messages << context[:contact].journal_messages
context[:duplicate].helpdesk_tickets << context[:contact].helpdesk_tickets
end
end
end
end
@@ -0,0 +1,15 @@
module RedmineHelpdesk
module Hooks
class HelperIssuesHook < Redmine::Hook::ViewListener
def helper_issues_show_detail_after_setting(context={})
if context[:detail].prop_key == 'vote'
detail = context[:detail]
context[:detail].value = HelpdeskTicket.vote_message(detail.value) if detail.value && detail.value.to_s =~ /^\d$/
context[:detail].old_value = HelpdeskTicket.vote_message(detail.old_value) if detail.old_value && detail.old_value.to_s =~ /^\d$/
end
end
end
end
end
@@ -0,0 +1,11 @@
module RedmineHelpdesk
module Hooks
class IssuesControllerHook < Redmine::Hook::ViewListener
def controller_issues_new_before_save(context = {})
ticket = context[:issue].helpdesk_ticket
return if ticket.nil? || ticket.from_address.present? || ticket.customer.nil? || ticket.customer.primary_email.blank?
ticket.from_address = ticket.customer.primary_email
end
end
end
end
@@ -0,0 +1,7 @@
module RedmineHelpdesk
module Hooks
class ViewContactsHook < Redmine::Hook::ViewListener
render_on :view_contacts_context_menu_before_delete, :partial => "context_menus/helpdesk_contacts"
end
end
end
@@ -0,0 +1,13 @@
module RedmineHelpdesk
module Hooks
class ViewIssuesHook < Redmine::Hook::ViewListener
render_on :view_issues_edit_notes_bottom, :partial => 'issues/send_response'
def view_issues_sidebar_issues_bottom(context = {})
context[:controller].send(:render_to_string, { :partial => 'issues/helpdesk_reports', :locals => context }) +
context[:controller].send(:render_to_string, { :partial => 'issues/helpdesk_customer_profile', :locals => context })
end
render_on :view_issues_show_details_bottom, :partial => 'issues/ticket_data'
render_on :view_issues_form_details_top, :partial => 'issues/ticket_data_form'
end
end
end
@@ -0,0 +1,9 @@
include ContactsHelper
module RedmineHelpdesk
module Hooks
class ShowJournalContactHook < Redmine::Hook::ViewListener
render_on :view_issues_history_journal_bottom, :partial => "journals/journal_contact"
end
end
end
@@ -0,0 +1,9 @@
module RedmineHelpdesk
module Hooks
class ViewsLayoutsHook < Redmine::Hook::ViewListener
def view_layouts_base_html_head(context={})
return stylesheet_link_tag(:helpdesk, :plugin => 'redmine_contacts_helpdesk')
end
end
end
end
@@ -0,0 +1,10 @@
module RedmineHelpdesk
module Hooks
class ViewProjectsHook < Redmine::Hook::ViewListener
def view_projects_show_sidebar_bottom(context = {})
context[:controller].send(:render_to_string, { :partial => 'issues/helpdesk_reports', :locals => context }) +
context[:controller].send(:render_to_string, { :partial => 'projects/helpdesk_tickets', :locals => context })
end
end
end
end
@@ -0,0 +1,45 @@
require_dependency 'application_helper'
module RedmineHelpdesk
module Patches
module ApplicationHelperPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :avatar, :helpdesk
alias_method_chain :link_to_user, :helpdesk
end
end
module InstanceMethods
# include ContactsHelper
def avatar_with_helpdesk(user, options = { })
if user.is_a?(Contact)
avatar_to(user, options)
else
avatar_without_helpdesk(user, options)
end
end
def link_to_user_with_helpdesk(user, options={})
if user.is_a?(Contact)
link_to_source(user, options)
else
link_to_user_without_helpdesk(user, options)
end
end
end
end
end
end
unless ApplicationHelper.included_modules.include?(RedmineHelpdesk::Patches::ApplicationHelperPatch)
ApplicationHelper.send(:include, RedmineHelpdesk::Patches::ApplicationHelperPatch)
end
@@ -0,0 +1,27 @@
module RedmineHelpdesk
module Patches
module AttachmentsControllerPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :read_authorize, :helpdesk
end
end
module InstanceMethods
def read_authorize_with_helpdesk
unless params[:ticket_id] && params[:hash] && HelpdeskTicket.where(:id => params[:ticket_id]).first && HelpdeskTicket.where(:id => params[:ticket_id]).first.try(:token) == params[:hash]
read_authorize_without_helpdesk
end
end
end
end
end
end
unless AttachmentsController.included_modules.include?(RedmineHelpdesk::Patches::AttachmentsControllerPatch)
AttachmentsController.send(:include, RedmineHelpdesk::Patches::AttachmentsControllerPatch)
end
@@ -0,0 +1,5 @@
if Redmine::VERSION.to_s < '2.4'
def accept_attachment?(attachment)
true
end
end
@@ -0,0 +1,47 @@
module RedmineHelpdesk
module Patches
module ContactPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
has_many :journals, :through => :journal_messages
has_many :journal_messages, :dependent => :destroy
has_many :tickets, :through => :helpdesk_tickets, :source => :issue #class_name => "Issue", :as => :issue, :foreign_key => 'issue_id'
has_many :helpdesk_tickets, :dependent => :destroy
end
end
module InstanceMethods
def mail
self.primary_email
end
def email_name
[name, ' <', mail, '>'].join
end
def all_tickets
if self.is_company
Issue.eager_load(:customer).where(:contacts => {:id => [self.id] | self.company_contacts.map(&:id) })
else
self.tickets
end
end
def find_assigned_user(project, current_assigned_id)
return Principal.find_by_id(current_assigned_id) unless RedmineHelpdesk.settings["helpdesk_assign_contact_user"].to_i > 0
return assigned_to if assigned_to.present? && Project.visible(assigned_to).pluck(:id).include?(project.id)
return contact_company.assigned_to if contact_company.present? && contact_company.assigned_to.present? &&
Project.visible(contact_company.assigned_to).pluck(:id).include?(project.id)
Principal.find_by_id(current_assigned_id)
end
end
end
end
end
unless Contact.included_modules.include?(RedmineHelpdesk::Patches::ContactPatch)
Contact.send(:include, RedmineHelpdesk::Patches::ContactPatch)
end
@@ -0,0 +1,57 @@
require_dependency 'query'
module RedmineHelpdesk
module Patches
module ContactQueryPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.send(:include, HelpdeskHelper)
base.class_eval do
unloadable
alias_method_chain :available_filters, :helpdesk
end
end
module InstanceMethods
def sql_for_number_of_tickets_field(_, operator, value)
"(#{Contact.table_name}.id IN (SELECT #{HelpdeskTicket.table_name}.contact_id
FROM #{HelpdeskTicket.table_name}
GROUP BY #{HelpdeskTicket.table_name}.contact_id
HAVING count(#{HelpdeskTicket.table_name}.contact_id) #{operator} #{value.first}))"
end
def sql_for_open_tickets_field(_, operator, value)
value = value.first
in_cond = if (operator == '!' && value == '0') || (operator == '=' && value == '1')
'IN'
else
'NOT IN'
end
"(#{Contact.table_name}.id #{in_cond} (SELECT #{HelpdeskTicket.table_name}.contact_id
FROM #{HelpdeskTicket.table_name}
INNER JOIN #{Issue.table_name} on #{Issue.table_name}.id = #{HelpdeskTicket.table_name}.issue_id
INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id
WHERE (#{IssueStatus.table_name}.is_closed = #{ActiveRecord::Base.connection.quoted_false})
))"
end
def available_filters_with_helpdesk
if @available_filters.blank? && User.current.allowed_to?(:view_helpdesk_tickets, project, :global => true)
add_available_filter('number_of_tickets', :type => :integer, :name => l(:label_helpdesk_number_of_tickets)) unless available_filters_without_helpdesk.key?('number_of_tickets')
add_available_filter('open_tickets', :type => :list, :name => l(:label_helpdesk_open_tickets), :values => [[l(:general_text_yes), '1'], [l(:general_text_no), '0']]) unless available_filters_without_helpdesk.key?('open_tickets')
else
available_filters_without_helpdesk
end
@available_filters
end
end
end
end
end
unless ContactQuery.included_modules.include?(RedmineHelpdesk::Patches::ContactQueryPatch)
ContactQuery.send(:include, RedmineHelpdesk::Patches::ContactQueryPatch)
end
@@ -0,0 +1,26 @@
module RedmineHelpdesk
module Patches
module ContactsHelperPatch
def self.included(base)
base.class_eval do
unloadable
alias_method_chain :contact_tabs, :helpdesk
end
end
def contact_tabs_with_helpdesk(contact)
tabs = contact_tabs_without_helpdesk(contact)
if contact.all_tickets.visible.count > 0
tabs.push({:name => 'helpdesk', :partial => 'contacts/helpdesk_tickets', :label => l(:label_helpdesk_ticket_plural) + " (#{contact.all_tickets.visible.open.count}/#{contact.all_tickets.visible.count})"} )
end
tabs
end
end
end
end
unless ContactsHelper.included_modules.include?(RedmineHelpdesk::Patches::ContactsHelperPatch)
ContactsHelper.send(:include, RedmineHelpdesk::Patches::ContactsHelperPatch)
end
@@ -0,0 +1,27 @@
require_dependency 'queries_helper'
module RedmineHelpdesk
module Patches
module GravatarHelperPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :gravatar_api_url, :helpdesk
end
end
module InstanceMethods
def gravatar_api_url_with_helpdesk(hash)
[Setting[:protocol], ':', gravatar_api_url_without_helpdesk(hash)].join
end
end
end
end
end
unless GravatarHelper::PublicMethods.included_modules.include?(RedmineHelpdesk::Patches::GravatarHelperPatch)
GravatarHelper::PublicMethods.send(:include, RedmineHelpdesk::Patches::GravatarHelperPatch)
end
@@ -0,0 +1,89 @@
module RedmineHelpdesk
module Patches
module IssuePatch
def self.included(base)
base.send(:extend, ClassMethods)
base.send(:include, InstanceMethods)
base.send(:include, ActionView::Helpers::DateHelper)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
has_one :customer, :through => :helpdesk_ticket
has_one :helpdesk_ticket, :dependent => :destroy
scope :order_by_status, lambda { joins(:status).order("#{IssueStatus.table_name}.is_closed, #{IssueStatus.table_name}.id, #{Issue.table_name}.id DESC") }
accepts_nested_attributes_for :helpdesk_ticket
safe_attributes 'helpdesk_ticket_attributes',
:if => lambda { |issue, user| user.allowed_to?(:edit_helpdesk_tickets, issue.project) }
end
end
module ClassMethods
def load_helpdesk_data(issues, user=User.current)
if issues.any?
helpdesk_tickets = HelpdeskTicket.where(:issue_id => issues.map(&:id))
issues.each do |issue|
issue.instance_variable_set "@helpdesk_ticket", (helpdesk_tickets.detect{|c| c.issue_id == issue.id} || nil)
end
end
end
end
module InstanceMethods
def journal_messages
@journal_messages ||= JournalMessage.includes(:message_file, :contact => [:avatar, :projects]).
where(:journal_id => journals.pluck(:id)).
uniq.to_a
end
def is_ticket?
helpdesk_ticket.present?
end
def last_message
self.helpdesk_ticket.last_message.content.truncate(250) if self.helpdesk_ticket
end
def ticket_source
self.helpdesk_ticket.ticket_source_name if self.helpdesk_ticket
end
def customer_company
return nil unless self.customer
self.customer.company
end
def last_message_date
self.helpdesk_ticket.last_message_date if self.helpdesk_ticket
end
def ticket_reaction_time
helpdesk_ticket && helpdesk_ticket.reaction_time ? distance_of_time_in_words(helpdesk_ticket.reaction_time) : ""
end
def ticket_first_response_time
helpdesk_ticket && helpdesk_ticket.first_response_time ? distance_of_time_in_words(helpdesk_ticket.first_response_time) : ""
end
def ticket_resolve_time
helpdesk_ticket && helpdesk_ticket.resolve_time ? distance_of_time_in_words(helpdesk_ticket.resolve_time) : ""
end
def vote
helpdesk_ticket.present? && helpdesk_ticket.vote.present? ? HelpdeskTicket.vote_message(helpdesk_ticket.vote) : ""
end
def vote_comment
helpdesk_ticket.present? && helpdesk_ticket.vote_comment.present? ? helpdesk_ticket.vote_comment.to_s : ""
end
end
end
end
end
unless Issue.included_modules.include?(RedmineHelpdesk::Patches::IssuePatch)
Issue.send(:include, RedmineHelpdesk::Patches::IssuePatch)
end
@@ -0,0 +1,185 @@
require_dependency 'query'
module RedmineHelpdesk
module Patches
module IssueQueryPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.send(:include, HelpdeskHelper)
base.class_eval do
unloadable
alias_method_chain :available_columns, :helpdesk
alias_method_chain :available_filters, :helpdesk
alias_method_chain :joins_for_order_statement, :helpdesk
alias_method_chain :issues, :helpdesk
end
end
module InstanceMethods
# def issues_with_helpdesk(options={})
# if project.blank? || (project && User.current.allowed_to?(:view_helpdesk_tickets, project))
# options[:include] = (options[:include] || []) + [:helpdesk_ticket]
# end
# issues_without_helpdesk(options)
# end
def issues_with_helpdesk(options={})
issues = issues_without_helpdesk(options)
if has_column?(:last_message) || has_column?(:last_message_date) || has_column?(:customer) || has_column?(:ticket_source) || has_column?(:customer_company) || has_column?(:helpdesk_ticket) || has_column?(:ticket_reaction_time) || has_column?(:ticket_first_response_time) || has_column?(:ticket_resolve_time) || has_column?(:vote) || has_column?(:vote_comment)
Issue.load_helpdesk_data(issues)
end
issues
end
def joins_for_order_statement_with_helpdesk(order_options)
joins = joins_for_order_statement_without_helpdesk(order_options)
ticket_joins = [joins].flatten
if order_options && (order_options.include?('reaction_time') ||
order_options.include?('first_response_time') ||
order_options.include?('resolve_time') ||
order_options.include?('vote'))
ticket_joins << "LEFT OUTER JOIN #{HelpdeskTicket.table_name} ON #{Issue.table_name}.id = #{HelpdeskTicket.table_name}.issue_id"
end
ticket_joins.any? ? ticket_joins.join(' ') : nil
end
def sql_for_customer_field(field, operator, value)
case operator
when "*", "!*" # Member / Not member
sw = operator == "!*" ? 'NOT' : ''
"(#{Issue.table_name}.id #{sw} IN (SELECT DISTINCT #{HelpdeskTicket.table_name}.issue_id FROM #{HelpdeskTicket.table_name}))"
when "=", "!"
sw = operator == "!" ? 'NOT' : ''
contacts_select = "SELECT #{HelpdeskTicket.table_name}.issue_id FROM #{HelpdeskTicket.table_name}
WHERE #{HelpdeskTicket.table_name}.contact_id IN (#{value.join(',')})"
"(#{Issue.table_name}.id #{sw} IN (#{contacts_select}))"
end
end
def sql_for_ticket_source_field(field, operator, value)
case operator
when "=", "!"
sw = operator == "!" ? 'NOT' : ''
contacts_select = "SELECT #{HelpdeskTicket.table_name}.issue_id FROM #{HelpdeskTicket.table_name}
WHERE #{HelpdeskTicket.table_name}.source IN (#{value.join(',')})"
"(#{Issue.table_name}.id #{sw} IN (#{contacts_select}))"
end
end
def sql_for_customer_company_field(field, operator, value)
sw = ["!", "!~"].include?(operator) ? 'NOT' : ''
case operator
when "="
like_value = "LIKE '#{value.first.to_s.downcase}'"
when "!*"
like_value = "IS NULL OR #{Contact.table_name}.company = ''"
when "*"
like_value = "IS NOT NULL OR #{Contact.table_name}.company <> ''"
when "~", "!~"
like_value ="LIKE '%#{self.class.connection.quote_string(value.first.to_s.downcase)}%'"
end
contacts_select = "SELECT #{HelpdeskTicket.table_name}.issue_id FROM #{HelpdeskTicket.table_name}
WHERE #{HelpdeskTicket.table_name}.contact_id IN (
SELECT #{Contact.table_name}.id
FROM #{Contact.table_name}
WHERE LOWER(#{Contact.table_name}.company) #{like_value}
)"
"(#{Issue.table_name}.id #{sw} IN (#{contacts_select}))"
end
def sql_for_ticket_reaction_time_field(field, operator, value)
"(#{Issue.table_name}.id IN (SELECT #{HelpdeskTicket.table_name}.issue_id
FROM #{HelpdeskTicket.table_name}
WHERE #{sql_for_field(field, operator, value.map{|v| v.to_i * 60},
HelpdeskTicket.table_name, "reaction_time")}))"
end
def sql_for_ticket_first_response_time_field(field, operator, value)
"(#{Issue.table_name}.id IN (SELECT #{HelpdeskTicket.table_name}.issue_id
FROM #{HelpdeskTicket.table_name}
WHERE #{sql_for_field(field, operator, value.map{|v| v.to_i * 60},
HelpdeskTicket.table_name, "first_response_time")}))"
end
def sql_for_ticket_resolve_time_field(field, operator, value)
"(#{Issue.table_name}.id IN (SELECT #{HelpdeskTicket.table_name}.issue_id
FROM #{HelpdeskTicket.table_name}
WHERE #{sql_for_field(field, operator, value.map{|v| v.to_i * 60},
HelpdeskTicket.table_name, "resolve_time")}))"
end
def sql_for_vote_field(field, operator, value)
case operator
when '=', '*'
compare = 'IN'
when '!', '!*'
compare = 'NOT IN'
end
issues_select = "SELECT DISTINCT(issue_id) FROM helpdesk_tickets WHERE vote IN (#{ value.join(',') })"
issues_with_votes = 'SELECT DISTINCT(issue_id) FROM helpdesk_tickets WHERE vote IS NOT NULL'
"(#{Issue.table_name}.id #{compare} (#{ %w(= !).include?(operator) ? issues_select : issues_with_votes }))"
end
def available_columns_with_helpdesk
if @available_columns.blank? && User.current.allowed_to?(:view_helpdesk_tickets, project, :global => true)
@available_columns = available_columns_without_helpdesk
@available_columns << QueryColumn.new(:last_message, :caption => :label_helpdesk_last_message)
@available_columns << QueryColumn.new(:last_message_date, :caption => :label_helpdesk_last_message_date)
@available_columns << QueryColumn.new(:customer, :caption => :label_helpdesk_contact)
@available_columns << QueryColumn.new(:ticket_source, :caption => :label_helpdesk_ticket_source)
@available_columns << QueryColumn.new(:customer_company, :caption => :label_helpdesk_contact_company)
@available_columns << QueryColumn.new(:helpdesk_ticket, :caption => :label_helpdesk_ticket)
@available_columns << QueryColumn.new(:ticket_reaction_time, :caption => :label_helpdesk_ticket_reaction_time, :sortable => "#{HelpdeskTicket.table_name}.reaction_time")
@available_columns << QueryColumn.new(:ticket_first_response_time, :caption => :label_helpdesk_ticket_first_response_time, :sortable => "#{HelpdeskTicket.table_name}.first_response_time")
@available_columns << QueryColumn.new(:ticket_resolve_time, :caption => :label_helpdesk_ticket_resolve_time, :sortable => "#{HelpdeskTicket.table_name}.resolve_time")
@available_columns << QueryColumn.new(:vote, :caption => :label_helpdesk_vote, :sortable => "#{HelpdeskTicket.table_name}.vote")
@available_columns << QueryColumn.new(:vote_comment, :caption => :label_helpdesk_vote_comment)
else
available_columns_without_helpdesk
end
@available_columns
end
def available_filters_with_helpdesk
# && !RedmineHelpdesk.settings[:issues_filters]
if @available_filters.blank? && User.current.allowed_to?(:view_helpdesk_tickets, project, :global => true)
selected_customer = filters['customer'].present? ? Contact.visible.where(:id => filters['customer'][:values]).map { |c| [c.name, c.id.to_s] } : []
add_available_filter('customer', :type => :list_optional, :field_format => 'contact', :name => l(:label_helpdesk_contact),
:values => selected_customer) unless available_filters_without_helpdesk.key?('customer')
add_available_filter('ticket_source', :type => :list, :name => l(:label_helpdesk_ticket_source),
:values => helpdesk_tickets_source_for_select) unless available_filters_without_helpdesk.key?('ticket_source')
add_available_filter('customer_company', :type => :string, :name => l(:label_helpdesk_contact_company)) unless available_filters_without_helpdesk.key?('customer_company')
add_available_filter('ticket_reaction_time', :type => :integer, :name => l(:label_helpdesk_ticket_reaction_time)) unless available_filters_without_helpdesk.key?('ticket_reaction_time')
add_available_filter('ticket_first_response_time', :type => :integer, :name => l(:label_helpdesk_ticket_first_response_time)) unless available_filters_without_helpdesk.key?('ticket_first_response_time')
add_available_filter('ticket_resolve_time', :type => :integer, :name => l(:label_helpdesk_ticket_resolve_time)) unless available_filters_without_helpdesk.key?('ticket_resolve_time')
add_available_filter('vote', :type => :list_optional, :name => l(:label_helpdesk_vote),
:values => [[l(:label_helpdesk_mark_awesome), '2'], [l(:label_helpdesk_mark_justok), '1'], [l(:label_helpdesk_mark_notgood), '0']]) unless available_filters_without_helpdesk.key?('vote')
else
available_filters_without_helpdesk
end
@available_filters
end
end
end
end
end
unless IssueQuery.included_modules.include?(RedmineHelpdesk::Patches::IssueQueryPatch)
IssueQuery.send(:include, RedmineHelpdesk::Patches::IssueQueryPatch)
end
@@ -0,0 +1,82 @@
module RedmineHelpdesk
module Patches
module IssuesControllerPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
# before_filter :apply_helpdesk_macro, :only => :update
after_filter :flash_helpdesk, :only => :update
after_filter :send_auto_answer, :only => :create
alias_method_chain :build_new_issue_from_params, :helpdesk
alias_method_chain :update_issue_from_params, :helpdesk
helper :helpdesk
end
end
module InstanceMethods
def flash_helpdesk
if @issue.current_journal.is_send_note
render_send_note_warning_if_needed(@issue.current_journal)
flash[:notice] = flash[:notice].to_s + " " + l(:notice_email_sent, "<span class='icon icon-email'>" + @issue.current_journal.journal_message.to_address + "</span>") if @issue.current_journal.send_note_errors.blank?
end
end
def send_auto_answer
return unless @issue && @issue.customer && User.current.allowed_to?(:send_response, @project)
case params[:helpdesk_send_as].to_i
when HelpdeskTicket::SEND_AS_NOTIFICATION
msg = HelpdeskMailer.auto_answer(@issue.customer, @issue).deliver
when HelpdeskTicket::SEND_AS_MESSAGE
if msg = HelpdeskMailer.initial_message(@issue.customer, @issue, params).deliver
@issue.helpdesk_ticket.message_id = msg.message_id
@issue.helpdesk_ticket.is_incoming = false
@issue.helpdesk_ticket.from_address = @issue.customer.primary_email
@issue.helpdesk_ticket.save
end
end
flash[:notice].blank? ? flash[:notice] = l(:notice_email_sent, "<span class='icon icon-email'>" + msg.to_addrs.first + "</span>") : flash[:notice] << " " + l(:notice_email_sent, "<span class='icon icon-email'>" + msg.to_addrs.first + "</span>") if msg
rescue Exception => e
flash[:error].blank? ? flash[:error] = e.message : flash[:error] << " " + e.message
end
def update_issue_from_params_with_helpdesk
is_updated = update_issue_from_params_without_helpdesk
return false unless is_updated
if params[:helpdesk] && params[:helpdesk][:is_send_mail] && User.current.allowed_to?(:send_response, @project) && @issue.customer
@issue.current_journal.build_journal_message
journal_params = params[:journal_message].merge(Hash[params[:journal_message].slice('to_address', 'cc_address', 'bcc_address').
map { |k, v| [k, v.join(',')] }]) if params[:journal_message]
@issue.current_journal.journal_message.update_attributes(journal_params)
@issue.current_journal.journal_message.to_address ||= @issue.customer.primary_email
@issue.current_journal.is_send_note = true
@issue.current_journal.notes = HelpdeskMailer.apply_macro(@issue.current_journal.notes, @issue.customer, @issue, User.current)
end
is_updated
end
def build_new_issue_from_params_with_helpdesk
build_new_issue_from_params_without_helpdesk
return if @issue.blank? || params[:customer_id].blank?
contact = Contact.visible.find_by_id(params[:customer_id])
@issue.build_helpdesk_ticket(:issue => @issue, :ticket_date => Time.now, :customer => contact) if contact
@issue.helpdesk_ticket.source = params[:source] if params[:source]
end
def render_send_note_warning_if_needed(journal)
return false if journal.blank? || journal.journal_message.blank?
flash[:warning] = flash[:warning].to_s + " " + l(:label_helpdesk_email_sending_problems) + ": " + journal.send_note_errors unless journal.send_note_errors.blank?
end
end
end
end
end
unless IssuesController.included_modules.include?(RedmineHelpdesk::Patches::IssuesControllerPatch)
IssuesController.send(:include, RedmineHelpdesk::Patches::IssuesControllerPatch)
end
@@ -0,0 +1,84 @@
module RedmineHelpdesk
module Patches
module JournalPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
has_one :contact, :through => :journal_message
has_one :journal_message, :dependent => :destroy
attr_accessor :is_send_note
attr_accessor :send_note_errors
after_create :send_note
after_create :update_helpdesk_ticket
end
end
module InstanceMethods
def is_incoming?
self.journal_message && self.journal_message.is_incoming?
end
def is_sent?
self.journal_message && !self.journal_message.is_incoming?
end
def message_author
self.is_incoming? ? self.contact : self.user
end
def helpdesk_ticket
self.journalized.respond_to?(:helpdesk_ticket) && self.journalized.helpdesk_ticket
end
def send_note
require 'timeout'
if self.issue.customer && self.is_send_note && self.notes
journal_message = self.journal_message
begin
response_options = {:to_address => journal_message.to_address, :cc_address => journal_message.cc_address, :bcc_address => journal_message.bcc_address}
Timeout::timeout(60) do
HelpdeskMailer.with_activated_perform_deliveries do
if msg = HelpdeskMailer.issue_response(self.issue.customer, self, response_options).deliver
journal_message.message_date = msg.date
journal_message.is_incoming = false
journal_message.message_id = msg.message_id.to_s.slice(0, 255)
journal_message.source = HelpdeskTicket::HELPDESK_EMAIL_SOURCE
journal_message.contact = Contact.find_by_emails([msg.to_addrs.first]).first || self.issue.customer
journal_message.to_address = msg.to_addrs.first.to_s.slice(0, 255)
journal_message.cc_address = msg.cc.join(', ').to_s.slice(0, 255)
journal_message.bcc_address = msg.bcc.join(', ').to_s.slice(0, 255)
journal_message.save!
end
end
end
rescue Exception => e
self.send_note_errors = e.message
end
end
end
def update_helpdesk_ticket
return false if helpdesk_ticket.blank? || (helpdesk_ticket && helpdesk_ticket.ticket_date.blank?)
helpdesk_ticket.save
end
end
end
end
end
unless Journal.included_modules.include?(RedmineHelpdesk::Patches::JournalPatch)
Journal.send(:include, RedmineHelpdesk::Patches::JournalPatch)
end
@@ -0,0 +1,44 @@
module RedmineHelpdesk
module Patches
module JournalsControllerPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :new, :helpdesk
end
end
module InstanceMethods
def new_with_helpdesk
@journal = Journal.visible.find(params[:journal_id]) if params[:journal_id]
if @journal
user = @journal.user
text = @journal.notes
if user.anonymous? && @journal.contact
user = @journal.contact
end
else
user = @issue.author
text = @issue.description
if user.anonymous? && @issue.customer
user = @issue.customer
end
end
# Replaces pre blocks with [...]
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
@content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
@content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
rescue ActiveRecord::RecordNotFound
render_404
end
end
end
end
end
unless JournalsController.included_modules.include?(RedmineHelpdesk::Patches::JournalsControllerPatch)
JournalsController.send(:include, RedmineHelpdesk::Patches::JournalsControllerPatch)
end
@@ -0,0 +1,99 @@
module RedmineHelpdesk
module Patches
module MailHandlerPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :receive_issue_reply, :handle_helpdesk
alias_method_chain :cleanup_body, :handle_helpdesk
alias_method_chain :dispatch, :handle_helpdesk
end
end
module InstanceMethods
def receive_issue_reply_with_handle_helpdesk(issue_id, from_journal=nil)
journal = receive_issue_reply_without_handle_helpdesk(issue_id, from_journal)
helpdesk_receive_issue_reply(issue_id, journal, from_journal)
end
private
def dispatch_with_handle_helpdesk
if email_tag
tag_issue = Issue.where(:id => email_tag[/\+(\d+)@/, 1].to_i).first
return dispatch_without_handle_helpdesk unless tag_issue
receive_issue_reply(tag_issue.id)
else
dispatch_without_handle_helpdesk
end
rescue MissingInformation => e
logger.error "#{email && email.message_id}: missing information from #{user}: #{e.message}" if logger
false
rescue UnauthorizedAction => e
logger.error "#{email && email.message_id}: unauthorized attempt from #{user}" if logger
false
rescue Exception => e
# TODO: send a email to the user
logger.error "#{email && email.message_id}: dispatch error #{e.message}" if logger
false
end
def email_tag
(email.to.present? && email.to.find { |email| email.match(/\+\d+@/) }) ||
(email.cc.present? && email.cc.find { |email| email.match(/\+\d+@/) })
end
def helpdesk_receive_issue_reply(issue_id, journal, from_journal=nil)
return unless journal
return journal if journal.notes.blank?
project = journal.issue.project
return journal unless journal.user.allowed_to?(:send_response, journal.issue.project) && journal.issue.customer
unless HelpdeskSettings["send_note_by_default", project]
regexp = /^@@sendmail@@\s*$/
return journal unless journal.notes.match(regexp)
journal.notes = journal.notes.gsub(regexp, '')
end
contact = journal.issue.customer
begin
HelpdeskMailer.with_activated_perform_deliveries do
if msg = HelpdeskMailer.issue_response(contact, journal).deliver
JournalMessage.create(:to_address => msg.to_addrs.first.to_s.slice(0, 255),
:is_incoming => false,
:message_date => Time.now,
:message_id => msg.message_id.to_s.slice(0, 255),
:source => HelpdeskTicket::HELPDESK_EMAIL_SOURCE,
:cc_address => msg.cc.join(', ').to_s.slice(0, 255),
:bcc_address => msg.bcc.join(', ').to_s.slice(0, 255),
:contact => contact,
:journal => journal)
journal.issue.assigned_to = User.current unless journal.issue.assigned_to
journal.issue.status_id = HelpdeskSettings[:helpdesk_new_status, journal.issue.project_id] unless HelpdeskSettings[:helpdesk_new_status, journal.issue.project_id].blank?
journal.issue.save
HelpdeskLogger.info "#{msg.message_id}: Replay sent to #{contact.name} - [#{contact.emails.first}]" if HelpdeskLogger
end
end
rescue Exception => e
HelpdeskLogger.info "Error of replay sending to #{contact.name} - [#{contact.emails.first}], #{e.message}" if HelpdeskLogger
end
journal.save!
journal
end
def cleanup_body_with_handle_helpdesk(body)
cleanup_body_without_handle_helpdesk(body).gsub('&#13;', '')
end
end
end
end
end
unless MailHandler.included_modules.include?(RedmineHelpdesk::Patches::MailHandlerPatch)
MailHandler.send(:include, RedmineHelpdesk::Patches::MailHandlerPatch)
end
@@ -0,0 +1,46 @@
require_dependency 'queries_helper'
module RedmineHelpdesk
module Patches
module ProjectsHelperPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :project_settings_tabs, :helpdesk
end
end
module InstanceMethods
# include ContactsHelper
def project_settings_tabs_with_helpdesk
tabs = project_settings_tabs_without_helpdesk
helpdesk_tabs = []
helpdesk_tabs.push({ :name => 'helpdesk',
:action => :edit_helpdesk_settings,
:partial => 'projects/settings/helpdesk_settings',
:label => :label_helpdesk })
helpdesk_tabs.push({ :name => 'helpdesk_template',
:action => :edit_helpdesk_settings,
:partial => 'projects/settings/helpdesk_template',
:label => :label_helpdesk_template })
helpdesk_tabs.push({ :name => 'helpdesk_canned_responses',
:action => :manage_canned_responses,
:partial => 'projects/settings/helpdesk_canned_responses',
:label => :label_helpdesk_canned_response_plural })
helpdesk_tabs.each { |tab| tabs << tab if User.current.allowed_to?(tab[:action], @project) }
tabs
end
end
end
end
end
unless ProjectsHelper.included_modules.include?(RedmineHelpdesk::Patches::ProjectsHelperPatch)
ProjectsHelper.send(:include, RedmineHelpdesk::Patches::ProjectsHelperPatch)
end
@@ -0,0 +1,63 @@
require_dependency 'queries_helper'
module RedmineHelpdesk
module Patches
module QueriesHelperPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :column_content, :helpdesk
end
end
module InstanceMethods
include ContactsHelper
def column_content_with_helpdesk(column, issue)
if column.name.eql?(:last_message) && issue.helpdesk_ticket && issue.is_a?(Issue)
content_tag(:span, '', :class => "icon #{issue.helpdesk_ticket.last_message.is_incoming ? 'icon-email' : 'icon-email-to'}") +
link_to(content_tag(:span, content_tag(:small, issue.helpdesk_ticket.last_message.content.truncate(250)), :class => 'description'),
{:controller => 'issues', :action => 'show', :id => issue.id, :anchor => "change-#{issue.helpdesk_ticket.last_message.id}"})
elsif column.name.eql?(:customer) && issue.is_a?(Issue)
issue.customer ? contact_tag(issue.customer, :type => "plain") : ""
elsif column.name.eql?(:customer_company) && issue.is_a?(Issue)
issue.customer ? issue.customer.company : ""
elsif column.name.eql?(:ticket_source) && issue.is_a?(Issue)
issue.helpdesk_ticket ? issue.helpdesk_ticket.ticket_source_name : ""
elsif column.name.eql?(:ticket_reaction_time) && issue.is_a?(Issue)
issue.ticket_reaction_time
elsif column.name.eql?(:ticket_first_response_time) && issue.is_a?(Issue)
issue.ticket_first_response_time
elsif column.name.eql?(:ticket_resolve_time) && issue.is_a?(Issue)
issue.ticket_resolve_time
elsif column.name.eql?(:last_message_date) && issue.is_a?(Issue)
issue.helpdesk_ticket ? l(:label_helpdesk_ago, time_tag(issue.helpdesk_ticket.last_message_date)) : ""
elsif column.name.eql?(:helpdesk_ticket) && issue.customer && issue.is_a?(Issue)
contact_tag(issue.customer, :size => "32", :type => "avatar", :class => "avatar") +
content_tag(:div,
content_tag(:p, link_to(issue.subject, issue_path(issue)), :class => 'ticket-name') +
content_tag(:p, content_tag(:small, issue.description.gsub("(\n|\r)", "").strip.truncate(100)), :class => "ticket-description") +
content_tag(:p, "#{content_tag('span', '', :class => "icon #{helpdesk_ticket_source_icon(issue.helpdesk_ticket)}", :title => l(:label_note_type_email))} #{l(:label_helpdesk_from)}: #{contact_tag(issue.customer, :type => "plain")}, ".html_safe + l(:label_updated_time, time_tag(issue.helpdesk_ticket.last_message_date)).html_safe, :class => "contact-info"),
:class => 'ticket-data')
# content_tag(:div, content_tag(:span, issue.status.name, :class => "tag-label-color status-#{issue.status.id}" , :style => "background-color: #{tag_color(issue.status.name)}"), :class => "ticket-status")
elsif column.name.eql?(:vote) && issue.helpdesk_ticket && issue.is_a?(Issue)
content_tag(:span, HelpdeskTicket.vote_message(issue.helpdesk_ticket.vote)) if issue.helpdesk_ticket.vote
elsif column.name.eql?(:vote_comment) && issue.helpdesk_ticket && issue.is_a?(Issue)
textilizable(issue.helpdesk_ticket.vote_comment.to_s)
else
column_content_without_helpdesk(column, issue)
end
end
end
end
end
end
unless QueriesHelper.included_modules.include?(RedmineHelpdesk::Patches::QueriesHelperPatch)
QueriesHelper.send(:include, RedmineHelpdesk::Patches::QueriesHelperPatch)
end
@@ -0,0 +1,38 @@
module RedmineHelpdesk
module Patches
module TimeReportPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :load_available_criteria, :helpdesk
end
end
module InstanceMethods
def load_available_criteria_with_helpdesk
@available_criteria = load_available_criteria_without_helpdesk
@available_criteria['customer'] = {:sql => "c_helpdesk_tickets.contact_id",
:kclass => Contact,
:joins => "LEFT OUTER JOIN helpdesk_tickets c_helpdesk_tickets ON c_helpdesk_tickets.issue_id = issues.id",
:label => :label_helpdesk_contact} if User.current.allowed_to?(:view_helpdesk_tickets, @project, :global => true)
@available_criteria['helpdesk_contact_company'] = {
:sql => "hcc_contacts.company",
:kclass => Contact,
:joins => "LEFT OUTER JOIN helpdesk_tickets hcc_helpdesk_tickets ON hcc_helpdesk_tickets.issue_id = issues.id LEFT OUTER JOIN contacts hcc_contacts on hcc_helpdesk_tickets.contact_id = hcc_contacts.id",
:label => :label_helpdesk_contact_company} if User.current.allowed_to?(:view_helpdesk_tickets, @project, :global => true)
@available_criteria
end
end
end
end
end
unless Redmine::Helpers::TimeReport.included_modules.include?(RedmineHelpdesk::Patches::TimeReportPatch)
Redmine::Helpers::TimeReport.send(:include, RedmineHelpdesk::Patches::TimeReportPatch)
end
@@ -0,0 +1,26 @@
module RedmineHelpdesk
module WikiMacros
Redmine::WikiFormatting::Macros.register do
desc "Mail icon Macro"
macro :mail do |obj, args|
"<span class=\"icon icon-email\"/>"
end
desc "Helpdesk send_file macro"
macro :send_file do |obj, args|
return "" unless obj.is_a?(Issue) || obj.is_a?(Journal)
issue = obj.is_a?(Journal) ? obj.issue : obj
return "" unless issue.respond_to?(:customer) || (issue.respond_to?(:customer) && issue.customer.blank?)
args, options = extract_macro_options(args, :parent)
raise 'No or bad arguments.' if args.size < 1
attachment = issue.attachments.where(:filename => args.first).first
link_to_attachment attachment if attachment
end
end
end
end
@@ -0,0 +1,51 @@
namespace :redmine do
namespace :plugins do
namespace :helpdesk do
desc <<-END_DESC
Update Helpdesk tickets from issue contacts
Issue attributes control options:
project=PROJECT identifier of the target project
status=STATUS name of the target status
tracker=TRACKER name of the target tracker
category=CATEGORY name of the target category
priority=PRIORITY name of the target priority
Examples:
rake redmine:plugins:helpdesk:update_tickets RAILS_ENV="production" \\
project=foo
END_DESC
task :update_tickets => :environment do
return "project should be selected" unless ENV['project']
project = Project.find(ENV['project'])
issues = project.issues.includes(:contacts).where("contacts.id IS NOT NULL")
issues.each do |issue|
if issue.helpdesk_ticket.blank? && issue.contacts && contact = issue.contacts.first
helpdesk_ticket = HelpdeskTicket.new(:from_address => contact.primary_email,
:to_address => HelpdeskSettings["helpdesk_answer_from", project.id],
:ticket_date => issue.created_on,
:customer => contact,
:issue => issue,
:source => HelpdeskTicket::HELPDESK_EMAIL_SOURCE)
message_file = issue.attachments.where(:filename => 'message.eml').first
helpdesk_ticket.message_file = message_file if message_file
helpdesk_ticket.save
end
end
JournalMessage.where(:message_date => nil).each do |message|
message.message_date = message.journal.created_on if message.journal
message.save
end
Attachment.where(:container_type => 'ContactJournal').update_all(:container_type => 'JournalMessage')
end
end
end
end
@@ -0,0 +1,136 @@
namespace :redmine do
namespace :email do
namespace :helpdesk do
desc <<-END_DESC
Read an email from standard input.
Issue attributes control options:
project=PROJECT identifier of the target project
status=STATUS name of the target status
tracker=TRACKER name of the target tracker
category=CATEGORY name of the target category
priority=PRIORITY name of the target priority
Examples:
rake redmine:email:helpdesk:read RAILS_ENV="production" \\
project=foo \\
tracker=bug < raw_email
END_DESC
task :read => :environment do
options = { :issue => {} }
%w(project project_id status assigned_to tracker category priority due_date).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
options[:reopen_status] = ENV['reopen_status'] if ENV['reopen_status']
options[:issue][:project_id] = options[:issue][:project]
HelpdeskMailer.receive(STDIN.read, options)
end
desc <<-END_DESC
Read emails from an IMAP server.
Available IMAP options:
host=HOST IMAP server host (default: 127.0.0.1)
port=PORT IMAP server port (default: 143)
ssl=SSL Use SSL? (default: false)
username=USERNAME IMAP account
password=PASSWORD IMAP password
folder=FOLDER IMAP folder to read (default: INBOX)
Issue attributes control options:
project_id=PROJECT_ID identifier of the target project
status=STATUS name of the target status
tracker=TRACKER name of the target tracker
category=CATEGORY name of the target category
priority=PRIORITY name of the target priority
reopen_status=STATUS name of the target status afret receive response
allow_override=ATTRS allow email content to override attributes
specified by previous options
ATTRS is a comma separated list of attributes
Processed emails control options:
move_on_success=MAILBOX move emails that were successfully received
to MAILBOX instead of deleting them
move_on_failure=MAILBOX move emails that were ignored to MAILBOX
Examples:
rake redmine:email:helpdesk:receive_imap RAILS_ENV="production" \\
host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\
project=foo \\
tracker=bug
END_DESC
task :receive_imap => :environment do
imap_options = {:host => ENV['host'],
:port => ENV['port'],
:ssl => ENV['ssl'],
:username => ENV['username'],
:password => ENV['password'],
:folder => ENV['folder'],
:move_on_success => ENV['move_on_success'],
:move_on_failure => ENV['move_on_failure']}
options = { :issue => {} }
%w(project_id project status assigned_to tracker category priority due_date).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
options[:reopen_status] = ENV['reopen_status'] if ENV['reopen_status']
options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
options[:issue][:project_id] = options[:issue][:project]
RedmineContacts::Mailer.check_imap(HelpdeskMailer, imap_options, options)
end
desc <<-END_DESC
Read emails from an POP3 server.
Available POP3 options:
host=HOST POP3 server host (default: 127.0.0.1)
port=PORT POP3 server port (default: 110)
username=USERNAME POP3 account
password=PASSWORD POP3 password
apop=1 use APOP authentication (default: false)
delete_unprocessed=1 delete messages that could not be processed
successfully from the server (default
behaviour is to leave them on the server)
See redmine:email:helpdesk:receive_pop3 for more options and examples.
END_DESC
task :receive_pop3 => :environment do
pop_options = {:host => ENV['host'],
:port => ENV['port'],
:apop => ENV['apop'],
:username => ENV['username'],
:password => ENV['password'],
:delete_unprocessed => ENV['delete_unprocessed']}
options = { :issue => {} }
%w(project_id project status assigned_to tracker category priority due_date).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
options[:reopen_status] = ENV['reopen_status'] if ENV['reopen_status']
options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
options[:issue][:project_id] = options[:issue][:project]
RedmineContacts::Mailer.check_pop3(HelpdeskMailer, pop_options, options)
end
desc <<-END_DESC
Receive emails using project settings.
rake redmine:email:helpdesk:receive RAILS_ENV="production"
END_DESC
task :receive => :environment do
Project.active.has_module(:contacts_helpdesk).each do |project|
begin
HelpdeskMailer.check_project(project.id)
rescue Exception => e
puts "Helpdesk MailHandler: can't get mail for project #{project.name} with error: #{e.message}"
end
end
end
end
end
end