Redmine 3.4.4

This commit is contained in:
Manuel Cillero 2018-02-02 22:19:29 +01:00
commit 64924a6376
2112 changed files with 259028 additions and 0 deletions

View file

@ -0,0 +1,21 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module AccountHelper
end

View file

@ -0,0 +1,33 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ActivitiesHelper
def sort_activity_events(events)
events_by_group = events.group_by(&:event_group)
sorted_events = []
events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event|
if group_events = events_by_group.delete(event.event_group)
group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i|
sorted_events << [e, i > 0]
end
end
end
sorted_events
end
end

View file

@ -0,0 +1,35 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module AdminHelper
def project_status_options_for_select(selected)
options_for_select([[l(:label_all), ''],
[l(:project_status_active), '1'],
[l(:project_status_closed), '5'],
[l(:project_status_archived), '9']], selected.to_s)
end
def plugin_data_for_updates(plugins)
data = {"v" => Redmine::VERSION.to_s, "p" => {}}
plugins.each do |plugin|
data["p"].merge! plugin.id => {"v" => plugin.version, "n" => plugin.name, "a" => plugin.author}
end
data
end
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,81 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module AttachmentsHelper
def container_attachments_edit_path(container)
object_attachments_edit_path container.class.name.underscore.pluralize, container.id
end
def container_attachments_path(container)
object_attachments_path container.class.name.underscore.pluralize, container.id
end
# Displays view/delete links to the attachments of the given object
# Options:
# :author -- author names are not displayed if set to false
# :thumbails -- display thumbnails if enabled in settings
def link_to_attachments(container, options = {})
options.assert_valid_keys(:author, :thumbnails)
attachments = if container.attachments.loaded?
container.attachments
else
container.attachments.preload(:author).to_a
end
if attachments.any?
options = {
:editable => container.attachments_editable?,
:deletable => container.attachments_deletable?,
:author => true
}.merge(options)
render :partial => 'attachments/links',
:locals => {
:container => container,
:attachments => attachments,
:options => options,
:thumbnails => (options[:thumbnails] && Setting.thumbnails_enabled?)
}
end
end
def render_api_attachment(attachment, api, options={})
api.attachment do
render_api_attachment_attributes(attachment, api)
options.each { |key, value| eval("api.#{key} value") }
end
end
def render_api_attachment_attributes(attachment, api)
api.id attachment.id
api.filename attachment.filename
api.filesize attachment.filesize
api.content_type attachment.content_type
api.description attachment.description
api.content_url download_named_attachment_url(attachment, attachment.filename)
if attachment.thumbnailable?
api.thumbnail_url thumbnail_url(attachment)
end
if attachment.author
api.author(:id => attachment.author.id, :name => attachment.author.name)
end
api.created_on attachment.created_on
end
end

View file

@ -0,0 +1,24 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module AuthSourcesHelper
def auth_source_partial_name(auth_source)
"form_#{auth_source.class.name.underscore}"
end
end

View file

@ -0,0 +1,41 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module BoardsHelper
def board_breadcrumb(item)
board = item.is_a?(Message) ? item.board : item
links = [link_to(l(:label_board_plural), project_boards_path(item.project))]
boards = board.ancestors.reverse
if item.is_a?(Message)
boards << board
end
links += boards.map {|ancestor| link_to(h(ancestor.name), project_board_path(ancestor.project, ancestor))}
breadcrumb links
end
def boards_options_for_select(boards)
options = []
Board.board_tree(boards) do |board, level|
label = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
label << board.name
options << [label, board.id]
end
options
end
end

View file

@ -0,0 +1,58 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module CalendarsHelper
def link_to_previous_month(year, month, options={})
target_year, target_month = if month == 1
[year - 1, 12]
else
[year, month - 1]
end
name = if target_month == 12
"#{month_name(target_month)} #{target_year}"
else
"#{month_name(target_month)}"
end
# \xc2\xab(utf-8) = &#171;
link_to_month(("\xc2\xab " + name), target_year, target_month, options)
end
def link_to_next_month(year, month, options={})
target_year, target_month = if month == 12
[year + 1, 1]
else
[year, month + 1]
end
name = if target_month == 1
"#{month_name(target_month)} #{target_year}"
else
"#{month_name(target_month)}"
end
# \xc2\xbb(utf-8) = &#187;
link_to_month((name + " \xc2\xbb"), target_year, target_month, options)
end
def link_to_month(link_name, year, month, options={})
link_to(link_name, {:params => request.query_parameters.merge(:year => year, :month => month)}, options)
end
end

View file

@ -0,0 +1,50 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ContextMenusHelper
def context_menu_link(name, url, options={})
options[:class] ||= ''
if options.delete(:selected)
options[:class] << ' icon-checked disabled'
options[:disabled] = true
end
if options.delete(:disabled)
options.delete(:method)
options.delete(:data)
options[:onclick] = 'return false;'
options[:class] << ' disabled'
url = '#'
end
link_to h(name), url, options
end
def bulk_update_custom_field_context_menu_link(field, text, value)
context_menu_link h(text),
bulk_update_issues_path(:ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back),
:method => :post,
:selected => (@issue && @issue.custom_field_value(field) == value)
end
def bulk_update_time_entry_custom_field_context_menu_link(field, text, value)
context_menu_link h(text),
bulk_update_time_entries_path(:ids => @time_entries.map(&:id).sort, :time_entry => {'custom_field_values' => {field.id => value}}, :back_url => @back),
:method => :post,
:selected => (@time_entry && @time_entry.custom_field_value(field) == value)
end
end

View file

@ -0,0 +1,183 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module CustomFieldsHelper
CUSTOM_FIELDS_TABS = [
{:name => 'IssueCustomField', :partial => 'custom_fields/index',
:label => :label_issue_plural},
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
:label => :label_spent_time},
{:name => 'ProjectCustomField', :partial => 'custom_fields/index',
:label => :label_project_plural},
{:name => 'VersionCustomField', :partial => 'custom_fields/index',
:label => :label_version_plural},
{:name => 'DocumentCustomField', :partial => 'custom_fields/index',
:label => :label_document_plural},
{:name => 'UserCustomField', :partial => 'custom_fields/index',
:label => :label_user_plural},
{:name => 'GroupCustomField', :partial => 'custom_fields/index',
:label => :label_group_plural},
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
:label => TimeEntryActivity::OptionName},
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
:label => IssuePriority::OptionName},
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
:label => DocumentCategory::OptionName}
]
def render_custom_fields_tabs(types)
tabs = CUSTOM_FIELDS_TABS.select {|h| types.include?(h[:name]) }
render_tabs tabs
end
def custom_field_type_options
CUSTOM_FIELDS_TABS.map {|h| [l(h[:label]), h[:name]]}
end
def custom_field_title(custom_field)
items = []
items << [l(:label_custom_field_plural), custom_fields_path]
items << [l(custom_field.type_name), custom_fields_path(:tab => custom_field.class.name)] if custom_field
items << (custom_field.nil? || custom_field.new_record? ? l(:label_custom_field_new) : custom_field.name)
title(*items)
end
def render_custom_field_format_partial(form, custom_field)
partial = custom_field.format.form_partial
if partial
render :partial => custom_field.format.form_partial, :locals => {:f => form, :custom_field => custom_field}
end
end
def custom_field_tag_name(prefix, custom_field)
name = "#{prefix}[custom_field_values][#{custom_field.id}]"
name << "[]" if custom_field.multiple?
name
end
def custom_field_tag_id(prefix, custom_field)
"#{prefix}_custom_field_values_#{custom_field.id}"
end
# Return custom field html tag corresponding to its format
def custom_field_tag(prefix, custom_value)
custom_value.custom_field.format.edit_tag self,
custom_field_tag_id(prefix, custom_value.custom_field),
custom_field_tag_name(prefix, custom_value.custom_field),
custom_value,
:class => "#{custom_value.custom_field.field_format}_cf"
end
# Return custom field name tag
def custom_field_name_tag(custom_field)
title = custom_field.description.presence
css = title ? "field-description" : nil
content_tag 'span', custom_field.name, :title => title, :class => css
end
# Return custom field label tag
def custom_field_label_tag(name, custom_value, options={})
required = options[:required] || custom_value.custom_field.is_required?
for_tag_id = options.fetch(:for_tag_id, "#{name}_custom_field_values_#{custom_value.custom_field.id}")
content = custom_field_name_tag custom_value.custom_field
content_tag "label", content +
(required ? " <span class=\"required\">*</span>".html_safe : ""),
:for => for_tag_id
end
# Return custom field tag with its label tag
def custom_field_tag_with_label(name, custom_value, options={})
tag = custom_field_tag(name, custom_value)
tag_id = nil
ids = tag.scan(/ id="(.+?)"/)
if ids.size == 1
tag_id = ids.first.first
end
custom_field_label_tag(name, custom_value, options.merge(:for_tag_id => tag_id)) + tag
end
# Returns the custom field tag for when bulk editing objects
def custom_field_tag_for_bulk_edit(prefix, custom_field, objects=nil, value='')
custom_field.format.bulk_edit_tag self,
custom_field_tag_id(prefix, custom_field),
custom_field_tag_name(prefix, custom_field),
custom_field,
objects,
value,
:class => "#{custom_field.field_format}_cf"
end
# Return a string used to display a custom value
def show_value(custom_value, html=true)
format_object(custom_value, html)
end
# Return a string used to display a custom value
def format_value(value, custom_field)
format_object(custom_field.format.formatted_value(self, custom_field, value, false), false)
end
# Return an array of custom field formats which can be used in select_tag
def custom_field_formats_for_select(custom_field)
Redmine::FieldFormat.as_select(custom_field.class.customized_class.name)
end
# Yields the given block for each custom field value of object that should be
# displayed, with the custom field and the formatted value as arguments
def render_custom_field_values(object, &block)
object.visible_custom_field_values.each do |custom_value|
formatted = show_value(custom_value)
if formatted.present?
yield custom_value.custom_field, formatted
end
end
end
# Renders the custom_values in api views
def render_api_custom_values(custom_values, api)
api.array :custom_fields do
custom_values.each do |custom_value|
attrs = {:id => custom_value.custom_field_id, :name => custom_value.custom_field.name}
attrs.merge!(:multiple => true) if custom_value.custom_field.multiple?
api.custom_field attrs do
if custom_value.value.is_a?(Array)
api.array :value do
custom_value.value.each do |value|
api.value value unless value.blank?
end
end
else
api.value custom_value.value
end
end
end
end unless custom_values.empty?
end
def edit_tag_style_tag(form, options={})
select_options = [[l(:label_drop_down_list), ''], [l(:label_checkboxes), 'check_box']]
if options[:include_radio]
select_options << [l(:label_radio_buttons), 'radio']
end
form.select :edit_tag_style, select_options, :label => :label_display
end
end

View file

@ -0,0 +1,21 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module DocumentsHelper
end

View file

@ -0,0 +1,38 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module EmailAddressesHelper
# Returns a link to enable or disable notifications for the address
def toggle_email_address_notify_link(address)
if address.notify?
link_to l(:label_disable_notifications),
user_email_address_path(address.user, address, :notify => '0'),
:method => :put, :remote => true,
:title => l(:label_disable_notifications),
:class => 'icon-only icon-email'
else
link_to l(:label_enable_notifications),
user_email_address_path(address.user, address, :notify => '1'),
:method => :put, :remote => true,
:title => l(:label_enable_notifications),
:class => 'icon-only icon-email-disabled'
end
end
end

View file

@ -0,0 +1,21 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module EnumerationsHelper
end

View file

@ -0,0 +1,43 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module GanttHelper
def gantt_zoom_link(gantt, in_or_out)
case in_or_out
when :in
if gantt.zoom < 4
link_to l(:text_zoom_in),
{:params => request.query_parameters.merge(gantt.params.merge(:zoom => (gantt.zoom + 1)))},
:class => 'icon icon-zoom-in'
else
content_tag(:span, l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe
end
when :out
if gantt.zoom > 1
link_to l(:text_zoom_out),
{:params => request.query_parameters.merge(gantt.params.merge(:zoom => (gantt.zoom - 1)))},
:class => 'icon icon-zoom-out'
else
content_tag(:span, l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe
end
end
end
end

View file

@ -0,0 +1,46 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module GroupsHelper
def group_settings_tabs(group)
tabs = []
tabs << {:name => 'general', :partial => 'groups/general', :label => :label_general}
tabs << {:name => 'users', :partial => 'groups/users', :label => :label_user_plural} if group.givable?
tabs << {:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural}
tabs
end
def render_principals_for_new_group_users(group, limit=100)
scope = User.active.sorted.not_in_group(group).like(params[:q])
principal_count = scope.count
principal_pages = Redmine::Pagination::Paginator.new principal_count, limit, params['page']
principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).to_a
s = content_tag('div',
content_tag('div', principals_check_box_tags('user_ids[]', principals), :id => 'principals'),
:class => 'objects-selection'
)
links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
link_to text, autocomplete_for_user_group_path(group, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
}
s + content_tag('span', links, :class => 'pagination')
end
end

View file

@ -0,0 +1,47 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ImportsHelper
def options_for_mapping_select(import, field, options={})
tags = "".html_safe
blank_text = options[:required] ? "-- #{l(:actionview_instancetag_blank_option)} --" : "&nbsp;".html_safe
tags << content_tag('option', blank_text, :value => '')
tags << options_for_select(import.columns_options, import.mapping[field])
if values = options[:values]
tags << content_tag('option', '--', :disabled => true)
tags << options_for_select(values.map {|text, value| [text, "value:#{value}"]}, import.mapping[field])
end
tags
end
def mapping_select_tag(import, field, options={})
name = "import_settings[mapping][#{field}]"
select_tag name, options_for_mapping_select(import, field, options), :id => "import_mapping_#{field}"
end
# Returns the options for the date_format setting
def date_format_options
Import::DATE_FORMATS.map do |f|
format = f.gsub('%', '').gsub(/[dmY]/) do
{'d' => 'DD', 'm' => 'MM', 'Y' => 'YYYY'}[$&]
end
[format, f]
end
end
end

View file

@ -0,0 +1,21 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module IssueCategoriesHelper
end

View file

@ -0,0 +1,25 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module IssueRelationsHelper
def collection_for_relation_type_select
values = IssueRelation::TYPES
values.keys.sort{|x,y| values[x][:order] <=> values[y][:order]}.collect{|k| [l(values[k][:name]), k]}
end
end

View file

@ -0,0 +1,21 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module IssueStatusesHelper
end

View file

@ -0,0 +1,542 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module IssuesHelper
include ApplicationHelper
include Redmine::Export::PDF::IssuesPdfHelper
def issue_list(issues, &block)
ancestors = []
issues.each do |issue|
while (ancestors.any? && !issue.is_descendant_of?(ancestors.last))
ancestors.pop
end
yield issue, ancestors.size
ancestors << issue unless issue.leaf?
end
end
def grouped_issue_list(issues, query, &block)
ancestors = []
grouped_query_results(issues, query) do |issue, group_name, group_count, group_totals|
while (ancestors.any? && !issue.is_descendant_of?(ancestors.last))
ancestors.pop
end
yield issue, ancestors.size, group_name, group_count, group_totals
ancestors << issue unless issue.leaf?
end
end
# Renders a HTML/CSS tooltip
#
# To use, a trigger div is needed. This is a div with the class of "tooltip"
# that contains this method wrapped in a span with the class of "tip"
#
# <div class="tooltip"><%= link_to_issue(issue) %>
# <span class="tip"><%= render_issue_tooltip(issue) %></span>
# </div>
#
def render_issue_tooltip(issue)
@cached_label_status ||= l(:field_status)
@cached_label_start_date ||= l(:field_start_date)
@cached_label_due_date ||= l(:field_due_date)
@cached_label_assigned_to ||= l(:field_assigned_to)
@cached_label_priority ||= l(:field_priority)
@cached_label_project ||= l(:field_project)
link_to_issue(issue) + "<br /><br />".html_safe +
"<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe +
"<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe +
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe +
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe +
"<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe +
"<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe
end
def issue_heading(issue)
h("#{issue.tracker} ##{issue.id}")
end
def render_issue_subject_with_tree(issue)
s = ''
ancestors = issue.root? ? [] : issue.ancestors.visible.to_a
ancestors.each do |ancestor|
s << '<div>' + content_tag('p', link_to_issue(ancestor, :project => (issue.project_id != ancestor.project_id)))
end
s << '<div>'
subject = h(issue.subject)
if issue.is_private?
subject = content_tag('span', l(:field_is_private), :class => 'private') + ' ' + subject
end
s << content_tag('h3', subject)
s << '</div>' * (ancestors.size + 1)
s.html_safe
end
def render_descendants_tree(issue)
s = '<table class="list issues odd-even">'
issue_list(issue.descendants.visible.preload(:status, :priority, :tracker, :assigned_to).sort_by(&:lft)) do |child, level|
css = "issue issue-#{child.id} hascontextmenu #{child.css_classes}"
css << " idnt idnt-#{level}" if level > 0
s << content_tag('tr',
content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
content_tag('td', link_to_issue(child, :project => (issue.project_id != child.project_id)), :class => 'subject', :style => 'width: 50%') +
content_tag('td', h(child.status), :class => 'status') +
content_tag('td', link_to_user(child.assigned_to), :class => 'assigned_to') +
content_tag('td', child.disabled_core_fields.include?('done_ratio') ? '' : progress_bar(child.done_ratio), :class=> 'done_ratio'),
:class => css)
end
s << '</table>'
s.html_safe
end
# Renders the list of related issues on the issue details view
def render_issue_relations(issue, relations)
manage_relations = User.current.allowed_to?(:manage_issue_relations, issue.project)
s = ''.html_safe
relations.each do |relation|
other_issue = relation.other_issue(issue)
css = "issue hascontextmenu #{other_issue.css_classes}"
link = manage_relations ? link_to(l(:label_relation_delete),
relation_path(relation),
:remote => true,
:method => :delete,
:data => {:confirm => l(:text_are_you_sure)},
:title => l(:label_relation_delete),
:class => 'icon-only icon-link-break'
) : nil
s << content_tag('tr',
content_tag('td', check_box_tag("ids[]", other_issue.id, false, :id => nil), :class => 'checkbox') +
content_tag('td', relation.to_s(@issue) {|other| link_to_issue(other, :project => Setting.cross_project_issue_relations?)}.html_safe, :class => 'subject', :style => 'width: 50%') +
content_tag('td', other_issue.status, :class => 'status') +
content_tag('td', other_issue.start_date, :class => 'start_date') +
content_tag('td', other_issue.due_date, :class => 'due_date') +
content_tag('td', other_issue.disabled_core_fields.include?('done_ratio') ? '' : progress_bar(other_issue.done_ratio), :class=> 'done_ratio') +
content_tag('td', link, :class => 'buttons'),
:id => "relation-#{relation.id}",
:class => css)
end
content_tag('table', s, :class => 'list issues odd-even')
end
def issue_estimated_hours_details(issue)
if issue.total_estimated_hours.present?
if issue.total_estimated_hours == issue.estimated_hours
l_hours_short(issue.estimated_hours)
else
s = issue.estimated_hours.present? ? l_hours_short(issue.estimated_hours) : ""
s << " (#{l(:label_total)}: #{l_hours_short(issue.total_estimated_hours)})"
s.html_safe
end
end
end
def issue_spent_hours_details(issue)
if issue.total_spent_hours > 0
path = project_time_entries_path(issue.project, :issue_id => "~#{issue.id}")
if issue.total_spent_hours == issue.spent_hours
link_to(l_hours_short(issue.spent_hours), path)
else
s = issue.spent_hours > 0 ? l_hours_short(issue.spent_hours) : ""
s << " (#{l(:label_total)}: #{link_to l_hours_short(issue.total_spent_hours), path})"
s.html_safe
end
end
end
# Returns an array of error messages for bulk edited issues
def bulk_edit_error_messages(issues)
messages = {}
issues.each do |issue|
issue.errors.full_messages.each do |message|
messages[message] ||= []
messages[message] << issue
end
end
messages.map { |message, issues|
"#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
}
end
# Returns a link for adding a new subtask to the given issue
def link_to_new_subtask(issue)
attrs = {
:parent_issue_id => issue
}
attrs[:tracker_id] = issue.tracker unless issue.tracker.disabled_core_fields.include?('parent_issue_id')
link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs, :back_url => issue_path(issue)))
end
def trackers_options_for_select(issue)
trackers = issue.allowed_target_trackers
if issue.new_record? && issue.parent_issue_id.present?
trackers = trackers.reject do |tracker|
issue.tracker_id != tracker.id && tracker.disabled_core_fields.include?('parent_issue_id')
end
end
trackers.collect {|t| [t.name, t.id]}
end
class IssueFieldsRows
include ActionView::Helpers::TagHelper
def initialize
@left = []
@right = []
end
def left(*args)
args.any? ? @left << cells(*args) : @left
end
def right(*args)
args.any? ? @right << cells(*args) : @right
end
def size
@left.size > @right.size ? @left.size : @right.size
end
def to_html
content =
content_tag('div', @left.reduce(&:+), :class => 'splitcontentleft') +
content_tag('div', @right.reduce(&:+), :class => 'splitcontentleft')
content_tag('div', content, :class => 'splitcontent')
end
def cells(label, text, options={})
options[:class] = [options[:class] || "", 'attribute'].join(' ')
content_tag 'div',
content_tag('div', label + ":", :class => 'label') + content_tag('div', text, :class => 'value'),
options
end
end
def issue_fields_rows
r = IssueFieldsRows.new
yield r
r.to_html
end
def render_half_width_custom_fields_rows(issue)
values = issue.visible_custom_field_values.reject {|value| value.custom_field.full_width_layout?}
return if values.empty?
half = (values.size / 2.0).ceil
issue_fields_rows do |rows|
values.each_with_index do |value, i|
css = "cf_#{value.custom_field.id}"
m = (i < half ? :left : :right)
rows.send m, custom_field_name_tag(value.custom_field), show_value(value), :class => css
end
end
end
def render_full_width_custom_fields_rows(issue)
values = issue.visible_custom_field_values.select {|value| value.custom_field.full_width_layout?}
return if values.empty?
s = ''.html_safe
values.each_with_index do |value, i|
attr_value = show_value(value)
next if attr_value.blank?
if value.custom_field.text_formatting == 'full'
attr_value = content_tag('div', attr_value, class: 'wiki')
end
content =
content_tag('hr') +
content_tag('p', content_tag('strong', custom_field_name_tag(value.custom_field) )) +
content_tag('div', attr_value, class: 'value')
s << content_tag('div', content, class: "cf_#{value.custom_field.id} attribute")
end
s
end
# Returns the path for updating the issue form
# with project as the current project
def update_issue_form_path(project, issue)
options = {:format => 'js'}
if issue.new_record?
if project
new_project_issue_path(project, options)
else
new_issue_path(options)
end
else
edit_issue_path(issue, options)
end
end
# Returns the number of descendants for an array of issues
def issues_descendant_count(issues)
ids = issues.reject(&:leaf?).map {|issue| issue.descendants.ids}.flatten.uniq
ids -= issues.map(&:id)
ids.size
end
def issues_destroy_confirmation_message(issues)
issues = [issues] unless issues.is_a?(Array)
message = l(:text_issues_destroy_confirmation)
descendant_count = issues_descendant_count(issues)
if descendant_count > 0
message << "\n" + l(:text_issues_destroy_descendants_confirmation, :count => descendant_count)
end
message
end
# Returns an array of users that are proposed as watchers
# on the new issue form
def users_for_new_issue_watchers(issue)
users = issue.watcher_users
if issue.project.users.count <= 20
users = (users + issue.project.users.sort).uniq
end
users
end
def email_issue_attributes(issue, user, html)
items = []
%w(author status priority assigned_to category fixed_version).each do |attribute|
unless issue.disabled_core_fields.include?(attribute+"_id")
if html
items << content_tag('strong', "#{l("field_#{attribute}")}: ") + (issue.send attribute)
else
items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
end
end
end
issue.visible_custom_field_values(user).each do |value|
if html
items << content_tag('strong', "#{value.custom_field.name}: ") + show_value(value, false)
else
items << "#{value.custom_field.name}: #{show_value(value, false)}"
end
end
items
end
def render_email_issue_attributes(issue, user, html=false)
items = email_issue_attributes(issue, user, html)
if html
content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe, :class => "details")
else
items.map{|s| "* #{s}"}.join("\n")
end
end
# Returns the textual representation of a journal details
# as an array of strings
def details_to_strings(details, no_html=false, options={})
options[:only_path] = (options[:only_path] == false ? false : true)
strings = []
values_by_field = {}
details.each do |detail|
if detail.property == 'cf'
field = detail.custom_field
if field && field.multiple?
values_by_field[field] ||= {:added => [], :deleted => []}
if detail.old_value
values_by_field[field][:deleted] << detail.old_value
end
if detail.value
values_by_field[field][:added] << detail.value
end
next
end
end
strings << show_detail(detail, no_html, options)
end
if values_by_field.present?
multiple_values_detail = Struct.new(:property, :prop_key, :custom_field, :old_value, :value)
values_by_field.each do |field, changes|
if changes[:added].any?
detail = multiple_values_detail.new('cf', field.id.to_s, field)
detail.value = changes[:added]
strings << show_detail(detail, no_html, options)
end
if changes[:deleted].any?
detail = multiple_values_detail.new('cf', field.id.to_s, field)
detail.old_value = changes[:deleted]
strings << show_detail(detail, no_html, options)
end
end
end
strings
end
# Returns the textual representation of a single journal detail
def show_detail(detail, no_html=false, options={})
multiple = false
show_diff = false
no_details = false
case detail.property
when 'attr'
field = detail.prop_key.to_s.gsub(/\_id$/, "")
label = l(("field_" + field).to_sym)
case detail.prop_key
when 'due_date', 'start_date'
value = format_date(detail.value.to_date) if detail.value
old_value = format_date(detail.old_value.to_date) if detail.old_value
when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id',
'priority_id', 'category_id', 'fixed_version_id'
value = find_name_by_reflection(field, detail.value)
old_value = find_name_by_reflection(field, detail.old_value)
when 'estimated_hours'
value = l_hours_short(detail.value.to_f) unless detail.value.blank?
old_value = l_hours_short(detail.old_value.to_f) unless detail.old_value.blank?
when 'parent_id'
label = l(:field_parent_issue)
value = "##{detail.value}" unless detail.value.blank?
old_value = "##{detail.old_value}" unless detail.old_value.blank?
when 'is_private'
value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
when 'description'
show_diff = true
end
when 'cf'
custom_field = detail.custom_field
if custom_field
label = custom_field.name
if custom_field.format.class.change_no_details
no_details = true
elsif custom_field.format.class.change_as_diff
show_diff = true
else
multiple = custom_field.multiple?
value = format_value(detail.value, custom_field) if detail.value
old_value = format_value(detail.old_value, custom_field) if detail.old_value
end
end
when 'attachment'
label = l(:label_attachment)
when 'relation'
if detail.value && !detail.old_value
rel_issue = Issue.visible.find_by_id(detail.value)
value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" :
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
elsif detail.old_value && !detail.value
rel_issue = Issue.visible.find_by_id(detail.old_value)
old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" :
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
end
relation_type = IssueRelation::TYPES[detail.prop_key]
label = l(relation_type[:name]) if relation_type
end
call_hook(:helper_issues_show_detail_after_setting,
{:detail => detail, :label => label, :value => value, :old_value => old_value })
label ||= detail.prop_key
value ||= detail.value
old_value ||= detail.old_value
unless no_html
label = content_tag('strong', label)
old_value = content_tag("i", h(old_value)) if detail.old_value
if detail.old_value && detail.value.blank? && detail.property != 'relation'
old_value = content_tag("del", old_value)
end
if detail.property == 'attachment' && value.present? &&
atta = detail.journal.journalized.attachments.detect {|a| a.id == detail.prop_key.to_i}
# Link to the attachment if it has not been removed
value = link_to_attachment(atta, only_path: options[:only_path])
if options[:only_path] != false
value += ' '
value += link_to_attachment atta, class: 'icon-only icon-download', title: l(:button_download), download: true
end
else
value = content_tag("i", h(value)) if value
end
end
if no_details
s = l(:text_journal_changed_no_detail, :label => label).html_safe
elsif show_diff
s = l(:text_journal_changed_no_detail, :label => label)
unless no_html
diff_link = link_to 'diff',
diff_journal_url(detail.journal_id, :detail_id => detail.id, :only_path => options[:only_path]),
:title => l(:label_view_diff)
s << " (#{ diff_link })"
end
s.html_safe
elsif detail.value.present?
case detail.property
when 'attr', 'cf'
if detail.old_value.present?
l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe
elsif multiple
l(:text_journal_added, :label => label, :value => value).html_safe
else
l(:text_journal_set_to, :label => label, :value => value).html_safe
end
when 'attachment', 'relation'
l(:text_journal_added, :label => label, :value => value).html_safe
end
else
l(:text_journal_deleted, :label => label, :old => old_value).html_safe
end
end
# Find the name of an associated record stored in the field attribute
def find_name_by_reflection(field, id)
unless id.present?
return nil
end
@detail_value_name_by_reflection ||= Hash.new do |hash, key|
association = Issue.reflect_on_association(key.first.to_sym)
name = nil
if association
record = association.klass.find_by_id(key.last)
if record
name = record.name.force_encoding('UTF-8')
end
end
hash[key] = name
end
@detail_value_name_by_reflection[[field, id]]
end
# Renders issue children recursively
def render_api_issue_children(issue, api)
return if issue.leaf?
api.array :children do
issue.children.each do |child|
api.issue(:id => child.id) do
api.tracker(:id => child.tracker_id, :name => child.tracker.name) unless child.tracker.nil?
api.subject child.subject
render_api_issue_children(child, api)
end
end
end
end
end

View file

@ -0,0 +1,69 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module JournalsHelper
# Returns the attachments of a journal that are displayed as thumbnails
def journal_thumbnail_attachments(journal)
ids = journal.details.select {|d| d.property == 'attachment' && d.value.present?}.map(&:prop_key)
ids.any? ? Attachment.where(:id => ids).select(&:thumbnailable?) : []
end
def render_notes(issue, journal, options={})
content = ''
css_classes = "wiki"
links = []
if journal.notes.present?
links << link_to(l(:button_quote),
quoted_issue_path(issue, :journal_id => journal),
:remote => true,
:method => 'post',
:title => l(:button_quote),
:class => 'icon-only icon-comment'
) if options[:reply_links]
if journal.editable_by?(User.current)
links << link_to(l(:button_edit),
edit_journal_path(journal),
:remote => true,
:method => 'get',
:title => l(:button_edit),
:class => 'icon-only icon-edit'
)
links << link_to(l(:button_delete),
journal_path(journal, :journal => {:notes => ""}),
:remote => true,
:method => 'put', :data => {:confirm => l(:text_are_you_sure)},
:title => l(:button_delete),
:class => 'icon-only icon-del'
)
css_classes << " editable"
end
end
content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty?
content << textilizable(journal, :notes)
content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes)
end
def render_private_notes_indicator(journal)
content = journal.private_notes? ? l(:field_is_private) : ''
css_classes = journal.private_notes? ? 'private' : ''
content_tag('span', content.html_safe, :id => "journal-#{journal.id}-private_notes", :class => css_classes)
end
end

View file

@ -0,0 +1,21 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MailHandlerHelper
end

View file

@ -0,0 +1,38 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MembersHelper
def render_principals_for_new_members(project, limit=100)
scope = Principal.active.visible.sorted.not_member_of(project).like(params[:q])
principal_count = scope.count
principal_pages = Redmine::Pagination::Paginator.new principal_count, limit, params['page']
principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).to_a
s = content_tag('div',
content_tag('div', principals_check_box_tags('membership[user_ids][]', principals), :id => 'principals'),
:class => 'objects-selection'
)
links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
}
s + content_tag('span', links, :class => 'pagination')
end
end

View file

@ -0,0 +1,21 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MessagesHelper
end

167
app/helpers/my_helper.rb Normal file
View file

@ -0,0 +1,167 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MyHelper
# Renders the blocks
def render_blocks(blocks, user, options={})
s = ''.html_safe
if blocks.present?
blocks.each do |block|
s << render_block(block, user).to_s
end
end
s
end
# Renders a single block
def render_block(block, user)
content = render_block_content(block, user)
if content.present?
handle = content_tag('span', '', :class => 'sort-handle', :title => l(:button_move))
close = link_to(l(:button_delete),
{:action => "remove_block", :block => block},
:remote => true, :method => 'post',
:class => "icon-only icon-close", :title => l(:button_delete))
content = content_tag('div', handle + close, :class => 'contextual') + content
content_tag('div', content, :class => "mypage-box", :id => "block-#{block}")
end
end
# Renders a single block content
def render_block_content(block, user)
unless block_definition = Redmine::MyPage.find_block(block)
Rails.logger.warn("Unknown block \"#{block}\" found in #{user.login} (id=#{user.id}) preferences")
return
end
settings = user.pref.my_page_settings(block)
if partial = block_definition[:partial]
begin
render(:partial => partial, :locals => {:user => user, :settings => settings, :block => block})
rescue ActionView::MissingTemplate
Rails.logger.warn("Partial \"#{partial}\" missing for block \"#{block}\" found in #{user.login} (id=#{user.id}) preferences")
return nil
end
else
send "render_#{block_definition[:name]}_block", block, settings
end
end
# Returns the select tag used to add a block to My page
def block_select_tag(user)
blocks_in_use = user.pref.my_page_layout.values.flatten
options = content_tag('option')
Redmine::MyPage.block_options(blocks_in_use).each do |label, block|
options << content_tag('option', label, :value => block, :disabled => block.blank?)
end
select_tag('block', options, :id => "block-select", :onchange => "$('#block-form').submit();")
end
def render_calendar_block(block, settings)
calendar = Redmine::Helpers::Calendar.new(User.current.today, current_language, :week)
calendar.events = Issue.visible.
where(:project_id => User.current.projects.map(&:id)).
where("(start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)", calendar.startdt, calendar.enddt, calendar.startdt, calendar.enddt).
includes(:project, :tracker, :priority, :assigned_to).
references(:project, :tracker, :priority, :assigned_to).
to_a
render :partial => 'my/blocks/calendar', :locals => {:calendar => calendar, :block => block}
end
def render_documents_block(block, settings)
documents = Document.visible.order("#{Document.table_name}.created_on DESC").limit(10).to_a
render :partial => 'my/blocks/documents', :locals => {:block => block, :documents => documents}
end
def render_issuesassignedtome_block(block, settings)
query = IssueQuery.new(:name => l(:label_assigned_to_me_issues), :user => User.current)
query.add_filter 'assigned_to_id', '=', ['me']
query.column_names = settings[:columns].presence || ['project', 'tracker', 'status', 'subject']
query.sort_criteria = settings[:sort].presence || [['priority', 'desc'], ['updated_on', 'desc']]
issues = query.issues(:limit => 10)
render :partial => 'my/blocks/issues', :locals => {:query => query, :issues => issues, :block => block}
end
def render_issuesreportedbyme_block(block, settings)
query = IssueQuery.new(:name => l(:label_reported_issues), :user => User.current)
query.add_filter 'author_id', '=', ['me']
query.column_names = settings[:columns].presence || ['project', 'tracker', 'status', 'subject']
query.sort_criteria = settings[:sort].presence || [['updated_on', 'desc']]
issues = query.issues(:limit => 10)
render :partial => 'my/blocks/issues', :locals => {:query => query, :issues => issues, :block => block}
end
def render_issueswatched_block(block, settings)
query = IssueQuery.new(:name => l(:label_watched_issues), :user => User.current)
query.add_filter 'watcher_id', '=', ['me']
query.column_names = settings[:columns].presence || ['project', 'tracker', 'status', 'subject']
query.sort_criteria = settings[:sort].presence || [['updated_on', 'desc']]
issues = query.issues(:limit => 10)
render :partial => 'my/blocks/issues', :locals => {:query => query, :issues => issues, :block => block}
end
def render_issuequery_block(block, settings)
query = IssueQuery.visible.find_by_id(settings[:query_id])
if query
query.column_names = settings[:columns] if settings[:columns].present?
query.sort_criteria = settings[:sort] if settings[:sort].present?
issues = query.issues(:limit => 10)
render :partial => 'my/blocks/issues', :locals => {:query => query, :issues => issues, :block => block, :settings => settings}
else
queries = IssueQuery.visible.sorted
render :partial => 'my/blocks/issue_query_selection', :locals => {:queries => queries, :block => block, :settings => settings}
end
end
def render_news_block(block, settings)
news = News.visible.
where(:project_id => User.current.projects.map(&:id)).
limit(10).
includes(:project, :author).
references(:project, :author).
order("#{News.table_name}.created_on DESC").
to_a
render :partial => 'my/blocks/news', :locals => {:block => block, :news => news}
end
def render_timelog_block(block, settings)
days = settings[:days].to_i
days = 7 if days < 1 || days > 365
entries = TimeEntry.
where("#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", User.current.id, User.current.today - (days - 1), User.current.today).
joins(:activity, :project).
references(:issue => [:tracker, :status]).
includes(:issue => [:tracker, :status]).
order("#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC").
to_a
entries_by_day = entries.group_by(&:spent_on)
render :partial => 'my/blocks/timelog', :locals => {:block => block, :entries => entries, :entries_by_day => entries_by_day, :days => days}
end
end

View file

@ -0,0 +1,21 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module NewsHelper
end

View file

@ -0,0 +1,64 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module PrincipalMembershipsHelper
def render_principal_memberships(principal)
render :partial => 'principal_memberships/index', :locals => {:principal => principal}
end
def call_table_header_hook(principal)
if principal.is_a?(Group)
call_hook :view_groups_memberships_table_header, :group => principal
else
call_hook :view_users_memberships_table_header, :user => principal
end
end
def call_table_row_hook(principal, membership)
if principal.is_a?(Group)
call_hook :view_groups_memberships_table_row, :group => principal, :membership => membership
else
call_hook :view_users_memberships_table_row, :user => principal, :membership => membership
end
end
def new_principal_membership_path(principal, *args)
if principal.is_a?(Group)
new_group_membership_path(principal, *args)
else
new_user_membership_path(principal, *args)
end
end
def edit_principal_membership_path(principal, *args)
if principal.is_a?(Group)
edit_group_membership_path(principal, *args)
else
edit_user_membership_path(principal, *args)
end
end
def principal_membership_path(principal, membership, *args)
if principal.is_a?(Group)
group_membership_path(principal, membership, *args)
else
user_membership_path(principal, membership, *args)
end
end
end

View file

@ -0,0 +1,140 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ProjectsHelper
def project_settings_tabs
tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
{:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
{:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
{:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural,
:url => {:tab => 'versions', :version_status => params[:version_status], :version_name => params[:version_name]}},
{:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
{:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
{:name => 'repositories', :action => :manage_repository, :partial => 'projects/settings/repositories', :label => :label_repository_plural},
{:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural},
{:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
]
tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
end
def parent_project_select_tag(project)
selected = project.parent
# retrieve the requested parent project
parent_id = (params[:project] && params[:project][:parent_id]) || params[:parent_id]
if parent_id
selected = (parent_id.blank? ? nil : Project.find(parent_id))
end
options = ''
options << "<option value=''>&nbsp;</option>" if project.allowed_parents.include?(nil)
options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id')
end
def render_project_action_links
links = "".html_safe
if User.current.allowed_to?(:add_project, nil, :global => true)
links << link_to(l(:label_project_new), new_project_path, :class => 'icon icon-add')
end
links
end
# Renders the projects index
def render_project_hierarchy(projects)
render_project_nested_lists(projects) do |project|
s = link_to_project(project, {}, :class => "#{project.css_classes} #{User.current.member_of?(project) ? 'icon icon-fav my-project' : nil}")
if project.description.present?
s << content_tag('div', textilizable(project.short_description, :project => project), :class => 'wiki description')
end
s
end
end
# Returns a set of options for a select field, grouped by project.
def version_options_for_select(versions, selected=nil)
grouped = Hash.new {|h,k| h[k] = []}
versions.each do |version|
grouped[version.project.name] << [version.name, version.id]
end
selected = selected.is_a?(Version) ? selected.id : selected
if grouped.keys.size > 1
grouped_options_for_select(grouped, selected)
else
options_for_select((grouped.values.first || []), selected)
end
end
def project_default_version_options(project)
versions = project.shared_versions.open.to_a
if project.default_version && !versions.include?(project.default_version)
versions << project.default_version
end
version_options_for_select(versions, project.default_version)
end
def project_default_assigned_to_options(project)
assignable_users = (project.assignable_users.to_a + [project.default_assigned_to]).uniq.compact
principals_options_for_select(assignable_users, project.default_assigned_to)
end
def format_version_sharing(sharing)
sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing)
l("label_version_sharing_#{sharing}")
end
def render_boards_tree(boards, parent=nil, level=0, &block)
selection = boards.select {|b| b.parent == parent}
return '' if selection.empty?
s = ''.html_safe
selection.each do |board|
node = capture(board, level, &block)
node << render_boards_tree(boards, board, level+1, &block)
s << content_tag('div', node)
end
content_tag('div', s, :class => 'sort-level')
end
def render_api_includes(project, api)
api.array :trackers do
project.trackers.each do |tracker|
api.tracker(:id => tracker.id, :name => tracker.name)
end
end if include_in_api_response?('trackers')
api.array :issue_categories do
project.issue_categories.each do |category|
api.issue_category(:id => category.id, :name => category.name)
end
end if include_in_api_response?('issue_categories')
api.array :time_entry_activities do
project.activities.each do |activity|
api.time_entry_activity(:id => activity.id, :name => activity.name)
end
end if include_in_api_response?('time_entry_activities')
api.array :enabled_modules do
project.enabled_modules.each do |enabled_module|
api.enabled_module(:id => enabled_module.id, :name => enabled_module.name)
end
end if include_in_api_response?('enabled_modules')
end
end

View file

@ -0,0 +1,406 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module QueriesHelper
include ApplicationHelper
def filters_options_for_select(query)
ungrouped = []
grouped = {}
query.available_filters.map do |field, field_options|
if field_options[:type] == :relation
group = :label_relations
elsif field_options[:type] == :tree
group = query.is_a?(IssueQuery) ? :label_relations : nil
elsif field =~ /^cf_\d+\./
group = (field_options[:through] || field_options[:field]).try(:name)
elsif field =~ /^(.+)\./
# association filters
group = "field_#{$1}".to_sym
elsif %w(member_of_group assigned_to_role).include?(field)
group = :field_assigned_to
elsif field_options[:type] == :date_past || field_options[:type] == :date
group = :label_date
end
if group
(grouped[group] ||= []) << [field_options[:name], field]
else
ungrouped << [field_options[:name], field]
end
end
# Don't group dates if there's only one (eg. time entries filters)
if grouped[:label_date].try(:size) == 1
ungrouped << grouped.delete(:label_date).first
end
s = options_for_select([[]] + ungrouped)
if grouped.present?
localized_grouped = grouped.map {|k,v| [k.is_a?(Symbol) ? l(k) : k.to_s, v]}
s << grouped_options_for_select(localized_grouped)
end
s
end
def query_filters_hidden_tags(query)
tags = ''.html_safe
query.filters.each do |field, options|
tags << hidden_field_tag("f[]", field, :id => nil)
tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
options[:values].each do |value|
tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
end
end
tags
end
def query_columns_hidden_tags(query)
tags = ''.html_safe
query.columns.each do |column|
tags << hidden_field_tag("c[]", column.name, :id => nil)
end
tags
end
def query_hidden_tags(query)
query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
end
def group_by_column_select_tag(query)
options = [[]] + query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}
select_tag('group_by', options_for_select(options, @query.group_by))
end
def available_block_columns_tags(query)
tags = ''.html_safe
query.available_block_columns.each do |column|
tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
end
tags
end
def available_totalable_columns_tags(query)
tags = ''.html_safe
query.available_totalable_columns.each do |column|
tags << content_tag('label', check_box_tag('t[]', column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
end
tags << hidden_field_tag('t[]', '')
tags
end
def query_available_inline_columns_options(query)
(query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
end
def query_selected_inline_columns_options(query)
(query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
end
def render_query_columns_selection(query, options={})
tag_name = (options[:name] || 'c') + '[]'
render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
end
def grouped_query_results(items, query, &block)
result_count_by_group = query.result_count_by_group
previous_group, first = false, true
totals_by_group = query.totalable_columns.inject({}) do |h, column|
h[column] = query.total_by_group_for(column)
h
end
items.each do |item|
group_name = group_count = nil
if query.grouped?
group = query.group_by_column.value(item)
if first || group != previous_group
if group.blank? && group != false
group_name = "(#{l(:label_blank_value)})"
else
group_name = format_object(group)
end
group_name ||= ""
group_count = result_count_by_group ? result_count_by_group[group] : nil
group_totals = totals_by_group.map {|column, t| total_tag(column, t[group] || 0)}.join(" ").html_safe
end
end
yield item, group_name, group_count, group_totals
previous_group, first = group, false
end
end
def render_query_totals(query)
return unless query.totalable_columns.present?
totals = query.totalable_columns.map do |column|
total_tag(column, query.total_for(column))
end
content_tag('p', totals.join(" ").html_safe, :class => "query-totals")
end
def total_tag(column, value)
label = content_tag('span', "#{column.caption}:")
value = if [:hours, :spent_hours, :total_spent_hours, :estimated_hours].include? column.name
format_hours(value)
else
format_object(value)
end
value = content_tag('span', value, :class => 'value')
content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
end
def column_header(query, column, options={})
if column.sortable?
css, order = nil, column.default_order
if column.name.to_s == query.sort_criteria.first_key
if query.sort_criteria.first_asc?
css = 'sort asc'
order = 'desc'
else
css = 'sort desc'
order = 'asc'
end
end
param_key = options[:sort_param] || :sort
sort_param = { param_key => query.sort_criteria.add(column.name, order).to_param }
while sort_param.keys.first.to_s =~ /^(.+)\[(.+)\]$/
sort_param = {$1 => {$2 => sort_param.values.first}}
end
link_options = {
:title => l(:label_sort_by, "\"#{column.caption}\""),
:class => css
}
if options[:sort_link_options]
link_options.merge! options[:sort_link_options]
end
content = link_to(column.caption,
{:params => request.query_parameters.deep_merge(sort_param)},
link_options
)
else
content = column.caption
end
content_tag('th', content)
end
def column_content(column, item)
value = column.value_object(item)
if value.is_a?(Array)
values = value.collect {|v| column_value(column, item, v)}.compact
safe_join(values, ', ')
else
column_value(column, item, value)
end
end
def column_value(column, item, value)
case column.name
when :id
link_to value, issue_path(item)
when :subject
link_to value, issue_path(item)
when :parent
value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
when :description
item.description? ? content_tag('div', textilizable(item, :description), :class => "wiki") : ''
when :last_notes
item.last_notes.present? ? content_tag('div', textilizable(item, :last_notes), :class => "wiki") : ''
when :done_ratio
progress_bar(value)
when :relations
content_tag('span',
value.to_s(item) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
:class => value.css_classes_for(item))
when :hours, :estimated_hours
format_hours(value)
when :spent_hours
link_to_if(value > 0, format_hours(value), project_time_entries_path(item.project, :issue_id => "#{item.id}"))
when :total_spent_hours
link_to_if(value > 0, format_hours(value), project_time_entries_path(item.project, :issue_id => "~#{item.id}"))
when :attachments
value.to_a.map {|a| format_object(a)}.join(" ").html_safe
else
format_object(value)
end
end
def csv_content(column, item)
value = column.value_object(item)
if value.is_a?(Array)
value.collect {|v| csv_value(column, item, v)}.compact.join(', ')
else
csv_value(column, item, value)
end
end
def csv_value(column, object, value)
case column.name
when :attachments
value.to_a.map {|a| a.filename}.join("\n")
else
format_object(value, false) do |value|
case value.class.name
when 'Float'
sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
when 'IssueRelation'
value.to_s(object)
when 'Issue'
if object.is_a?(TimeEntry)
"#{value.tracker} ##{value.id}: #{value.subject}"
else
value.id
end
else
value
end
end
end
end
def query_to_csv(items, query, options={})
columns = query.columns
Redmine::Export::CSV.generate do |csv|
# csv header fields
csv << columns.map {|c| c.caption.to_s}
# csv lines
items.each do |item|
csv << columns.map {|c| csv_content(c, item)}
end
end
end
# Retrieve query from session or build a new query
def retrieve_query(klass=IssueQuery, use_session=true)
session_key = klass.name.underscore.to_sym
if params[:query_id].present?
cond = "project_id IS NULL"
cond << " OR project_id = #{@project.id}" if @project
@query = klass.where(cond).find(params[:query_id])
raise ::Unauthorized unless @query.visible?
@query.project = @project
session[session_key] = {:id => @query.id, :project_id => @query.project_id} if use_session
elsif api_request? || params[:set_filter] || !use_session || session[session_key].nil? || session[session_key][:project_id] != (@project ? @project.id : nil)
# Give it a name, required to be valid
@query = klass.new(:name => "_", :project => @project)
@query.build_from_params(params)
session[session_key] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :totalable_names => @query.totalable_names, :sort => @query.sort_criteria.to_a} if use_session
else
# retrieve from session
@query = nil
@query = klass.find_by_id(session[session_key][:id]) if session[session_key][:id]
@query ||= klass.new(:name => "_", :filters => session[session_key][:filters], :group_by => session[session_key][:group_by], :column_names => session[session_key][:column_names], :totalable_names => session[session_key][:totalable_names], :sort_criteria => session[session_key][:sort])
@query.project = @project
end
if params[:sort].present?
@query.sort_criteria = params[:sort]
if use_session
session[session_key] ||= {}
session[session_key][:sort] = @query.sort_criteria.to_a
end
end
@query
end
def retrieve_query_from_session(klass=IssueQuery)
session_key = klass.name.underscore.to_sym
session_data = session[session_key]
if session_data
if session_data[:id]
@query = IssueQuery.find_by_id(session_data[:id])
return unless @query
else
@query = IssueQuery.new(:name => "_", :filters => session_data[:filters], :group_by => session_data[:group_by], :column_names => session_data[:column_names], :totalable_names => session_data[:totalable_names], :sort_criteria => session[session_key][:sort])
end
if session_data.has_key?(:project_id)
@query.project_id = session_data[:project_id]
else
@query.project = @project
end
@query
end
end
# Returns the query definition as hidden field tags
def query_as_hidden_field_tags(query)
tags = hidden_field_tag("set_filter", "1", :id => nil)
if query.filters.present?
query.filters.each do |field, filter|
tags << hidden_field_tag("f[]", field, :id => nil)
tags << hidden_field_tag("op[#{field}]", filter[:operator], :id => nil)
filter[:values].each do |value|
tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
end
end
else
tags << hidden_field_tag("f[]", "", :id => nil)
end
query.columns.each do |column|
tags << hidden_field_tag("c[]", column.name, :id => nil)
end
if query.totalable_names.present?
query.totalable_names.each do |name|
tags << hidden_field_tag("t[]", name, :id => nil)
end
end
if query.group_by.present?
tags << hidden_field_tag("group_by", query.group_by, :id => nil)
end
if query.sort_criteria.present?
tags << hidden_field_tag("sort", query.sort_criteria.to_param, :id => nil)
end
tags
end
def query_hidden_sort_tag(query)
hidden_field_tag("sort", query.sort_criteria.to_param, :id => nil)
end
# Returns the queries that are rendered in the sidebar
def sidebar_queries(klass, project)
klass.visible.global_or_on_project(@project).sorted.to_a
end
# Renders a group of queries
def query_links(title, queries)
return '' if queries.empty?
# links to #index on issues/show
url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : {}
content_tag('h3', title) + "\n" +
content_tag('ul',
queries.collect {|query|
css = 'query'
css << ' selected' if query == @query
content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
}.join("\n").html_safe,
:class => 'queries'
) + "\n"
end
# Renders the list of queries for the sidebar
def render_sidebar_queries(klass, project)
queries = sidebar_queries(klass, project)
out = ''.html_safe
out << query_links(l(:label_my_queries), queries.select(&:is_private?))
out << query_links(l(:label_query_plural), queries.reject(&:is_private?))
out
end
end

View file

@ -0,0 +1,43 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ReportsHelper
def aggregate(data, criteria)
a = 0
data.each { |row|
match = 1
criteria.each { |k, v|
match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && (v == 0 ? ['f', false] : ['t', true]).include?(row[k]))
} unless criteria.nil?
a = a + row["total"].to_i if match == 1
} unless data.nil?
a
end
def aggregate_link(data, criteria, *args)
a = aggregate data, criteria
a > 0 ? link_to(h(a), *args) : '-'
end
def aggregate_path(project, field, row, options={})
parameters = {:set_filter => 1, :subproject_id => '!*', field => row.id}.merge(options)
project_issues_path(row.is_a?(Project) ? row : project, parameters)
end
end

View file

@ -0,0 +1,310 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module RepositoriesHelper
def format_revision(revision)
if revision.respond_to? :format_identifier
revision.format_identifier
else
revision.to_s
end
end
def truncate_at_line_break(text, length = 255)
if text
text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')
end
end
def render_properties(properties)
unless properties.nil? || properties.empty?
content = ''
properties.keys.sort.each do |property|
content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>".html_safe)
end
content_tag('ul', content.html_safe, :class => 'properties')
end
end
def render_changeset_changes
changes = @changeset.filechanges.limit(1000).reorder('path').collect do |change|
case change.action
when 'A'
# Detects moved/copied files
if !change.from_path.blank?
change.action =
@changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
end
change
when 'D'
@changeset.filechanges.detect {|c| c.from_path == change.path} ? nil : change
else
change
end
end.compact
tree = { }
changes.each do |change|
p = tree
dirs = change.path.to_s.split('/').select {|d| !d.blank?}
path = ''
dirs.each do |dir|
path += '/' + dir
p[:s] ||= {}
p = p[:s]
p[path] ||= {}
p = p[path]
end
p[:c] = change
end
render_changes_tree(tree[:s])
end
def render_changes_tree(tree)
return '' if tree.nil?
output = ''
output << '<ul>'
tree.keys.sort.each do |file|
style = 'change'
text = File.basename(h(file))
if s = tree[file][:s]
style << ' folder'
path_param = to_path_param(@repository.relative_path(file))
text = link_to(h(text), :controller => 'repositories',
:action => 'show',
:id => @project,
:repository_id => @repository.identifier_param,
:path => path_param,
:rev => @changeset.identifier)
output << "<li class='#{style}'>#{text}"
output << render_changes_tree(s)
output << "</li>"
elsif c = tree[file][:c]
style << " change-#{c.action}"
path_param = to_path_param(@repository.relative_path(c.path))
text = link_to(h(text), :controller => 'repositories',
:action => 'entry',
:id => @project,
:repository_id => @repository.identifier_param,
:path => path_param,
:rev => @changeset.identifier) unless c.action == 'D'
text << " - #{h(c.revision)}" unless c.revision.blank?
text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories',
:action => 'diff',
:id => @project,
:repository_id => @repository.identifier_param,
:path => path_param,
:rev => @changeset.identifier) + ') '.html_safe if c.action == 'M'
text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank?
output << "<li class='#{style}'>#{text}</li>"
end
end
output << '</ul>'
output.html_safe
end
def repository_field_tags(form, repository)
method = repository.class.name.demodulize.underscore + "_field_tags"
if repository.is_a?(Repository) &&
respond_to?(method) && method != 'repository_field_tags'
send(method, form, repository)
end
end
def scm_select_tag(repository)
scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
Redmine::Scm::Base.all.each do |scm|
if Setting.enabled_scm.include?(scm) ||
(repository && repository.class.name.demodulize == scm)
scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
end
end
select_tag('repository_scm',
options_for_select(scm_options, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?),
:data => {:remote => true, :method => 'get', :url => new_project_repository_path(repository.project)})
end
def with_leading_slash(path)
path.to_s.starts_with?('/') ? path : "/#{path}"
end
def subversion_field_tags(form, repository)
content_tag('p', form.text_field(:url, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')) +
scm_path_info_tag(repository)) +
content_tag('p', form.text_field(:login, :size => 30)) +
content_tag('p', form.password_field(
:password, :size => 30, :name => 'ignore',
:value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
:onfocus => "this.value=''; this.name='repository[password]';",
:onchange => "this.name='repository[password]';"))
end
def darcs_field_tags(form, repository)
content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')) +
scm_path_info_tag(repository)) +
scm_log_encoding_tag(form, repository)
end
def mercurial_field_tags(form, repository)
content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')
) +
scm_path_info_tag(repository)) +
scm_path_encoding_tag(form, repository)
end
def git_field_tags(form, repository)
content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')
) +
scm_path_info_tag(repository)) +
scm_path_encoding_tag(form, repository) +
content_tag('p', form.check_box(
:report_last_commit,
:label => l(:label_git_report_last_commit)
))
end
def cvs_field_tags(form, repository)
content_tag('p', form.text_field(
:root_url,
:label => l(:field_cvsroot),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('root_url')) +
scm_path_info_tag(repository)) +
content_tag('p', form.text_field(
:url,
:label => l(:field_cvs_module),
:size => 30, :required => true,
:disabled => !repository.safe_attribute?('url'))) +
scm_log_encoding_tag(form, repository) +
scm_path_encoding_tag(form, repository)
end
def bazaar_field_tags(form, repository)
content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')) +
scm_path_info_tag(repository)) +
scm_log_encoding_tag(form, repository)
end
def filesystem_field_tags(form, repository)
content_tag('p', form.text_field(
:url, :label => l(:field_root_directory),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')) +
scm_path_info_tag(repository)) +
scm_path_encoding_tag(form, repository)
end
def scm_path_info_tag(repository)
text = scm_path_info(repository)
if text.present?
content_tag('em', text, :class => 'info')
else
''
end
end
def scm_path_info(repository)
scm_name = repository.scm_name.to_s.downcase
info_from_config = Redmine::Configuration["scm_#{scm_name}_path_info"].presence
return info_from_config.html_safe if info_from_config
l("text_#{scm_name}_repository_note", :default => '')
end
def scm_log_encoding_tag(form, repository)
select = form.select(
:log_encoding,
[nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding),
:required => true
)
content_tag('p', select)
end
def scm_path_encoding_tag(form, repository)
select = form.select(
:path_encoding,
[nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
)
content_tag('p', select + content_tag('em', l(:text_scm_path_encoding_note), :class => 'info'))
end
def index_commits(commits, heads)
return nil if commits.nil? or commits.first.parents.nil?
refs_map = {}
heads.each do |head|
refs_map[head.scmid] ||= []
refs_map[head.scmid] << head
end
commits_by_scmid = {}
commits.reverse.each_with_index do |commit, commit_index|
commits_by_scmid[commit.scmid] = {
:parent_scmids => commit.parents.collect { |parent| parent.scmid },
:rdmid => commit_index,
:refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil,
:scmid => commit.scmid,
:href => block_given? ? yield(commit.scmid) : commit.scmid
}
end
heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
space = nil
heads.each do |head|
if commits_by_scmid.include? head.scmid
space = index_head((space || -1) + 1, head, commits_by_scmid)
end
end
# when no head matched anything use first commit
space ||= index_head(0, commits.first, commits_by_scmid)
return commits_by_scmid, space
end
def index_head(space, commit, commits_by_scmid)
stack = [[space, commits_by_scmid[commit.scmid]]]
max_space = space
until stack.empty?
space, commit = stack.pop
commit[:space] = space if commit[:space].nil?
space -= 1
commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
parent_commit = commits_by_scmid[parent_scmid]
if parent_commit and parent_commit[:space].nil?
stack.unshift [space += 1, parent_commit]
end
end
max_space = space if max_space < space
end
max_space
end
end

View file

@ -0,0 +1,21 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module RolesHelper
end

View file

@ -0,0 +1,85 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module RoutesHelper
# Returns the path to project issues or to the cross-project
# issue list if project is nil
def _project_issues_path(project, *args)
if project
project_issues_path(project, *args)
else
issues_path(*args)
end
end
def _project_news_path(project, *args)
if project
project_news_index_path(project, *args)
else
news_index_path(*args)
end
end
def _new_project_issue_path(project, *args)
if project
new_project_issue_path(project, *args)
else
new_issue_path(*args)
end
end
def _project_calendar_path(project, *args)
project ? project_calendar_path(project, *args) : issues_calendar_path(*args)
end
def _project_gantt_path(project, *args)
project ? project_gantt_path(project, *args) : issues_gantt_path(*args)
end
def _time_entries_path(project, issue, *args)
if project
project_time_entries_path(project, *args)
else
time_entries_path(*args)
end
end
def _report_time_entries_path(project, issue, *args)
if project
report_project_time_entries_path(project, *args)
else
report_time_entries_path(*args)
end
end
def _new_time_entry_path(project, issue, *args)
if issue
new_issue_time_entry_path(issue, *args)
elsif project
new_project_time_entry_path(project, *args)
else
new_time_entry_path(*args)
end
end
def board_path(board, *args)
project_board_path(board.project, board, *args)
end
end

View file

@ -0,0 +1,69 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module SearchHelper
def highlight_tokens(text, tokens)
return text unless text && tokens && !tokens.empty?
re_tokens = tokens.collect {|t| Regexp.escape(t)}
regexp = Regexp.new "(#{re_tokens.join('|')})", Regexp::IGNORECASE
result = ''
text.split(regexp).each_with_index do |words, i|
if result.length > 1200
# maximum length of the preview reached
result << '...'
break
end
if i.even?
result << h(words.length > 100 ? "#{words.slice(0..44)} ... #{words.slice(-45..-1)}" : words)
else
t = (tokens.index(words.downcase) || 0) % 4
result << content_tag('span', h(words), :class => "highlight token-#{t}")
end
end
result.html_safe
end
def type_label(t)
l("label_#{t.singularize}_plural", :default => t.to_s.humanize)
end
def project_select_tag
options = [[l(:label_project_all), 'all']]
options << [l(:label_my_projects), 'my_projects'] unless User.current.memberships.empty?
options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.descendants.active.empty?
options << [@project.name, ''] unless @project.nil?
label_tag("scope", l(:description_project_scope), :class => "hidden-for-sighted") +
select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1
end
def render_results_by_type(results_by_type)
links = []
# Sorts types by results count
results_by_type.keys.sort {|a, b| results_by_type[b] <=> results_by_type[a]}.each do |t|
c = results_by_type[t]
next if c == 0
text = "#{type_label(t)} (#{c})"
links << link_to(h(text), :q => params[:q], :titles_only => params[:titles_only],
:all_words => params[:all_words], :scope => params[:scope], t => 1)
end
('<ul>'.html_safe +
links.map {|link| content_tag('li', link)}.join(' ').html_safe +
'</ul>'.html_safe) unless links.empty?
end
end

View file

@ -0,0 +1,210 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module SettingsHelper
def administration_settings_tabs
tabs = [{:name => 'general', :partial => 'settings/general', :label => :label_general},
{:name => 'display', :partial => 'settings/display', :label => :label_display},
{:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication},
{:name => 'api', :partial => 'settings/api', :label => :label_api},
{:name => 'projects', :partial => 'settings/projects', :label => :label_project_plural},
{:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking},
{:name => 'timelog', :partial => 'settings/timelog', :label => :label_time_tracking},
{:name => 'attachments', :partial => 'settings/attachments', :label => :label_attachment_plural},
{:name => 'notifications', :partial => 'settings/notifications', :label => :field_mail_notification},
{:name => 'mail_handler', :partial => 'settings/mail_handler', :label => :label_incoming_emails},
{:name => 'repositories', :partial => 'settings/repositories', :label => :label_repository_plural}
]
end
def render_settings_error(errors)
return if errors.blank?
s = ''.html_safe
errors.each do |name, message|
s << content_tag('li', content_tag('b', l("setting_#{name}")) + " " + message)
end
content_tag('div', content_tag('ul', s), :id => 'errorExplanation')
end
def setting_value(setting)
value = nil
if params[:settings]
value = params[:settings][setting]
end
value || Setting.send(setting)
end
def setting_select(setting, choices, options={})
if blank_text = options.delete(:blank)
choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices
end
setting_label(setting, options).html_safe +
select_tag("settings[#{setting}]",
options_for_select(choices, setting_value(setting).to_s),
options).html_safe
end
def setting_multiselect(setting, choices, options={})
setting_values = setting_value(setting)
setting_values = [] unless setting_values.is_a?(Array)
content_tag("label", l(options[:label] || "setting_#{setting}")) +
hidden_field_tag("settings[#{setting}][]", '').html_safe +
choices.collect do |choice|
text, value = (choice.is_a?(Array) ? choice : [choice, choice])
content_tag(
'label',
check_box_tag(
"settings[#{setting}][]",
value,
setting_values.include?(value),
:id => nil
) + text.to_s,
:class => (options[:inline] ? 'inline' : 'block')
)
end.join.html_safe
end
def setting_text_field(setting, options={})
setting_label(setting, options).html_safe +
text_field_tag("settings[#{setting}]", setting_value(setting), options).html_safe
end
def setting_text_area(setting, options={})
setting_label(setting, options).html_safe +
text_area_tag("settings[#{setting}]", setting_value(setting), options).html_safe
end
def setting_check_box(setting, options={})
setting_label(setting, options).html_safe +
hidden_field_tag("settings[#{setting}]", 0, :id => nil).html_safe +
check_box_tag("settings[#{setting}]", 1, setting_value(setting).to_s != '0', options).html_safe
end
def setting_label(setting, options={})
label = options.delete(:label)
if label == false
''
else
text = label.is_a?(String) ? label : l(label || "setting_#{setting}")
label_tag("settings_#{setting}", text, options[:label_options])
end
end
# Renders a notification field for a Redmine::Notifiable option
def notification_field(notifiable)
tag_data = notifiable.parent.present? ?
{:parent_notifiable => notifiable.parent} :
{:disables => "input[data-parent-notifiable=#{notifiable.name}]"}
tag = check_box_tag('settings[notified_events][]',
notifiable.name,
setting_value('notified_events').include?(notifiable.name),
:id => nil,
:data => tag_data)
text = l_or_humanize(notifiable.name, :prefix => 'label_')
options = {}
if notifiable.parent.present?
options[:class] = "parent"
end
content_tag(:label, tag + text, options)
end
def session_lifetime_options
options = [[l(:label_disabled), 0]]
options += [4, 8, 12].map {|hours|
[l('datetime.distance_in_words.x_hours', :count => hours), (hours * 60).to_s]
}
options += [1, 7, 30, 60, 365].map {|days|
[l('datetime.distance_in_words.x_days', :count => days), (days * 24 * 60).to_s]
}
options
end
def session_timeout_options
options = [[l(:label_disabled), 0]]
options += [1, 2, 4, 8, 12, 24, 48].map {|hours|
[l('datetime.distance_in_words.x_hours', :count => hours), (hours * 60).to_s]
}
options
end
def link_copied_issue_options
options = [
[:general_text_Yes, 'yes'],
[:general_text_No, 'no'],
[:label_ask, 'ask']
]
options.map {|label, value| [l(label), value.to_s]}
end
def cross_project_subtasks_options
options = [
[:label_disabled, ''],
[:label_cross_project_system, 'system'],
[:label_cross_project_tree, 'tree'],
[:label_cross_project_hierarchy, 'hierarchy'],
[:label_cross_project_descendants, 'descendants']
]
options.map {|label, value| [l(label), value.to_s]}
end
def parent_issue_dates_options
options = [
[:label_parent_task_attributes_derived, 'derived'],
[:label_parent_task_attributes_independent, 'independent']
]
options.map {|label, value| [l(label), value.to_s]}
end
def parent_issue_priority_options
options = [
[:label_parent_task_attributes_derived, 'derived'],
[:label_parent_task_attributes_independent, 'independent']
]
options.map {|label, value| [l(label), value.to_s]}
end
def parent_issue_done_ratio_options
options = [
[:label_parent_task_attributes_derived, 'derived'],
[:label_parent_task_attributes_independent, 'independent']
]
options.map {|label, value| [l(label), value.to_s]}
end
# Returns the options for the date_format setting
def date_format_setting_options(locale)
Setting::DATE_FORMATS.map do |f|
today = ::I18n.l(User.current.today, :locale => locale, :format => f)
format = f.gsub('%', '').gsub(/[dmY]/) do
{'d' => 'dd', 'm' => 'mm', 'Y' => 'yyyy'}[$&]
end
["#{today} (#{format})", f]
end
end
end

163
app/helpers/sort_helper.rb Normal file
View file

@ -0,0 +1,163 @@
# encoding: utf-8
#
# Helpers to sort tables using clickable column headers.
#
# Author: Stuart Rackham <srackham@methods.co.nz>, March 2005.
# Jean-Philippe Lang, 2009
# License: This source code is released under the MIT license.
#
# - Consecutive clicks toggle the column's sort order.
# - Sort state is maintained by a session hash entry.
# - CSS classes identify sort column and state.
# - Typically used in conjunction with the Pagination module.
#
# Example code snippets:
#
# Controller:
#
# helper :sort
# include SortHelper
#
# def list
# sort_init 'last_name'
# sort_update %w(first_name last_name)
# @items = Contact.find_all nil, sort_clause
# end
#
# Controller (using Pagination module):
#
# helper :sort
# include SortHelper
#
# def list
# sort_init 'last_name'
# sort_update %w(first_name last_name)
# @contact_pages, @items = paginate :contacts,
# :order_by => sort_clause,
# :per_page => 10
# end
#
# View (table header in list.rhtml):
#
# <thead>
# <tr>
# <%= sort_header_tag('id', :title => 'Sort by contact ID') %>
# <%= sort_header_tag('last_name', :caption => 'Name') %>
# <%= sort_header_tag('phone') %>
# <%= sort_header_tag('address', :width => 200) %>
# </tr>
# </thead>
#
# - Introduces instance variables: @sort_default, @sort_criteria
# - Introduces param :sort
#
module SortHelper
def sort_name
controller_name + '_' + action_name + '_sort'
end
# Initializes the default sort.
# Examples:
#
# sort_init 'name'
# sort_init 'id', 'desc'
# sort_init ['name', ['id', 'desc']]
# sort_init [['name', 'desc'], ['id', 'desc']]
#
def sort_init(*args)
case args.size
when 1
@sort_default = args.first.is_a?(Array) ? args.first : [[args.first]]
when 2
@sort_default = [[args.first, args.last]]
else
raise ArgumentError
end
end
# Updates the sort state. Call this in the controller prior to calling
# sort_clause.
# - criteria can be either an array or a hash of allowed keys
#
def sort_update(criteria, sort_name=nil)
sort_name ||= self.sort_name
@sort_criteria = Redmine::SortCriteria.new(params[:sort] || session[sort_name] || @sort_default)
@sortable_columns = criteria
session[sort_name] = @sort_criteria.to_param
end
# Clears the sort criteria session data
#
def sort_clear
session[sort_name] = nil
end
# Returns an SQL sort clause corresponding to the current sort state.
# Use this to sort the controller's table items collection.
#
def sort_clause()
@sort_criteria.sort_clause(@sortable_columns)
end
def sort_criteria
@sort_criteria
end
# Returns a link which sorts by the named column.
#
# - column is the name of an attribute in the sorted record collection.
# - the optional caption explicitly specifies the displayed link text.
# - 2 CSS classes reflect the state of the link: sort and asc or desc
#
def sort_link(column, caption, default_order)
css, order = nil, default_order
if column.to_s == @sort_criteria.first_key
if @sort_criteria.first_asc?
css = 'sort asc'
order = 'desc'
else
css = 'sort desc'
order = 'asc'
end
end
caption = column.to_s.humanize unless caption
sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param }
link_to(caption, {:params => request.query_parameters.merge(sort_options)}, :class => css)
end
# Returns a table header <th> tag with a sort link for the named column
# attribute.
#
# Options:
# :caption The displayed link name (defaults to titleized column name).
# :title The tag's 'title' attribute (defaults to 'Sort by :caption').
#
# Other options hash entries generate additional table header tag attributes.
#
# Example:
#
# <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %>
#
def sort_header_tag(column, options = {})
caption = options.delete(:caption) || column.to_s.humanize
default_order = options.delete(:default_order) || 'asc'
options[:title] = l(:label_sort_by, "\"#{caption}\"") unless options[:title]
content_tag('th', sort_link(column, caption, default_order), options)
end
# Returns the css classes for the current sort order
#
# Example:
#
# sort_css_classes
# # => "sort-by-created-on sort-desc"
def sort_css_classes
if @sort_criteria.first_key
"sort-by-#{@sort_criteria.first_key.to_s.dasherize} sort-#{@sort_criteria.first_asc? ? 'asc' : 'desc'}"
end
end
end

View file

@ -0,0 +1,121 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module TimelogHelper
include ApplicationHelper
# Returns a collection of activities for a select field. time_entry
# is optional and will be used to check if the selected TimeEntryActivity
# is active.
def activity_collection_for_select_options(time_entry=nil, project=nil)
project ||= time_entry.try(:project)
project ||= @project
if project.nil?
activities = TimeEntryActivity.shared.active
else
activities = project.activities
end
collection = []
if time_entry && time_entry.activity && !time_entry.activity.active?
collection << [ "--- #{l(:actionview_instancetag_blank_option)} ---", '' ]
else
collection << [ "--- #{l(:actionview_instancetag_blank_option)} ---", '' ] unless activities.detect(&:is_default)
end
activities.each { |a| collection << [a.name, a.id] }
collection
end
def select_hours(data, criteria, value)
if value.to_s.empty?
data.select {|row| row[criteria].blank? }
else
data.select {|row| row[criteria].to_s == value.to_s}
end
end
def sum_hours(data)
sum = 0
data.each do |row|
sum += row['hours'].to_f
end
sum
end
def format_criteria_value(criteria_options, value)
if value.blank?
"[#{l(:label_none)}]"
elsif k = criteria_options[:klass]
obj = k.find_by_id(value.to_i)
if obj.is_a?(Issue)
obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}"
else
obj
end
elsif cf = criteria_options[:custom_field]
format_value(value, cf)
else
value.to_s
end
end
def report_to_csv(report)
Redmine::Export::CSV.generate do |csv|
# Column headers
headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) }
headers += report.periods
headers << l(:label_total_time)
csv << headers
# Content
report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours)
# Total row
str_total = l(:label_total_time)
row = [ str_total ] + [''] * (report.criteria.size - 1)
total = 0
report.periods.each do |period|
sum = sum_hours(select_hours(report.hours, report.columns, period.to_s))
total += sum
row << (sum > 0 ? sum : '')
end
row << total
csv << row
end
end
def report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours, level=0)
hours.collect {|h| h[criteria[level]].to_s}.uniq.each do |value|
hours_for_value = select_hours(hours, criteria[level], value)
next if hours_for_value.empty?
row = [''] * level
row << format_criteria_value(available_criteria[criteria[level]], value).to_s
row += [''] * (criteria.length - level - 1)
total = 0
periods.each do |period|
sum = sum_hours(select_hours(hours_for_value, columns, period.to_s))
total += sum
row << (sum > 0 ? sum : '')
end
row << total
csv << row
if criteria.length > level + 1
report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours_for_value, level + 1)
end
end
end
end

View file

@ -0,0 +1,21 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module TrackersHelper
end

View file

@ -0,0 +1,64 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module UsersHelper
def users_status_options_for_select(selected)
user_count_by_status = User.group('status').count.to_hash
options_for_select([[l(:label_all), ''],
["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'],
["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'],
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s)
end
def user_mail_notification_options(user)
user.valid_notification_options.collect {|o| [l(o.last), o.first]}
end
def textarea_font_options
[[l(:label_font_default), '']] + UserPreference::TEXTAREA_FONT_OPTIONS.map {|o| [l("label_font_#{o}"), o]}
end
def change_status_link(user)
url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
if user.locked?
link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
elsif user.registered?
link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
elsif user != User.current
link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock'
end
end
def additional_emails_link(user)
if user.email_addresses.count > 1 || Setting.max_additional_emails.to_i > 0
link_to l(:label_email_address_plural), user_email_addresses_path(@user), :class => 'icon icon-email-add', :remote => true
end
end
def user_settings_tabs
tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
{:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
]
if Group.givable.any?
tabs.insert 1, {:name => 'groups', :partial => 'users/groups', :label => :label_group_plural}
end
tabs
end
end

View file

@ -0,0 +1,76 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module VersionsHelper
def version_anchor(version)
if @project == version.project
anchor version.name
else
anchor "#{version.project.try(:identifier)}-#{version.name}"
end
end
def version_filtered_issues_path(version, options = {})
options = {:fixed_version_id => version, :set_filter => 1}.merge(options)
project = case version.sharing
when 'hierarchy', 'tree'
if version.project && version.project.root.visible?
version.project.root
else
version.project
end
when 'system'
nil
else
version.project
end
if project
project_issues_path(project, options)
else
issues_path(options)
end
end
STATUS_BY_CRITERIAS = %w(tracker status priority author assigned_to category)
def render_issue_status_by(version, criteria)
criteria = 'tracker' unless STATUS_BY_CRITERIAS.include?(criteria)
h = Hash.new {|k,v| k[v] = [0, 0]}
begin
# Total issue count
version.fixed_issues.group(criteria).count.each {|c,s| h[c][0] = s}
# Open issues count
version.fixed_issues.open.group(criteria).count.each {|c,s| h[c][1] = s}
rescue ActiveRecord::RecordNotFound
# When grouping by an association, Rails throws this exception if there's no result (bug)
end
# Sort with nil keys in last position
counts = h.keys.sort {|a,b| a.nil? ? 1 : (b.nil? ? -1 : a <=> b)}.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
max = counts.collect {|c| c[:total]}.max
render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max}
end
def status_by_options_for_select(value)
options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value)
end
end

View file

@ -0,0 +1,80 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module WatchersHelper
def watcher_link(objects, user)
return '' unless user && user.logged?
objects = Array.wrap(objects)
return '' unless objects.any?
watched = Watcher.any_watched?(objects, user)
css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ')
text = watched ? l(:button_unwatch) : l(:button_watch)
url = watch_path(
:object_type => objects.first.class.to_s.underscore,
:object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort)
)
method = watched ? 'delete' : 'post'
link_to text, url, :remote => true, :method => method, :class => css
end
# Returns the css class used to identify watch links for a given +object+
def watcher_css(objects)
objects = Array.wrap(objects)
id = (objects.size == 1 ? objects.first.id : 'bulk')
"#{objects.first.class.to_s.underscore}-#{id}-watcher"
end
# Returns a comma separated list of users watching the given object
def watchers_list(object)
remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
content = ''.html_safe
lis = object.watcher_users.preload(:email_address).collect do |user|
s = ''.html_safe
s << avatar(user, :size => "16").to_s
s << link_to_user(user, :class => 'user')
if remove_allowed
url = {:controller => 'watchers',
:action => 'destroy',
:object_type => object.class.to_s.underscore,
:object_id => object.id,
:user_id => user}
s << ' '
s << link_to(l(:button_delete), url,
:remote => true, :method => 'delete',
:class => "delete icon-only icon-del",
:title => l(:button_delete))
end
content << content_tag('li', s, :class => "user-#{user.id}")
end
content.present? ? content_tag('ul', content, :class => 'watchers') : content
end
def watchers_checkboxes(object, users, checked=nil)
users.map do |user|
c = checked.nil? ? object.watched_by?(user) : checked
tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil
content_tag 'label', "#{tag} #{h(user)}".html_safe,
:id => "issue_watcher_user_ids_#{user.id}",
:class => "floating"
end.join.html_safe
end
end

View file

@ -0,0 +1,21 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module WelcomeHelper
end

View file

@ -0,0 +1,67 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module WikiHelper
include Redmine::Export::PDF::WikiPdfHelper
def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
pages = pages.group_by(&:parent) unless pages.is_a?(Hash)
s = ''.html_safe
if pages.has_key?(parent)
pages[parent].each do |page|
attrs = "value='#{page.id}'"
attrs << " selected='selected'" if selected == page
indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : ''
s << content_tag('option', (indent + h(page.pretty_title)).html_safe, :value => page.id.to_s, :selected => selected == page) +
wiki_page_options_for_select(pages, selected, page, level + 1)
end
end
s
end
def wiki_page_wiki_options_for_select(page)
projects = Project.allowed_to(:rename_wiki_pages).joins(:wiki).preload(:wiki).to_a
projects << page.project unless projects.include?(page.project)
project_tree_options_for_select(projects, :selected => page.project) do |project|
wiki_id = project.wiki.try(:id)
{:value => wiki_id, :selected => wiki_id == page.wiki_id}
end
end
def wiki_page_breadcrumb(page)
breadcrumb(page.ancestors.reverse.collect {|parent|
link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project, :version => nil})
})
end
# Returns the path for the Cancel link when editing a wiki page
def wiki_page_edit_cancel_path(page)
if page.new_record?
if parent = page.parent
project_wiki_page_path(parent.project, parent.title)
else
project_wiki_index_path(page.project)
end
else
project_wiki_page_path(page.project, page.title)
end
end
end

View file

@ -0,0 +1,95 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module WorkflowsHelper
def options_for_workflow_select(name, objects, selected, options={})
option_tags = ''.html_safe
multiple = false
if selected
if selected.size == objects.size
selected = 'all'
else
selected = selected.map(&:id)
if selected.size > 1
multiple = true
end
end
else
selected = objects.first.try(:id)
end
all_tag_options = {:value => 'all', :selected => (selected == 'all')}
if multiple
all_tag_options.merge!(:style => "display:none;")
end
option_tags << content_tag('option', l(:label_all), all_tag_options)
option_tags << options_from_collection_for_select(objects, "id", "name", selected)
select_tag name, option_tags, {:multiple => multiple}.merge(options)
end
def field_required?(field)
field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
end
def field_permission_tag(permissions, status, field, roles)
name = field.is_a?(CustomField) ? field.id.to_s : field
options = [["", ""], [l(:label_readonly), "readonly"]]
options << [l(:label_required), "required"] unless field_required?(field)
html_options = {}
if perm = permissions[status.id][name]
if perm.uniq.size > 1 || perm.size < @roles.size * @trackers.size
options << [l(:label_no_change_option), "no_change"]
selected = 'no_change'
else
selected = perm.first
end
end
hidden = field.is_a?(CustomField) &&
!field.visible? &&
!roles.detect {|role| role.custom_fields.to_a.include?(field)}
if hidden
options[0][0] = l(:label_hidden)
selected = ''
html_options[:disabled] = true
end
select_tag("permissions[#{status.id}][#{name}]", options_for_select(options, selected), html_options)
end
def transition_tag(workflows, old_status, new_status, name)
w = workflows.select {|w| w.old_status == old_status && w.new_status == new_status}.size
tag_name = "transitions[#{ old_status.try(:id) || 0 }][#{new_status.id}][#{name}]"
if w == 0 || w == @roles.size * @trackers.size
hidden_field_tag(tag_name, "0", :id => nil) +
check_box_tag(tag_name, "1", w != 0,
:class => "old-status-#{old_status.try(:id) || 0} new-status-#{new_status.id}")
else
select_tag tag_name,
options_for_select([
[l(:general_text_Yes), "1"],
[l(:general_text_No), "0"],
[l(:label_no_change_option), "no_change"]
], "no_change")
end
end
end