🚀 Sólo código modificado para instalar con docker
This commit is contained in:
parent
41d93da720
commit
de8d940598
2217 changed files with 1 additions and 278373 deletions
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,33 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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_by(&:event_datetime).reverse_each do |event|
|
||||
if group_events = events_by_group.delete(event.event_group)
|
||||
group_events.sort_by(&:event_datetime).reverse.each_with_index do |e, i|
|
||||
sorted_events << [e, i > 0]
|
||||
end
|
||||
end
|
||||
end
|
||||
sorted_events
|
||||
end
|
||||
end
|
|
@ -1,35 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
@ -1,98 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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_pagination
|
||||
pagination_links_each @paginator do |text, parameters, options|
|
||||
if att = @attachments[parameters[:page] - 1]
|
||||
link_to text, named_attachment_path(att, att.filename)
|
||||
end
|
||||
end if @paginator
|
||||
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
|
||||
|
||||
def render_file_content(attachment, content)
|
||||
if attachment.is_markdown?
|
||||
render :partial => 'common/markup', :locals => {:markup_text_formatting => 'markdown', :markup_text => content}
|
||||
elsif attachment.is_textile?
|
||||
render :partial => 'common/markup', :locals => {:markup_text_formatting => 'textile', :markup_text => content}
|
||||
else
|
||||
render :partial => 'common/file', :locals => {:content => content, :filename => attachment.filename}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,24 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,75 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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 AvatarsHelper
|
||||
include GravatarHelper::PublicMethods
|
||||
|
||||
def assignee_avatar(user, options={})
|
||||
return '' unless user
|
||||
|
||||
options.merge!(:title => l(:field_assigned_to) + ": " + user.name)
|
||||
avatar(user, options).to_s.html_safe
|
||||
end
|
||||
|
||||
def author_avatar(user, options={})
|
||||
return '' unless user
|
||||
|
||||
options.merge!(:title => l(:field_author) + ": " + user.name)
|
||||
avatar(user, options).to_s.html_safe
|
||||
end
|
||||
|
||||
# Returns the avatar image tag for the given +user+ if avatars are enabled
|
||||
# +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
|
||||
def avatar(user, options = { })
|
||||
if Setting.gravatar_enabled?
|
||||
options.merge!(:default => Setting.gravatar_default)
|
||||
options[:class] = GravatarHelper::DEFAULT_OPTIONS[:class] + " " + options[:class] if options[:class]
|
||||
email = nil
|
||||
if user.respond_to?(:mail)
|
||||
email = user.mail
|
||||
options[:title] = user.name unless options[:title]
|
||||
elsif user.to_s =~ %r{<(.+?)>}
|
||||
email = $1
|
||||
end
|
||||
if email.present?
|
||||
gravatar(email.to_s.downcase, options) rescue nil
|
||||
elsif user.is_a?(AnonymousUser)
|
||||
anonymous_avatar(options)
|
||||
else
|
||||
nil
|
||||
end
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a link to edit user's avatar if avatars are enabled
|
||||
def avatar_edit_link(user, options={})
|
||||
if Setting.gravatar_enabled?
|
||||
url = Redmine::Configuration['avatar_server_url']
|
||||
link_to avatar(user, {:title => l(:button_edit)}.merge(options)), url, :target => '_blank'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def anonymous_avatar(options={})
|
||||
image_tag 'anonymous.png', GravatarHelper::DEFAULT_OPTIONS.except(:default, :rating, :ssl).merge(options)
|
||||
end
|
||||
end
|
|
@ -1,41 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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 ? ' ' * 2 * level + '» ' : '').html_safe
|
||||
label << board.name
|
||||
options << [label, board.id]
|
||||
end
|
||||
options
|
||||
end
|
||||
end
|
|
@ -1,29 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
||||
include Redmine::Utils::DateCalculation
|
||||
|
||||
def calendar_day_css_classes(calendar, day)
|
||||
css = day.month==calendar.month ? +'even' : +'odd'
|
||||
css << " today" if User.current.today == day
|
||||
css << " nwday" if non_working_week_days.include?(day.cwday)
|
||||
css
|
||||
end
|
||||
end
|
|
@ -1,58 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,217 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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)
|
||||
css = "#{custom_value.custom_field.field_format}_cf"
|
||||
data = nil
|
||||
if custom_value.custom_field.full_text_formatting?
|
||||
css += ' wiki-edit'
|
||||
data = {
|
||||
:auto_complete => true,
|
||||
:issues_url => auto_complete_issues_path(:project_id => custom_value.customized.project, :q => '')
|
||||
} if custom_value.customized&.try(:project)
|
||||
end
|
||||
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 => css,
|
||||
:data => data)
|
||||
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
|
||||
|
||||
# Returns custom field value tag
|
||||
def custom_field_value_tag(value)
|
||||
attr_value = show_value(value)
|
||||
|
||||
if !attr_value.blank? && value.custom_field.full_text_formatting?
|
||||
content_tag('div', attr_value, :class => 'wiki')
|
||||
else
|
||||
attr_value
|
||||
end
|
||||
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
|
||||
|
||||
def select_type_radio_buttons(default_type)
|
||||
if CUSTOM_FIELDS_TABS.none? {|tab| tab[:name] == default_type}
|
||||
default_type = 'IssueCustomField'
|
||||
end
|
||||
custom_field_type_options.map do |name, type|
|
||||
content_tag(:label, :style => 'display:block;') do
|
||||
radio_button_tag('type', type, type == default_type) + name
|
||||
end
|
||||
end.join("\n").html_safe
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,40 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,45 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,44 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,55 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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 import_title
|
||||
l(:"label_import_#{import_partial_prefix}")
|
||||
end
|
||||
|
||||
def import_partial_prefix
|
||||
@import.class.name.sub('Import', '').underscore.pluralize
|
||||
end
|
||||
|
||||
def options_for_mapping_select(import, field, options={})
|
||||
tags = "".html_safe
|
||||
blank_text = options[:required] ? "-- #{l(:actionview_instancetag_blank_option)} --" : " ".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] || options[:default_value])
|
||||
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
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,25 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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_by{|k| values[k][:order]}.collect{|k| [l(values[k][:name]), k]}
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,647 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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) + (" (#{format_date(issue.closed_on)})" if issue.closed?)}<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>: #{avatar(issue.assigned_to, :size => '13', :title => l(:field_assigned_to)) if issue.assigned_to} #{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 = subject + ' ' + content_tag('span', l(:field_is_private), :class => 'badge badge-private private')
|
||||
end
|
||||
s << content_tag('h3', subject)
|
||||
s << '</div>' * (ancestors.size + 1)
|
||||
s.html_safe
|
||||
end
|
||||
|
||||
def render_descendants_tree(issue)
|
||||
manage_relations = User.current.allowed_to?(:manage_subtasks, issue.project)
|
||||
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
|
||||
buttons =
|
||||
if manage_relations
|
||||
link_to(l(:label_delete_link_to_subtask),
|
||||
issue_path(
|
||||
{:id => child.id, :issue => {:parent_issue_id => ''},
|
||||
:back_url => issue_path(issue.id), :no_flash => '1'}),
|
||||
:method => :put,
|
||||
:data => {:confirm => l(:text_are_you_sure)},
|
||||
:title => l(:label_delete_link_to_subtask),
|
||||
:class => 'icon-only icon-link-break'
|
||||
)
|
||||
else
|
||||
"".html_safe
|
||||
end
|
||||
buttons << link_to_context_menu
|
||||
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') +
|
||||
content_tag('td', h(child.status), :class => 'status') +
|
||||
content_tag('td', link_to_user(child.assigned_to), :class => 'assigned_to') +
|
||||
content_tag('td', format_date(child.start_date), :class => 'start_date') +
|
||||
content_tag('td', format_date(child.due_date), :class => 'due_date') +
|
||||
content_tag('td',
|
||||
(if child.disabled_core_fields.include?('done_ratio')
|
||||
''
|
||||
else
|
||||
progress_bar(child.done_ratio)
|
||||
end),
|
||||
:class=> 'done_ratio') +
|
||||
content_tag('td', buttons, :class => 'buttons'),
|
||||
: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}"
|
||||
buttons =
|
||||
if 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'
|
||||
)
|
||||
else
|
||||
"".html_safe
|
||||
end
|
||||
buttons << link_to_context_menu
|
||||
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') +
|
||||
content_tag('td', other_issue.status, :class => 'status') +
|
||||
content_tag('td', link_to_user(other_issue.assigned_to), :class => 'assigned_to') +
|
||||
content_tag('td', format_date(other_issue.start_date), :class => 'start_date') +
|
||||
content_tag('td', format_date(other_issue.due_date), :class => 'due_date') +
|
||||
content_tag('td',
|
||||
(if other_issue.disabled_core_fields.include?('done_ratio')
|
||||
''
|
||||
else
|
||||
progress_bar(other_issue.done_ratio)
|
||||
end),
|
||||
:class=> 'done_ratio') +
|
||||
content_tag('td', buttons, :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
|
||||
# link to global time entries if cross-project subtasks are allowed
|
||||
# in order to show time entries from not descendents projects
|
||||
if %w(system tree hierarchy).include?(Setting.cross_project_subtasks)
|
||||
path = time_entries_path(:issue_id => "~#{issue.id}")
|
||||
end
|
||||
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
|
||||
|
||||
def issue_due_date_details(issue)
|
||||
return if issue&.due_date.nil?
|
||||
s = format_date(issue.due_date)
|
||||
s += " (#{due_date_distance_in_words(issue.due_date)})" unless issue.closed?
|
||||
s
|
||||
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 = trackers_for_select(issue)
|
||||
trackers.collect {|t| [t.name, t.id]}
|
||||
end
|
||||
|
||||
def trackers_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
|
||||
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|
|
||||
m = (i < half ? :left : :right)
|
||||
rows.send m, custom_field_name_tag(value.custom_field), custom_field_value_tag(value), :class => value.custom_field.css_classes
|
||||
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_tag = custom_field_value_tag(value)
|
||||
next if attr_value_tag.blank?
|
||||
content =
|
||||
content_tag('hr') +
|
||||
content_tag('p', content_tag('strong', custom_field_name_tag(value.custom_field) )) +
|
||||
content_tag('div', attr_value_tag, class: 'value')
|
||||
s << content_tag('div', content, class: "#{value.custom_field.css_classes} 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.select{|u| u.status == User::STATUS_ACTIVE}
|
||||
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 start_date due_date).each do |attribute|
|
||||
if issue.disabled_core_fields.grep(/^#{attribute}(_id)?$/).empty?
|
||||
attr_value = (issue.send attribute).to_s
|
||||
next if attr_value.blank?
|
||||
if html
|
||||
items << content_tag('strong', "#{l("field_#{attribute}")}: ") + attr_value
|
||||
else
|
||||
items << "#{l("field_#{attribute}")}: #{attr_value}"
|
||||
end
|
||||
end
|
||||
end
|
||||
issue.visible_custom_field_values(user).each do |value|
|
||||
cf_value = show_value(value, false)
|
||||
next if cf_value.blank?
|
||||
if html
|
||||
items << content_tag('strong', "#{value.custom_field.name}: ") + cf_value
|
||||
else
|
||||
items << "#{value.custom_field.name}: #{cf_value}"
|
||||
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
|
||||
|
||||
MultipleValuesDetail = Struct.new(:property, :prop_key, :custom_field, :old_value, :value)
|
||||
|
||||
# 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)
|
||||
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?
|
||||
values_by_field.each do |field, changes|
|
||||
if changes[:added].any?
|
||||
detail = MultipleValuesDetail.new('cf', field.id.to_s, field)
|
||||
detail.value = changes[:added]
|
||||
strings << show_detail(detail, no_html, options)
|
||||
end
|
||||
if changes[:deleted].any?
|
||||
detail = MultipleValuesDetail.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 =
|
||||
if rel_issue.nil?
|
||||
"#{l(:label_issue)} ##{detail.value}"
|
||||
else
|
||||
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
|
||||
end
|
||||
elsif detail.old_value && !detail.value
|
||||
rel_issue = Issue.visible.find_by_id(detail.old_value)
|
||||
old_value =
|
||||
if rel_issue.nil?
|
||||
"#{l(:label_issue)} ##{detail.old_value}"
|
||||
else
|
||||
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
|
||||
end
|
||||
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(
|
||||
l(:label_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
|
||||
# For project, return the associated record only if is visible for the current User
|
||||
def find_name_by_reflection(field, id)
|
||||
return nil if id.blank?
|
||||
@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 && !record.is_a?(Project)) || (record.is_a?(Project) && record.visible?)
|
||||
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
|
||||
|
||||
# Issue history tabs
|
||||
def issue_history_tabs
|
||||
tabs = []
|
||||
if @journals.present?
|
||||
journals_without_notes = @journals.select{|value| value.notes.blank?}
|
||||
journals_with_notes = @journals.reject{|value| value.notes.blank?}
|
||||
|
||||
tabs << {:name => 'history', :label => :label_history, :onclick => 'showIssueHistory("history", this.href)', :partial => 'issues/tabs/history', :locals => {:issue => @issue, :journals => @journals}}
|
||||
tabs << {:name => 'notes', :label => :label_issue_history_notes, :onclick => 'showIssueHistory("notes", this.href)'} if journals_with_notes.any?
|
||||
tabs << {:name => 'properties', :label => :label_issue_history_properties, :onclick => 'showIssueHistory("properties", this.href)'} if journals_without_notes.any?
|
||||
end
|
||||
tabs << {:name => 'time_entries', :label => :label_time_entry_plural, :remote => true, :onclick => "getRemoteTab('time_entries', '#{tab_issue_path(@issue, :name => 'time_entries')}', '#{issue_path(@issue, :tab => 'time_entries')}')"} if User.current.allowed_to?(:view_time_entries, @project) && @issue.spent_hours > 0
|
||||
tabs << {:name => 'changesets', :label => :label_associated_revisions, :remote => true, :onclick => "getRemoteTab('changesets', '#{tab_issue_path(@issue, :name => 'changesets')}', '#{issue_path(@issue, :tab => 'changesets')}')"} if @has_changesets
|
||||
tabs
|
||||
end
|
||||
|
||||
def issue_history_default_tab
|
||||
# tab params overrides user default tab preference
|
||||
return params[:tab] if params[:tab].present?
|
||||
user_default_tab = User.current.pref.history_default_tab
|
||||
|
||||
case user_default_tab
|
||||
when 'last_tab_visited'
|
||||
cookies['history_last_tab'].presence || 'notes'
|
||||
when ''
|
||||
'notes'
|
||||
else
|
||||
user_default_tab
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,71 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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?).sort_by{|a| ids.index(a.id.to_s)} : []
|
||||
end
|
||||
|
||||
# Returns the action links for an issue journal
|
||||
def render_journal_actions(issue, journal, options={})
|
||||
links = []
|
||||
if journal.notes.present?
|
||||
if options[:reply_links]
|
||||
indice = journal.indice || @journal.issue.visible_journals_with_index.find{|j| j.id == @journal.id}.indice
|
||||
links << link_to(l(:button_quote),
|
||||
quoted_issue_path(issue, :journal_id => journal, :journal_indice => indice),
|
||||
:remote => true,
|
||||
:method => 'post',
|
||||
:title => l(:button_quote),
|
||||
:class => 'icon-only icon-comment'
|
||||
)
|
||||
end
|
||||
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'
|
||||
)
|
||||
end
|
||||
end
|
||||
safe_join(links, ' ')
|
||||
end
|
||||
|
||||
def render_notes(issue, journal, options={})
|
||||
content_tag('div', textilizable(journal, :notes), :id => "journal-#{journal.id}-notes", :class => "wiki")
|
||||
end
|
||||
|
||||
def render_private_notes_indicator(journal)
|
||||
content = journal.private_notes? ? l(:field_is_private) : ''
|
||||
css_classes = journal.private_notes? ? 'badge badge-private private' : ''
|
||||
content_tag('span', content.html_safe, :id => "journal-#{journal.id}-private_notes", :class => css_classes)
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,62 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
||||
# Returns inheritance information for an inherited member role
|
||||
def render_role_inheritance(member, role)
|
||||
content = member.role_inheritance(role).map do |h|
|
||||
if h.is_a?(Project)
|
||||
l(:label_inherited_from_parent_project)
|
||||
elsif h.is_a?(Group)
|
||||
l(:label_inherited_from_group, :name => h.name.to_s)
|
||||
end
|
||||
end.compact.uniq
|
||||
|
||||
if content.present?
|
||||
content_tag('em', content.join(", "), :class => "info")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,187 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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 => 'icon-only icon-sort-handle 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 => User.current.projects).
|
||||
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.add_filter 'project.status', '=', ["#{Project::STATUS_ACTIVE}"]
|
||||
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.add_filter 'project.status', '=', ["#{Project::STATUS_ACTIVE}"]
|
||||
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_issuesupdatedbyme_block(block, settings)
|
||||
query = IssueQuery.new(:name => l(:label_updated_issues), :user => User.current)
|
||||
query.add_filter 'updated_by', '=', ['me']
|
||||
query.add_filter 'project.status', '=', ["#{Project::STATUS_ACTIVE}"]
|
||||
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.add_filter 'project.status', '=', ["#{Project::STATUS_ACTIVE}"]
|
||||
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 => User.current.projects).
|
||||
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
|
||||
|
||||
def render_activity_block(block, settings)
|
||||
events_by_day = Redmine::Activity::Fetcher.new(User.current, :author => User.current).events(nil, nil, :limit => 10).group_by {|event| User.current.time_to_date(event.event_datetime)}
|
||||
|
||||
render :partial => 'my/blocks/activity', :locals => {:events_by_day => events_by_day}
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,64 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,185 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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_project},
|
||||
{:name => 'members', :action => :manage_members,
|
||||
:partial => 'projects/settings/members', :label => :label_member_plural},
|
||||
{:name => 'issues', :action => :edit_project, :module => :issue_tracking,
|
||||
:partial => 'projects/settings/issues', :label => :label_issue_tracking},
|
||||
{: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 => '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 => :label_time_tracking}
|
||||
]
|
||||
tabs.
|
||||
select {|tab| User.current.allowed_to?(tab[:action], @project)}.
|
||||
select {|tab| tab[:module].nil? || @project.module_enabled?(tab[:module])}
|
||||
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=''> </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
|
||||
if User.current.admin?
|
||||
links << link_to(l(:label_administration), admin_projects_path, :class => 'icon icon-settings')
|
||||
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-user 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
|
||||
|
||||
def bookmark_link(project, user = User.current)
|
||||
return '' unless user && user.logged?
|
||||
@jump_box ||= Redmine::ProjectJumpBox.new user
|
||||
bookmarked = @jump_box.bookmark?(project)
|
||||
css = +"icon bookmark "
|
||||
|
||||
if bookmarked
|
||||
css << "icon-bookmark"
|
||||
method = "delete"
|
||||
text = l(:button_project_bookmark_delete)
|
||||
else
|
||||
css << "icon-bookmark-off"
|
||||
method = "post"
|
||||
text = l(:button_project_bookmark)
|
||||
end
|
||||
|
||||
url = bookmark_project_path(project)
|
||||
link_to text, url, remote: true, method: method, class: css
|
||||
end
|
||||
|
||||
def grouped_project_list(projects, query, &block)
|
||||
ancestors = []
|
||||
grouped_query_results(projects, query) do |project, group_name, group_count, group_totals|
|
||||
ancestors.pop while ancestors.any? && !project.is_descendant_of?(ancestors.last)
|
||||
yield project, ancestors.size, group_name, group_count, group_totals
|
||||
ancestors << project unless project.leaf?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,62 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# 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 ProjectsQueriesHelper
|
||||
include ApplicationHelper
|
||||
|
||||
def column_value(column, item, value)
|
||||
if item.is_a?(Project)
|
||||
case column.name
|
||||
when :name
|
||||
link_to_project(item) + (content_tag('span', '', :class => 'icon icon-user my-project', :title => l(:label_my_projects)) if User.current.member_of?(item))
|
||||
when :short_description
|
||||
item.description? ? content_tag('div', textilizable(item, :short_description), :class => "wiki") : ''
|
||||
when :homepage
|
||||
item.homepage? ? content_tag('div', textilizable(item, :homepage), :class => "wiki") : ''
|
||||
when :status
|
||||
get_project_status_label[column.value_object(item)]
|
||||
when :parent_id
|
||||
link_to_project(item.parent) unless item.parent.nil?
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def csv_value(column, object, value)
|
||||
if object.is_a?(Project)
|
||||
case column.name
|
||||
when :status
|
||||
get_project_status_label[column.value_object(object)]
|
||||
when :parent_id
|
||||
object.parent.name unless object.parent.nil?
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_project_status_label
|
||||
{
|
||||
Project::STATUS_ACTIVE => l(:project_status_active),
|
||||
Project::STATUS_CLOSED => l(:project_status_closed)
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,443 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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.
|
||||
|
||||
require 'redmine/export/csv'
|
||||
|
||||
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 /^cf_\d+\./.match?(field)
|
||||
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
|
||||
elsif %w(estimated_hours spent_time).include?(field)
|
||||
group = :label_time_tracking
|
||||
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, options={})
|
||||
tag_name = (options[:name] || 't') + '[]'
|
||||
tags = ''.html_safe
|
||||
query.available_totalable_columns.each do |column|
|
||||
tags << content_tag('label', check_box_tag(tag_name, column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
|
||||
end
|
||||
tags << hidden_field_tag(tag_name, '')
|
||||
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 available_display_types_tags(query)
|
||||
tags = ''.html_safe
|
||||
query.available_display_types.each do |t|
|
||||
tags << radio_button_tag('display_type', t, @query.display_type == t, :id => "display_type_#{t}") +
|
||||
content_tag('label', l(:"label_display_type_#{t}"), :for => "display_type_#{t}", :class => "inline")
|
||||
end
|
||||
tags
|
||||
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.group_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, :total_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 icon icon-sorted-desc'
|
||||
order = 'desc'
|
||||
else
|
||||
css = 'sort desc icon icon-sorted-asc'
|
||||
order = 'asc'
|
||||
end
|
||||
end
|
||||
param_key = options[:sort_param] || :sort
|
||||
sort_param = {param_key => query.sort_criteria.add(column.name, order).to_param}
|
||||
sort_param = {$1 => {$2 => sort_param.values.first}} while sort_param.keys.first.to_s =~ /^(.+)\[(.+)\]$/
|
||||
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, :class => column.css_classes)
|
||||
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, :total_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.visible? ? "#{value.tracker} ##{value.id}: #{value.subject}" : "##{value.id}"
|
||||
else
|
||||
value.id
|
||||
end
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def query_to_csv(items, query, options={})
|
||||
columns = query.columns
|
||||
|
||||
Redmine::Export::CSV.generate(:encoding => params[:encoding]) 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, options={})
|
||||
session_key = klass.name.underscore.to_sym
|
||||
|
||||
if params[:query_id].present?
|
||||
scope = klass.where(:project_id => nil)
|
||||
scope = scope.or(klass.where(:project_id => @project)) if @project
|
||||
@query = scope.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, options[:defaults])
|
||||
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 =
|
||||
if controller_name == 'issues'
|
||||
{:controller => 'issues', :action => 'index', :project_id => @project}
|
||||
else
|
||||
{}
|
||||
end
|
||||
content_tag('h3', title) + "\n" +
|
||||
content_tag(
|
||||
'ul',
|
||||
queries.collect {|query|
|
||||
css = +'query'
|
||||
clear_link = +''
|
||||
if query == @query
|
||||
css << ' selected'
|
||||
clear_link += link_to_clear_query
|
||||
end
|
||||
content_tag('li',
|
||||
link_to(query.name,
|
||||
url_params.merge(:query_id => query),
|
||||
:class => css) +
|
||||
clear_link.html_safe)
|
||||
}.join("\n").html_safe,
|
||||
:class => 'queries'
|
||||
) + "\n"
|
||||
end
|
||||
|
||||
def link_to_clear_query
|
||||
link_to(
|
||||
l(:button_clear),
|
||||
{:set_filter => 1, :sort => '', :project_id => @project},
|
||||
:class => 'icon-only icon-clear-query',
|
||||
:title => l(:button_clear)
|
||||
)
|
||||
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
|
|
@ -1,43 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,310 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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_pagination
|
||||
pagination_links_each @paginator do |text, parameters, options|
|
||||
if entry = @entries[parameters[:page] - 1]
|
||||
ent_path = Redmine::CodesetUtil.replace_invalid_utf8(entry.path)
|
||||
link_to text, {action: 'entry', id: @project, repository_id: @repository.identifier_param, path: to_path_param(ent_path), rev: @rev}
|
||||
end
|
||||
end if @paginator
|
||||
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 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_by!(&: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
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,85 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,216 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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 => 'users', :partial => 'settings/users', :label => :label_user_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
|
||||
|
||||
def gravatar_default_setting_options
|
||||
[['Identicons', 'identicon'],
|
||||
['Monster ids', 'monsterid'],
|
||||
['Mystery man', 'mm'],
|
||||
['Retro', 'retro'],
|
||||
['Robohash', 'robohash'],
|
||||
['Wavatars', 'wavatar']]
|
||||
end
|
||||
end
|
|
@ -1,162 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# 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 icon icon-sorted-desc'
|
||||
order = 'desc'
|
||||
else
|
||||
css = 'sort desc icon icon-sorted-asc'
|
||||
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
|
|
@ -1,137 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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 user_collection_for_select_options(time_entry)
|
||||
collection = time_entry.assignable_users
|
||||
collection << time_entry.user if time_entry.user && !collection.include?(time_entry.user)
|
||||
principals_options_for_select(collection, time_entry.user_id.to_s)
|
||||
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, html=true)
|
||||
if value.blank?
|
||||
"[#{l(:label_none)}]"
|
||||
elsif k = criteria_options[:klass]
|
||||
obj = k.find_by_id(value.to_i)
|
||||
if obj.is_a?(Issue)
|
||||
if obj.visible?
|
||||
html ? link_to_issue(obj) : "#{obj.tracker} ##{obj.id}: #{obj.subject}"
|
||||
else
|
||||
"##{obj.id}"
|
||||
end
|
||||
else
|
||||
format_object(obj, html)
|
||||
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(:encoding => params[:encoding]) do |csv|
|
||||
# Column headers
|
||||
headers = report.criteria.collect {|criteria| l_or_humanize(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, false).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
|
||||
|
||||
def cancel_button_tag_for_time_entry(project)
|
||||
fallback_path = project ? project_time_entries_path(project) : time_entries_path
|
||||
cancel_button_tag(fallback_path)
|
||||
end
|
||||
|
||||
end
|
|
@ -1,27 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
||||
def tracker_name_tag(tracker)
|
||||
title = tracker.description.presence
|
||||
css = title ? "field-description" : nil
|
||||
content_tag 'span', tracker.name, :class => css, :title => title
|
||||
end
|
||||
end
|
|
@ -1,100 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
||||
include ApplicationHelper
|
||||
|
||||
def users_status_options_for_select(selected)
|
||||
user_count_by_status = User.group('status').count.to_hash
|
||||
options_for_select([[l(:label_all), '']] + (User.valid_statuses.map {|c| ["#{l('status_' + User::LABEL_BY_STATUS[c])} (#{user_count_by_status[c].to_i})", c]}), 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 history_default_tab_options
|
||||
[[l('label_issue_history_notes'), 'notes'],
|
||||
[l('label_history'), 'history'],
|
||||
[l('label_issue_history_properties'), 'properties'],
|
||||
[l('label_time_entry_plural'), 'time_entries'],
|
||||
[l('label_associated_revisions'), 'changesets'],
|
||||
[l('label_last_tab_visited'), 'last_tab_visited']]
|
||||
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
|
||||
|
||||
def users_to_csv(users)
|
||||
Redmine::Export::CSV.generate(:encoding => params[:encoding]) do |csv|
|
||||
columns = [
|
||||
'login',
|
||||
'firstname',
|
||||
'lastname',
|
||||
'mail',
|
||||
'admin',
|
||||
'created_on',
|
||||
'last_login_on',
|
||||
'status'
|
||||
]
|
||||
|
||||
# csv header fields
|
||||
csv << columns.map{|column| l('field_' + column)}
|
||||
# csv lines
|
||||
users.each do |user|
|
||||
csv << columns.map do |column|
|
||||
if column == 'status'
|
||||
l(("status_#{User::LABEL_BY_STATUS[user.status]}"))
|
||||
else
|
||||
format_object(user.send(column), false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,107 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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.visible_fixed_issues.group(criteria).count.each {|c,s| h[c][0] = s}
|
||||
# Open issues count
|
||||
version.visible_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
|
||||
sorted_keys =
|
||||
h.keys.sort {|a, b|
|
||||
if a.nil?
|
||||
1
|
||||
else
|
||||
b.nil? ? -1 : a <=> b
|
||||
end
|
||||
}
|
||||
counts =
|
||||
sorted_keys.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
|
||||
|
||||
def link_to_new_issue(version, project)
|
||||
if version.open? && User.current.allowed_to?(:add_issues, project)
|
||||
trackers = Issue.allowed_target_trackers(project)
|
||||
|
||||
unless trackers.empty?
|
||||
issue = Issue.new(:project => project)
|
||||
new_issue_tracker = trackers.detect do |tracker|
|
||||
issue.tracker = tracker
|
||||
issue.safe_attribute?('fixed_version_id')
|
||||
end
|
||||
end
|
||||
|
||||
if new_issue_tracker
|
||||
attrs = {
|
||||
:tracker_id => new_issue_tracker,
|
||||
:fixed_version_id => version.id
|
||||
}
|
||||
link_to l(:label_issue_new), new_project_issue_path(project, :issue => attrs, :back_url => version_path(version)), :class => 'icon icon-add'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,80 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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
|
|
@ -1,71 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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) ? (' ' * level * 2 + '» ') : ''
|
||||
|
||||
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
|
||||
|
||||
def wiki_content_update_info(content)
|
||||
l(:label_updated_time_by, :author => link_to_user(content.author), :age => time_tag(content.updated_on)).html_safe
|
||||
end
|
||||
end
|
|
@ -1,100 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 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(transition_count, old_status, new_status, name)
|
||||
w = transition_count
|
||||
tag_name = "transitions[#{ old_status.try(:id) || 0 }][#{new_status.id}][#{name}]"
|
||||
if old_status == new_status
|
||||
check_box_tag(tag_name, "1", true,
|
||||
{:disabled => true, :class => "old-status-#{old_status.try(:id) || 0} new-status-#{new_status.id}"})
|
||||
elsif 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
|
Loading…
Add table
Add a link
Reference in a new issue