Nuevo plugin Additionals 2.0.20

This commit is contained in:
Manuel Cillero 2019-06-16 12:53:09 +02:00
parent a2a901b71b
commit 93e1e28683
354 changed files with 40514 additions and 0 deletions

View file

@ -0,0 +1,39 @@
class AdditionalsAssignToMeController < ApplicationController
before_action :find_issue
helper :additionals_issues
def update
old_user = @issue.assigned_to
user_in_project = @project.assignable_users.detect { |u| u.id == User.current.id }
if old_user == User.current || user_in_project.nil?
redirect_to(issue_path(@issue))
return
end
@issue.init_journal(User.current)
@issue.assigned_to = User.current
if !@issue.save || old_user == @issue.assigned_to
flash[:error] = l(:error_issues_could_not_be_assigned_to_me)
return redirect_to(issue_path(@issue))
end
last_journal = @issue.journals.visible.order(:created_on).last
return redirect_to(issue_path(@issue)) if last_journal.nil?
last_journal = @issue.journals.visible.order(:created_on).last
redirect_to "#{issue_path(@issue)}#change-#{last_journal.id}"
end
private
def find_issue
@issue = Issue.find(params[:issue_id])
raise Unauthorized unless @issue.visible? && @issue.editable?
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
end

View file

@ -0,0 +1,43 @@
class AdditionalsChangeStatusController < ApplicationController
before_action :find_issue
helper :additionals_issues
def update
issue_old_status_id = @issue.status.id
issue_old_user = @issue.assigned_to
new_status_id = params[:new_status_id].to_i
allowed_status = @issue.sidbar_change_status_allowed_to(User.current, new_status_id)
if new_status_id < 1 || @issue.status_id == new_status_id || allowed_status.nil?
redirect_to(issue_path(@issue))
return
end
@issue.init_journal(User.current)
@issue.status_id = new_status_id
@issue.assigned_to = User.current if @issue.status_x_affected?(new_status_id) && issue_old_user != User.current
if !@issue.save || issue_old_status_id == @issue.status_id
flash[:error] = l(:error_issue_status_could_not_changed)
return redirect_to(issue_path(@issue))
end
last_journal = @issue.journals.visible.order(:created_on).last
return redirect_to(issue_path(@issue)) if last_journal.nil?
last_journal = @issue.journals.visible.order(:created_on).last
redirect_to "#{issue_path(@issue)}#change-#{last_journal.id}"
end
private
def find_issue
@issue = Issue.find(params[:issue_id])
raise Unauthorized unless @issue.visible? && @issue.editable?
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
end

View file

@ -0,0 +1,7 @@
class AdditionalsMacrosController < ApplicationController
before_action :require_login
def show
@available_macros = AdditionalsMacro.all
end
end

View file

@ -0,0 +1,46 @@
module AdditionalsFontawesomeHelper
def fontawesome_info_url
s = []
s << l(:label_set_icon_from)
s << link_to('https://fontawesome.com/icons?m=free', 'https://fontawesome.com/icons?m=free', class: 'external')
safe_join(s, ' ')
end
# name = TYPE-FA_NAME, eg. fas_car
# fas_cloud-upload-alt
# far_id-card
# fab_font-awesome
# options = class
# pre_text
# post_text
# title
def font_awesome_icon(name, options = {})
info = AdditionalsFontAwesome.value_info(name)
return '' if info.blank?
post_text = ''
options['aria-hidden'] = 'true'
options[:class] = if options[:class].present?
info[:classes] + ' ' + options[:class]
else
info[:classes]
end
s = []
if options[:pre_text].present?
s << options[:pre_text]
s << ' '
options.delete(:pre_text)
end
if options[:post_text].present?
post_text = options[:post_text]
options.delete(:post_text)
end
s << content_tag('span', '', options)
if post_text.present?
s << ' '
s << post_text
end
safe_join(s)
end
end

View file

@ -0,0 +1,24 @@
module AdditionalsIssuesHelper
def issue_author_options_for_select(project, issue = nil)
authors = project.users.sorted
s = []
return s unless authors.any?
s << content_tag('option', "<< #{l(:label_me)} >>", value: User.current.id) if authors.include?(User.current)
if issue.nil?
s << options_from_collection_for_select(authors, 'id', 'name')
else
s << content_tag('option', issue.author, value: issue.author_id, selected: true) if issue.author && !authors.include?(issue.author)
s << options_from_collection_for_select(authors, 'id', 'name', issue.author_id)
end
safe_join(s)
end
def show_issue_change_author?(issue)
if issue.new_record? && User.current.allowed_to?(:change_new_issue_author, issue.project) ||
issue.persisted? && User.current.allowed_to?(:edit_issue_author, issue.project)
true
end
end
end

View file

@ -0,0 +1,214 @@
module AdditionalsMenuHelper
def additionals_top_menu_setup
return unless User.current.try(:hrm_user_type_id).nil?
if Additionals.setting?(:remove_mypage)
Redmine::MenuManager.map(:top_menu).delete(:my_page) if Redmine::MenuManager.map(:top_menu).exists?(:my_page)
else
handle_top_menu_item(:my_page, url: my_page_path, after: :home, if: proc { User.current.logged? })
end
if Additionals.setting?(:remove_help)
Redmine::MenuManager.map(:top_menu).delete(:help) if Redmine::MenuManager.map(:top_menu).exists?(:help)
elsif User.current.logged?
handle_top_menu_item(:help, url: '#', symbol: 'fas_question', last: true)
@additionals_help_items = additionals_help_menu_items
else
handle_top_menu_item(:help, url: Redmine::Info.help_url, symbol: 'fas_question', last: true)
end
end
def handle_top_menu_item(menu_name, item)
Redmine::MenuManager.map(:top_menu).delete(menu_name.to_sym) if Redmine::MenuManager.map(:top_menu).exists?(menu_name.to_sym)
html_options = {}
html_options[:class] = 'external' if item[:url].include? '://'
html_options[:title] = item[:title] if item[:title].present?
menu_options = { parent: item[:parent].present? ? item[:parent].to_sym : nil,
html: html_options }
menu_options[:if] = menu_options[:if] if menu_options[:if].present?
menu_options[:caption] = if item[:symbol].present? && item[:name].present?
font_awesome_icon(item[:symbol], post_text: item[:name])
elsif item[:symbol].present?
font_awesome_icon(item[:symbol])
elsif item[:name].present?
item[:name].to_s
end
if item[:last].present? && item[:last]
menu_options[:last] = true
elsif item[:before].present?
menu_options[:before] = item[:before]
elsif item[:after].present?
menu_options[:after] = item[:after]
else
menu_options[:before] = :help
end
Redmine::MenuManager.map(:top_menu).push(menu_name, item[:url], menu_options)
end
def render_custom_top_menu_item
items = additionals_build_custom_items
return if items.empty?
user_roles = Role.givable
.joins(:members).where(members: { user_id: User.current.id })
.joins(members: :project).where(projects: { status: Project::STATUS_ACTIVE })
.distinct
.reorder(nil)
.pluck(:id)
items.each do |item|
additionals_custom_top_menu_item(item, user_roles)
end
end
def additionals_build_custom_items
items = []
Additionals::MAX_CUSTOM_MENU_ITEMS.times do |num|
menu_name = "custom_menu#{num}"
item = { menu_name: menu_name.to_sym,
url: Additionals.settings[menu_name + '_url'],
name: Additionals.settings[menu_name + '_name'],
title: Additionals.settings[menu_name + '_title'],
roles: Additionals.settings[menu_name + '_roles'] }
if item[:name].present? && item[:url].present? && item[:roles].present?
items << item
elsif Redmine::MenuManager.map(:top_menu).exists?(item[:menu_name])
Redmine::MenuManager.map(:top_menu).delete(item[:menu_name])
end
end
items
end
def additionals_custom_top_menu_item(item, user_roles)
show_entry = false
item[:roles].each do |role|
if user_roles.empty? && role.to_i == Role::BUILTIN_ANONYMOUS
show_entry = true
break
elsif User.current.logged? && role.to_i == Role::BUILTIN_NON_MEMBER
# if user is logged in and non_member is active in item,
# always show it
show_entry = true
break
end
user_roles.each do |user_role|
if role.to_i == user_role
show_entry = true
break
end
end
break if show_entry == true
end
if show_entry
handle_top_menu_item(item[:menu_name], item)
elsif Redmine::MenuManager.map(:top_menu).exists?(item[:menu_name])
Redmine::MenuManager.map(:top_menu).delete(item[:menu_name])
end
end
def addtionals_help_plugin_items
user_items = [{ title: 'Redmine Guide', url: Redmine::Info.help_url },
{ title: "Redmine #{l(:label_macro_plural)}", url: additionals_macros_path }]
admin_items = [{ title: 'Additionals', url: 'https://additionals.readthedocs.io/en/latest/manual/', manual: true },
{ title: 'Redmine Changelog', url: 'https://www.redmine.org/projects/redmine/wiki/Changelog_3_4' },
{ title: 'Redmine Upgrade', url: 'https://www.redmine.org/projects/redmine/wiki/RedmineUpgrade' },
{ title: 'Redmine Security Advisories', url: 'https://www.redmine.org/projects/redmine/wiki/Security_Advisories' }]
Redmine::Plugin.all.each do |plugin|
next if plugin.id == :additionals
plugin_item_base = nil
begin
plugin_item_base = plugin.id.to_s.camelize.constantize
rescue LoadError
Rails.logger.debug "Ignore plugin #{plugin.id} for help integration"
rescue StandardError => e
raise e unless e.class.to_s == 'NameError'
end
plugin_item = plugin_item_base.try(:additionals_help_items) unless plugin_item_base.nil?
plugin_item = additionals_help_items_fallbacks(plugin.id) if plugin_item.nil?
next if plugin_item.nil?
plugin_item.each do |temp_item|
u_items = if !temp_item[:manual].nil? && temp_item[:manual]
{ title: "#{temp_item[:title]} #{l(:label_help_manual)}", url: temp_item[:url] }
else
{ title: temp_item[:title], url: temp_item[:url] }
end
if !temp_item[:admin].nil? && temp_item[:admin]
admin_items << u_items
else
user_items << u_items
end
end
end
{ user: user_items, admin: admin_items }
end
def additionals_help_menu_items
plugin_items = addtionals_help_plugin_items
pages = plugin_items[:user].sort_by { |k| k[:title] }
if User.current.admin?
pages << { title: '-' }
pages += plugin_items[:admin].sort_by { |k| k[:title] }
end
s = []
pages.each_with_index do |item, idx|
s << if item[:title] == '-'
content_tag(:li, tag(:hr))
else
html_options = { class: 'help_item_' + idx.to_s }
if item[:url].include? '://'
html_options[:class] << ' external'
html_options[:target] = '_blank'
end
content_tag(:li,
link_to(item[:title], item[:url], html_options))
end
end
safe_join(s)
end
# Plugin help items definition for plugins,
# which do not have additionals_help_menu_items integration
def additionals_help_items_fallbacks(plugin_id)
plugins = { redmine_wiki_lists: [{ title: 'Wiki Lists Macros',
url: 'https://www.r-labs.org/projects/wiki_lists/wiki/Wiki_Lists_en' }],
redmine_wiki_extensions: [{ title: 'Wiki Extensions',
url: 'https://www.r-labs.org/projects/r-labs/wiki/Wiki_Extensions_en' }],
redmine_git_hosting: [{ title: 'Redmine Git Hosting',
url: 'http://redmine-git-hosting.io/get_started/',
admin: true }],
redmine_contacts: [{ title: 'Redmine CRM',
url: 'https://www.redmineup.com/pages/help/crm',
admin: true }],
redmine_contacts_helpdesk: [{ title: 'Redmine Helpdesk',
url: 'https://www.redmineup.com/pages/help/helpdesk',
admin: true }],
redmine_ldap_sync: [{ title: 'Redmine LDAP',
url: 'https://www.redmine.org/projects/redmine/wiki/RedmineLDAP',
admin: true },
{ title: 'Redmine LDAP Sync',
url: 'https://github.com/thorin/redmine_ldap_sync/blob/master/README.md',
admin: true }] }
plugins[plugin_id]
end
end

View file

@ -0,0 +1,261 @@
module AdditionalsQueriesHelper
def additionals_query_session_key(object_type)
"#{object_type}_query".to_sym
end
def additionals_retrieve_query(object_type, options = {})
session_key = additionals_query_session_key(object_type)
query_class = Object.const_get("#{object_type.camelcase}Query")
if params[:query_id].present?
additionals_load_query_id(query_class, session_key, params[:query_id], options, object_type)
elsif api_request? ||
params[:set_filter] ||
session[session_key].nil? ||
session[session_key][:project_id] != (@project ? @project.id : nil)
# Give it a name, required to be valid
@query = query_class.new(name: '_')
@query.project = @project
@query.user_filter = options[:user_filter] if options[:user_filter]
@query.build_from_params(params)
session[session_key] = { project_id: @query.project_id }
# session has a limit to 4k, we have to use a cache for it for larger data
Rails.cache.write(additionals_query_cache_key(object_type),
filters: @query.filters,
group_by: @query.group_by,
column_names: @query.column_names,
totalable_names: @query.totalable_names,
sort_criteria: params[:sort].presence || @query.sort_criteria.to_a)
else
# retrieve from session
@query = query_class.find_by(id: session[session_key][:id]) if session[session_key][:id]
session_data = Rails.cache.read(additionals_query_cache_key(object_type))
@query ||= query_class.new(name: '_',
filters: session_data.nil? ? nil : session_data[:filters],
group_by: session_data.nil? ? nil : session_data[:group_by],
column_names: session_data.nil? ? nil : session_data[:column_names],
totalable_names: session_data.nil? ? nil : session_data[:totalable_names],
sort_criteria: params[:sort].presence || (session_data.nil? ? nil : session_data[:sort_criteria]))
@query.project = @project
if params[:sort].present?
@query.sort_criteria = params[:sort]
# we have to write cache for sort order
Rails.cache.write(additionals_query_cache_key(object_type),
filters: @query.filters,
group_by: @query.group_by,
column_names: @query.column_names,
totalable_names: @query.totalable_names,
sort_criteria: params[:sort])
elsif session_data.present?
@query.sort_criteria = session_data[:sort_criteria]
end
end
end
def additionals_load_query_id(query_class, session_key, query_id, options, object_type)
cond = 'project_id IS NULL'
cond << " OR project_id = #{@project.id}" if @project
@query = query_class.where(cond).find(query_id)
raise ::Unauthorized unless @query.visible?
@query.project = @project
@query.user_filter = options[:user_filter] if options[:user_filter]
session[session_key] = { id: @query.id, project_id: @query.project_id }
@query.sort_criteria = params[:sort] if params[:sort].present?
# we have to write cache for sort order
Rails.cache.write(additionals_query_cache_key(object_type),
filters: @query.filters,
group_by: @query.group_by,
column_names: @query.column_names,
totalable_names: @query.totalable_names,
sort_criteria: @query.sort_criteria)
end
def additionals_query_cache_key(object_type)
project_id = @project.nil? ? 0 : @project.id
"#{object_type}_query_data_#{session.id}_#{project_id}"
end
def additionals_select2_search_users(where_filter = '', where_params = {})
q = params[:q].to_s.strip
exclude_id = params[:user_id].to_i
scope = User.active.where(type: 'User')
scope = scope.where.not(id: exclude_id) if exclude_id > 0
scope = scope.where(where_filter, where_params) if where_filter.present?
scope = scope.like(q) if q.present?
scope = scope.order(last_login_on: :desc)
.limit(params[:limit] || Additionals::SELECT2_INIT_ENTRIES)
@users = scope.to_a.sort! { |x, y| x.name <=> y.name }
render layout: false, partial: 'auto_completes/additionals_users'
end
def additionals_query_to_xlsx(items, query, options = {})
require 'write_xlsx'
columns = if options[:columns].present? || options[:c].present?
query.available_columns
else
query.columns
end
stream = StringIO.new('')
export_to_xlsx(items, columns, filename: stream)
stream.string
end
def additionals_result_to_xlsx(items, columns, options = {})
raise 'option filename is mission' if options[:filename].blank?
require 'write_xlsx'
export_to_xlsx(items, columns, options)
end
def export_to_xlsx(items, columns, options)
workbook = WriteXLSX.new(options[:filename])
worksheet = workbook.add_worksheet
# Freeze header row and # column.
freeze_row = options[:freeze_first_row].nil? || options[:freeze_first_row] ? 1 : 0
freeze_column = options[:freeze_first_column].nil? || options[:freeze_first_column] ? 1 : 0
worksheet.freeze_panes(freeze_row, freeze_column)
options[:columns_width] = if options[:xlsx_write_header_row].present?
send(options[:xlsx_write_header_row], workbook, worksheet, columns)
else
xlsx_write_header_row(workbook, worksheet, columns)
end
options[:columns_width] = if options[:xlsx_write_item_rows].present?
send(options[:xlsx_write_item_rows], workbook, worksheet, columns, items, options)
else
xlsx_write_item_rows(workbook, worksheet, columns, items, options)
end
columns.size.times do |index|
worksheet.set_column(index, index, options[:columns_width][index])
end
workbook.close
end
def xlsx_write_header_row(workbook, worksheet, columns)
columns_width = []
columns.each_with_index do |c, index|
value = if c.class.name == 'String'
c
else
c.caption.to_s
end
worksheet.write(0, index, value, workbook.add_format(xlsx_cell_format(:header)))
columns_width << xlsx_get_column_width(value)
end
columns_width
end
def xlsx_write_item_rows(workbook, worksheet, columns, items, options = {})
hyperlink_format = workbook.add_format(xlsx_cell_format(:link))
even_text_format = workbook.add_format(xlsx_cell_format(:text, '', 0))
even_text_format.set_num_format(0x31)
odd_text_format = workbook.add_format(xlsx_cell_format(:text, '', 1))
odd_text_format.set_num_format(0x31)
items.each_with_index do |line, line_index|
columns.each_with_index do |c, column_index|
value = csv_content(c, line)
if c.name == :id # ID
link = url_for(controller: line.class.name.underscore.pluralize, action: 'show', id: line.id)
worksheet.write(line_index + 1, column_index, link, hyperlink_format, value)
elsif xlsx_hyperlink_cell?(value)
worksheet.write(line_index + 1, column_index, value, hyperlink_format, value)
elsif !c.inline?
# block column can be multiline strings
value.gsub!("\r\n", "\n")
text_format = line_index.even? ? even_text_format : odd_text_format
worksheet.write_rich_string(line_index + 1, column_index, value, text_format)
else
worksheet.write(line_index + 1,
column_index,
value,
workbook.add_format(xlsx_cell_format(:cell, value, line_index)))
end
width = xlsx_get_column_width(value)
options[:columns_width][column_index] = width if options[:columns_width][column_index] < width
end
end
options[:columns_width]
end
def xlsx_get_column_width(value)
value_str = value.to_s
# 1.1: margin
width = (value_str.length + value_str.chars.reject(&:ascii_only?).length) * 1.1 + 1
# 30: max width
width > 30 ? 30 : width
end
def xlsx_cell_format(type, value = 0, index = 0)
format = { border: 1, text_wrap: 1, valign: 'top' }
case type
when :header
format[:bold] = 1
format[:color] = 'white'
format[:bg_color] = 'gray'
when :link
format[:color] = 'blue'
format[:underline] = 1
format[:bg_color] = 'silver' unless index.even?
else
format[:bg_color] = 'silver' unless index.even?
format[:color] = 'red' if value.is_a?(Numeric) && value < 0
end
format
end
def xlsx_hyperlink_cell?(token)
# Match http, https or ftp URL
if token =~ %r{\A[fh]tt?ps?://}
true
# Match mailto:
elsif token.present? && token.start_with?('mailto:')
true
# Match internal or external sheet link
elsif token =~ /\A(?:in|ex)ternal:/
true
end
end
# Returns the query definition as hidden field tags
# columns in ignored_column_names are skipped (names as symbols)
# TODO: this is a temporary fix and should be removed
# after https://www.redmine.org/issues/29830 is in Redmine core.
def query_as_hidden_field_tags(query, ignored_column_names = nil)
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|
next if ignored_column_names.present? && ignored_column_names.include?(column.name)
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
tags << hidden_field_tag('group_by', query.group_by, id: nil) if query.group_by.present?
tags << hidden_field_tag('sort', query.sort_criteria.to_param, id: nil) if query.sort_criteria.present?
tags
end
end

View file

@ -0,0 +1,125 @@
require 'digest/md5'
module AdditionalsTagHelper
# deprecated: this will removed after a while
def render_additionals_tags_list(tags, options = {})
additionals_tag_cloud(tags, options)
end
# deprecated: this will removed after a while
def render_additionals_tag_link_line(tag_list)
additionals_tag_links(tag_list)
end
def additionals_tag_cloud(tags, options = {})
return if tags.blank?
options[:show_count] = true
# prevent ActsAsTaggableOn::TagsHelper from calling `all`
# otherwise we will need sort tags after `tag_cloud`
tags = tags.all if tags.respond_to?(:all)
s = []
tags.each do |tag|
s << additionals_tag_link(tag, options)
end
sep = if options[:tags_without_color]
', '
else
' '
end
content_tag(:div, safe_join(s, sep), class: 'tags')
end
# plain list of tags
def render_additionals_tags(tags, sep = ' ')
s = if tags.blank?
['']
else
tags.map(&:name)
end
s.join(sep)
end
def additionals_tag_links(tag_list, options = {})
return unless tag_list
sep = if options[:tags_without_color]
', '
else
' '
end
safe_join(tag_list.map do |tag|
additionals_tag_link(tag, options)
end, sep)
end
def additionals_tag_link(tag, options = {})
tag_name = []
tag_name << tag.name
unless options[:tags_without_color]
tag_bg_color = additionals_tag_color(tag.name)
tag_fg_color = additionals_tag_fg_color(tag_bg_color)
tag_style = "background-color: #{tag_bg_color}; color: #{tag_fg_color}"
end
tag_name << content_tag('span', "(#{tag.count})", class: 'tag-count') if options[:show_count]
if options[:tags_without_color]
content_tag('span',
link_to(safe_join(tag_name), additionals_tag_url(tag.name, options)),
class: 'tag-label')
else
content_tag('span',
link_to(safe_join(tag_name),
additionals_tag_url(tag.name, options),
style: tag_style),
class: 'additionals-tag-label-color',
style: tag_style)
end
end
def additionals_tag_url(tag_name, options = {})
action = options[:tag_action].presence || (controller_name == 'hrm_user_resources' ? 'show' : 'index')
{ controller: options[:tag_controller].presence || controller_name,
action: action,
set_filter: 1,
project_id: @project,
fields: [:tags],
values: { tags: [tag_name] },
operators: { tags: '=' } }
end
private
def tag_cloud(tags, classes)
return [] if tags.empty?
max_count = tags.max_by(&:count).count.to_f
tags.each do |tag|
index = ((tag.count / max_count) * (classes.size - 1))
yield tag, classes[index.nan? ? 0 : index.round]
end
end
def additionals_tag_color(tag_name)
"##{Digest::MD5.hexdigest(tag_name)[0..5]}"
end
def additionals_tag_fg_color(bg_color)
# calculate contrast text color according to YIQ method
# https://24ways.org/2010/calculating-color-contrast/
# https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color
r = bg_color[1..2].hex
g = bg_color[3..4].hex
b = bg_color[5..6].hex
(r * 299 + g * 587 + b * 114) >= 128_000 ? 'black' : 'white'
end
end

View file

@ -0,0 +1,35 @@
module AdditionalsWikiPdfHelper
include Redmine::Export::PDF
def wiki_page_to_pdf(page, project)
pdf = ITCPDF.new(current_language)
pdf.set_title("#{project} - #{page.title}")
pdf.alias_nb_pages
pdf.footer_date = format_date(User.current.today)
pdf.add_page
unless Additionals.setting?(:wiki_pdf_remove_title)
pdf.SetFontStyle('B', 11)
pdf.RDMMultiCell(190, 5,
"#{project} - #{page.title} - # #{page.content.version}")
end
pdf.ln
# Set resize image scale
pdf.set_image_scale(1.6)
pdf.SetFontStyle('', 9)
if Additionals.setting?(:wiki_pdf_remove_attachments)
pdf.RDMwriteFormattedCell(190,
5,
'',
'',
textilizable(page.content,
:text,
only_path: false,
edit_section_links: false,
headings: false,
inline_attachments: false), page.attachments)
else
write_wiki_page(pdf, page)
end
pdf.output
end
end

View file

@ -0,0 +1,118 @@
class AdditionalsFontAwesome
include Redmine::I18n
class << self
def load_icons(type)
data = YAML.safe_load(ERB.new(IO.read(Rails.root.join('plugins',
'additionals',
'config',
'fontawesome_icons.yml'))).result) || {}
icons = {}
data.each do |key, values|
icons[key] = { unicode: values['unicode'], label: values['label'] } if values['styles'].include?(convert_type2style(type))
end
icons
end
def convert_type2style(type)
case type
when :fab
'brands'
when :far
'regular'
else
'solid'
end
end
def font_weight(key)
case key
when :fas
900
else
'normal'
end
end
def font_family(key)
case key
when :fab
'Font Awesome\ 5 Brands'
else
'Font Awesome\ 5 Free'
end
end
def key2value(key, type)
"fa#{type}_" + key
end
def classes(value)
info = value_info(value)
return '' if info.blank?
info[:classes]
end
def json_values(type)
FONTAWESOME_ICONS[type].collect { |fa_symbol, values| { id: key2value(fa_symbol, type[-1]), text: values[:label] } }
end
def select_values(type)
FONTAWESOME_ICONS[type].collect { |fa_symbol, values| [values[:label], key2value(fa_symbol, type[-1])] }
end
def json_for_select
[{ text: l(:label_fontawesome_regular), children: json_values(:far) },
{ text: l(:label_fontawesome_solid), children: json_values(:fas) },
{ text: l(:label_fontawesome_brands), children: json_values(:fab) }].to_json
end
# show only one value as current selected
# (all other options are retrieved by select2
def active_option_for_select(selected)
info = value_info(selected, with_details: true)
return [] if info.blank?
[[info[:label], selected]]
end
def options_for_select
[[l(:label_fontawesome_regular), select_values(:far)],
[l(:label_fontawesome_solid), select_values(:fas)],
[l(:label_fontawesome_brands), select_values(:fab)]]
end
def value_info(value, options = {})
return {} if value.blank?
values = value.split('_')
return {} unless values.count == 2
info = { type: values[0].to_sym,
name: "fa-#{values[1]}" }
info[:classes] = "#{info[:type]} #{info[:name]}"
info[:font_weight] = font_weight(info[:type])
info[:font_family] = font_family(info[:type])
if options[:with_details]
info.merge!(load_details(info[:type], values[1]))
return {} if info[:unicode].blank?
end
info
end
private
def load_details(type, name)
return {} unless FONTAWESOME_ICONS.key?(type)
values = FONTAWESOME_ICONS[type][name]
return {} if values.blank?
{ unicode: "&#x#{values[:unicode]};".html_safe, label: values[:label] } # rubocop:disable Rails/OutputSafety
end
end
end

View file

@ -0,0 +1,43 @@
class AdditionalsImport < Import
class_attribute :import_class
# Returns the objects that were imported
def saved_objects
object_ids = saved_items.pluck(:obj_id)
import_class.where(id: object_ids).order(:id)
end
def project=(project)
settings['project'] = project
end
def project
settings['project']
end
def mappable_custom_fields
object = import_class.new
@custom_fields = object.custom_field_values.map(&:custom_field)
end
def build_custom_field_attributes(object, row)
object.custom_field_values.each_with_object({}) do |v, h|
value = case v.custom_field.field_format
when 'date'
row_date(row, "cf_#{v.custom_field.id}")
when 'list'
row_value(row, "cf_#{v.custom_field.id}").try(:split, ',')
else
row_value(row, "cf_#{v.custom_field.id}")
end
next unless value
h[v.custom_field.id.to_s] =
if value.is_a?(Array)
value.map { |val| v.custom_field.value_from_keyword(val.strip, object) }.compact.flatten
else
v.custom_field.value_from_keyword(value, object)
end
end
end
end

View file

@ -0,0 +1,75 @@
class AdditionalsMacro
def self.all(options = {})
all = Redmine::WikiFormatting::Macros.available_macros
macros = {}
macro_list = []
# needs to run every request (for each user once)
permissions = build_permissions(options)
if options[:filtered].present?
options[:filtered] << 'hello_world'
else
options[:filtered] = ['hello_world']
end
all.each do |macro, macro_options|
next if options[:filtered].include?(macro.to_s)
next unless macro_allowed(macro, permissions)
macro_list << macro.to_s
macros[macro] = macro_options
end
if options[:only_names]
macro_list.sort
else
macros.sort
end
end
def self.macro_allowed(macro, permissions)
permissions.each do |permission|
next if permission[:list].exclude?(macro)
return false unless permission[:access]
end
true
end
def self.build_permissions(options)
gpermission = []
macro_permissions.each do |permission|
permission[:access] = if options[:controller_only] &&
permission[:controller].present? &&
options[:controller_only].to_sym != permission[:controller]
false
else
User.current.allowed_to?(permission[:permission], nil, global: true)
end
gpermission << permission
end
gpermission
end
def self.macro_permissions
[{ list: %i[issue issue_name_link],
permission: :view_issues },
{ list: %i[password password_query password_tag password_tag_count],
permission: :view_passwords },
{ list: %i[contact deal contact_avatar contact_note contact_plain],
permission: :view_contacts },
{ list: %i[db db_query db_tag db_tag_count],
permission: :view_db_entries },
{ list: %i[child_pages calendar last_updated_at last_updated_by lastupdated_at lastupdated_by
new_page recently_updated recent comments comment_form tags taggedpages tagcloud
show_count count vote show_vote terms_accept terms_reject],
permission: :view_wiki_pages,
controller: :wiki },
{ list: %i[mail send_file],
permission: :view_helpdesk_tickets },
{ list: %i[kb article_id article category],
permission: :view_kb_articles }]
end
end

View file

@ -0,0 +1,150 @@
module AdditionalsQuery
def self.included(base)
base.send :include, InstanceMethods
end
module InstanceMethods
def initialize_ids_filter(options = {})
if options[:label]
add_available_filter 'ids', type: :integer, label: options[:label]
else
add_available_filter 'ids', type: :integer, name: '#'
end
end
def sql_for_ids_field(_field, operator, value)
if operator == '='
# accepts a comma separated list of ids
ids = value.first.to_s.scan(/\d+/).map(&:to_i)
if ids.present?
"#{queried_table_name}.id IN (#{ids.join(',')})"
else
'1=0'
end
else
sql_for_field('id', operator, value, queried_table_name, 'id')
end
end
def initialize_project_filter(options = {})
if project.nil?
add_available_filter('project_id', order: options[:position],
type: :list,
values: -> { project_values })
end
return if project.nil? || project.leaf? || subproject_values.empty?
add_available_filter('subproject_id', order: options[:position],
type: :list_subprojects,
values: -> { subproject_values })
end
def initialize_created_filter(options = {})
add_available_filter 'created_on', order: options[:position],
type: :date_past,
label: options[:label].presence
end
def initialize_updated_filter(options = {})
add_available_filter 'updated_on', order: options[:position],
type: :date_past,
label: options[:label].presence
end
def initialize_tags_filter(options = {})
values = if project
queried_class.available_tags(project: project.id)
else
queried_class.available_tags
end
return if values.blank?
add_available_filter 'tags', order: options[:position],
type: :list,
values: values.collect { |t| [t.name, t.name] }
end
def initialize_author_filter(options = {})
return if author_values.empty?
add_available_filter('author_id', order: options[:position],
type: :list_optional,
values: options[:no_lambda].nil? ? author_values : -> { author_values })
end
def initialize_assignee_filter(options = {})
return if author_values.empty?
add_available_filter('assigned_to_id', order: options[:position],
type: :list_optional,
values: options[:no_lambda] ? author_values : -> { author_values })
end
def initialize_watcher_filter(options = {})
return if watcher_values.empty? || !User.current.logged?
add_available_filter('watcher_id', order: options[:position],
type: :list,
values: options[:no_lambda] ? watcher_values : -> { watcher_values })
end
def watcher_values
watcher_values = [["<< #{l(:label_me)} >>", 'me']]
watcher_values += users.collect { |s| [s.name, s.id.to_s] } if User.current.allowed_to?(:manage_public_queries, project, global: true)
watcher_values
end
def sql_for_watcher_id_field(field, operator, value)
watchable_type = queried_class == User ? 'Principal' : queried_class.to_s
db_table = Watcher.table_name
"#{queried_table_name}.id #{operator == '=' ? 'IN' : 'NOT IN'}
(SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='#{watchable_type}' AND " +
sql_for_field(field, '=', value, db_table, 'user_id') + ')'
end
def sql_for_tags_field(field, _operator, value)
AdditionalsTag.sql_for_tags_field(queried_class, operator_for(field), value)
end
def sql_for_is_private_field(_field, operator, value)
if bool_operator(operator, value)
return '1=1' if value.count > 1
"#{queried_table_name}.is_private = #{self.class.connection.quoted_true}"
else
return '1=0' if value.count > 1
"#{queried_table_name}.is_private = #{self.class.connection.quoted_false}"
end
end
# use for list fields with to values 1 (true) and 0 (false)
def bool_operator(operator, values)
operator == '=' && values.first == '1' || operator != '=' && values.first != '1'
end
# use for list
def bool_values
[[l(:general_text_yes), '1'], [l(:general_text_no), '0']]
end
def query_count
objects_scope.count
rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid, e.message
end
def results_scope(options = {})
order_option = [group_by_sort_order, (options[:order] || sort_clause)].flatten.reject(&:blank?)
objects_scope(options)
.order(order_option)
.joins(joins_for_order_statement(order_option.join(',')))
.limit(options[:limit])
.offset(options[:offset])
rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid, e.message
end
end
end

View file

@ -0,0 +1,70 @@
class AdditionalsTag
TAG_TABLE_NAME = RedmineCrm::Tag.table_name if defined? RedmineCrm
TAGGING_TABLE_NAME = RedmineCrm::Tagging.table_name if defined? RedmineCrm
PROJECT_TABLE_NAME = Project.table_name
def self.get_available_tags(klass, options = {})
scope = RedmineCrm::Tag.where({})
scope = scope.where("#{PROJECT_TABLE_NAME}.id = ?", options[:project]) if options[:project]
if options[:permission]
scope = scope.where(tag_access(options[:permission]))
elsif options[:visible_condition]
scope = scope.where(klass.visible_condition(User.current))
end
scope = scope.where("LOWER(#{TAG_TABLE_NAME}.name) LIKE ?", "%#{options[:name_like].downcase}%") if options[:name_like]
scope = scope.where("#{TAG_TABLE_NAME}.name=?", options[:name]) if options[:name]
scope = scope.where("#{TAGGING_TABLE_NAME}.taggable_id!=?", options[:exclude_id]) if options[:exclude_id]
scope = scope.where(options[:where_field] => options[:where_value]) if options[:where_field].present? && options[:where_value]
scope = scope.select("#{TAG_TABLE_NAME}.*, COUNT(DISTINCT #{TAGGING_TABLE_NAME}.taggable_id) AS count")
scope = scope.joins(tag_joins(klass, options))
scope = scope.group("#{TAG_TABLE_NAME}.id, #{TAG_TABLE_NAME}.name").having('COUNT(*) > 0')
scope = scope.order("#{TAG_TABLE_NAME}.name")
scope
end
def self.tag_joins(klass, options = {})
table_name = klass.table_name
joins = ["JOIN #{TAGGING_TABLE_NAME} ON #{TAGGING_TABLE_NAME}.tag_id = #{TAG_TABLE_NAME}.id"]
joins << "JOIN #{table_name} " \
"ON #{table_name}.id = #{TAGGING_TABLE_NAME}.taggable_id AND #{TAGGING_TABLE_NAME}.taggable_type = '#{klass}'"
if options[:project_join]
joins << options[:project_join]
elsif options[:project] || !options[:without_projects]
joins << "JOIN #{PROJECT_TABLE_NAME} ON #{table_name}.project_id = #{PROJECT_TABLE_NAME}.id"
end
joins
end
def self.tag_access(permission)
projects_allowed = if permission.nil?
Project.visible.pluck(:id)
else
Project.where(Project.allowed_to_condition(User.current, permission)).pluck(:id)
end
if projects_allowed.present?
"#{PROJECT_TABLE_NAME}.id IN (#{projects_allowed.join(',')})" unless projects_allowed.empty?
else
'1=0'
end
end
def self.remove_unused_tags
unused = RedmineCrm::Tag.find_by_sql(<<-SQL)
SELECT * FROM tags WHERE id NOT IN (
SELECT DISTINCT tag_id FROM taggings
)
SQL
unused.each(&:destroy)
end
def self.sql_for_tags_field(klass, operator, value)
compare = operator.eql?('=') ? 'IN' : 'NOT IN'
ids_list = klass.tagged_with(value).collect(&:id).push(0).join(',')
"( #{klass.table_name}.id #{compare} (#{ids_list}) ) "
end
end

View file

@ -0,0 +1,5 @@
Deface::Override.new virtual_path: 'account/register',
name: 'add-invisble-captcha',
insert_top: 'div.box',
original: 'e64d82c46cc3322e4d953aa119d1e71e81854158',
partial: 'account/invisible_captcha'

View file

@ -0,0 +1,5 @@
Deface::Override.new virtual_path: 'admin/info',
name: 'add-system_info',
insert_after: 'table.list',
original: '73b55ca692bcf4db9ecb7a16ec6d6f9e46f08a90',
partial: 'admin/system_info'

View file

@ -0,0 +1,5 @@
Deface::Override.new virtual_path: 'issues/_action_menu',
name: 'add-issue-assign-to-me',
insert_bottom: 'div.contextual',
original: 'c0a30490bb9ac5c5644e674319f17e40c57034d8',
partial: 'issues/additionals_action_menu'

View file

@ -0,0 +1,5 @@
Deface::Override.new virtual_path: 'layouts/base',
name: 'add-body-header',
insert_before: 'div#wrapper',
original: '4af81ed701989727953cea2e376c9d83665d7eb2',
partial: 'additionals/global_body_header'

View file

@ -0,0 +1,5 @@
Deface::Override.new virtual_path: 'roles/_form',
name: 'roles-form-hide',
insert_before: 'p.manage_members_shown',
original: 'b2a317f49e0b65ae506c8871f0c2bcc3e8098766',
partial: 'roles/additionals_form'

View file

@ -0,0 +1,10 @@
Deface::Override.new virtual_path: 'users/show',
name: 'user-show-info-hook',
insert_top: 'div.splitcontentleft ul:first-child',
original: 'aff8d775275e3f33cc45d72b8e2896144be4beff',
partial: 'hooks/view_users_show'
Deface::Override.new virtual_path: 'users/show',
name: 'user-contextual-hook',
insert_bottom: 'div.contextual',
original: '9d6a7ad6ba0addc68c6b4f6c3b868511bc8eb542',
partial: 'hooks/view_users_contextual'

View file

@ -0,0 +1,15 @@
Deface::Override.new virtual_path: 'welcome/index',
name: 'add-welcome-bottom-content',
insert_after: 'div.splitcontentright',
original: 'dd470844bcaa4d7c9dc66e70e6c0c843d42969bf',
partial: 'welcome/overview_bottom'
Deface::Override.new virtual_path: 'welcome/index',
name: 'add-welcome-top-content',
insert_before: 'div.splitcontentleft',
original: 'e7de0a2e88c5ccb4d1feb7abac239e4b669babed',
partial: 'welcome/overview_top'
Deface::Override.new virtual_path: 'welcome/index',
name: 'remove-welcome-news',
replace: 'div.news',
original: '163f5df8f0cb2d5009d7f57ad38174ed29201a1a',
partial: 'welcome/overview_news'

View file

@ -0,0 +1,5 @@
Deface::Override.new virtual_path: 'wiki/show',
name: 'addto-wiki-show',
insert_before: 'div.contextual',
original: '6b0cb1646d5e2cb23feee1805949e266036581e6',
partial: 'wiki/show_additionals'

View file

@ -0,0 +1,5 @@
Deface::Override.new virtual_path: 'wiki/_sidebar',
name: 'addto-wiki-sidebar',
insert_after: 'ul',
original: '07a5375c015a7d96826c9977c4d8889c4a98bb49',
partial: 'wiki/global_sidebar'

View file

@ -0,0 +1,2 @@
- if Additionals.setting?(:invisible_captcha)
= invisible_captcha

View file

@ -0,0 +1,5 @@
- login_text = Additionals.settings[:account_login_bottom]
- if login_text.present?
br
.login-additionals
= textilizable(login_text)

View file

@ -0,0 +1,9 @@
- footer = Additionals.settings[:global_footer]
- if footer.present?
.additionals-footer
= textilizable(footer)
- if @additionals_help_items.present?
javascript:
$(function() {
$('a.help').parent().append("<ul class=\"menu-children\">#{escape_javascript(@additionals_help_items)}</ul>");
});

View file

@ -0,0 +1,5 @@
- unless (controller_name == 'account' && action_name == 'login') || \
(controller_name == 'my') || \
(controller_name == 'account' && action_name == 'lost_password')
- if Additionals.setting?(:add_go_to_top)
a.gototop[href="#gototop"] = l(:label_go_to_top)

View file

@ -0,0 +1,27 @@
div id="#{export_format}-export-options" style="display: none"
h3.title = l(:label_export_options, export_format: export_format.upcase)
= form_tag(url, method: :get, id: "#{export_format}-export-form") do
- if @query.available_filters.key?('description')
= query_as_hidden_field_tags @query, [:description]
else
= query_as_hidden_field_tags @query
p
label
= radio_button_tag 'c[]', '', true
= l(:description_selected_columns)
br
label
= radio_button_tag 'c[]', 'all_inline'
= l(:description_all_columns)
- if @query.available_filters.key?('description')
p
label
= check_box_tag 'c[]', 'description', @query.has_column?(:description)
= l(:field_description)
- if Rails.version >= '5.2'
= export_csv_encoding_select_tag
p.buttons
= submit_tag l(:button_export), name: nil, onclick: 'hideModal(this);'
'
= link_to_function l(:button_cancel), 'hideModal(this);'

View file

@ -0,0 +1,2 @@
- if Additionals.setting?(:add_go_to_top)
a#gototop

View file

@ -0,0 +1,5 @@
- sidebar = Additionals.settings[:global_sidebar]
- if sidebar.present?
br
.sidebar-additionals
= textilizable(sidebar)

View file

@ -0,0 +1,7 @@
= render(partial: 'additionals/live_search_ajax_call.js', layout: false, formats: [:js])
- unless defined? classes
- classes = 'title'
h2 class="#{classes}"
= @query.new_record? ? l(title) : h(@query.name)
span.additionals-live-search
= text_field_tag(:search, q, autocomplete: 'off', class: 'live-search-field', placeholder: l(placeholder))

View file

@ -0,0 +1,9 @@
- additionals_top_menu_setup
- if Additionals.settings[:external_urls].to_i > 0
= javascript_include_tag('redirect', plugin: 'additionals')
- if Additionals.settings[:external_urls].to_i == 2
= javascript_include_tag('noreferrer', plugin: 'additionals')
= additionals_library_load(:font_awesome)
= stylesheet_link_tag 'additionals', plugin: 'additionals'
- if User.current.try(:hrm_user_type_id).nil?
- render_custom_top_menu_item

View file

@ -0,0 +1,18 @@
javascript:
$(function() {
// when the #search field changes
$('#search').live_observe_field(2, function() {
var form = $('#query_form'); // grab the form wrapping the search bar.
var url = form.attr('action');
form.find('[name="c[]"] option').each(function(i, elem) {
$(elem).attr('selected', true)
})
var formData = form.serialize();
form.find('[name="c[]"] option').each(function(i, elem) {
$(elem).attr('selected', false)
})
$.get(url, formData, function(data) { // perform an AJAX get, the trailing function is what happens on successful get.
$("#query-result-list").html(data); // replace the "results" div with the result of action taken
});
});
});

View file

@ -0,0 +1,31 @@
- options = {} if options.nil?
javascript:
$("##{field_id}").select2({
ajax: {
url: "#{ajax_url}",
dataType: 'json',
delay: 250,
data: function(params) {
return {
q: params.term
};
},
processResults: function(data, params) {
return {
results: data
};
},
cache: true
},
placeholder: "#{options[:placeholder].presence}",
allowClear: #{options[:allow_clear].present? && options[:allow_clear] ? 'true' : 'false'},
minimumInputLength: 0,
width: '60%',
templateResult: formatState
});
function formatState(opt) {
if (opt.loading) return opt.name;
var $opt = $('<span>' + opt.name_with_icon + '</span>');
return $opt;
};

View file

@ -0,0 +1,27 @@
fieldset.box
legend = l(:additionals_query_list_defaults)
- setting_name_columns = "#{query_type}_list_defaults"
- query = query_class.new(@settings[setting_name_columns.to_sym])
- if Redmine::VERSION.to_s >= '4'
.default-query-settings-label-redmine4
= render_query_columns_selection(query, name: "settings[#{setting_name_columns}][column_names]")
- else
.default-query-settings-label
= render_query_columns_selection(query, name: "settings[#{setting_name_columns}][column_names]")
- columns = query_class.new.available_totalable_columns
- if columns.count > 0
fieldset.box
legend = l(:additionals_query_list_default_totals)
.default-query-settings-totals
- setting_name_totals = "#{query_type}_list_default_totals"
= hidden_field_tag("settings[#{setting_name_totals}][]", '')
- columns.each do |s|
label.inline
- value = @settings[setting_name_totals.to_sym].present? ? @settings[setting_name_totals.to_sym].include?(s.name.to_s) : false
= check_box_tag("settings[#{setting_name_totals}][]",
s.name,
value,
id: nil)
= s.caption

View file

@ -0,0 +1,26 @@
- if defined?(show_always) && show_always || entry.tag_list.present?
.tags.attribute
- unless defined? hide_label
span.label
= l(:field_tag_list)
' :
- if defined?(editable) && editable
#tags-data
= additionals_tag_links(entry.tags, tags_without_color: defined?(tags_without_color) ? tags_without_color : false)
'
span.contextual
= link_to l(:label_edit_tags),
{},
onclick: "$('#edit_tags_form').show(); $('#tags-data').hide(); return false;",
id: 'edit_tags_link'
#edit_tags_form style="display: none;"
= form_tag(update_url, method: :put, multipart: true ) do
= render partial: 'tags_form'
'
= submit_tag l(:button_save), class: 'button-small'
'
= link_to l(:button_cancel), {}, onclick: "$('#edit_tags_form').hide(); $('#tags-data').show(); return false;"
- else
= additionals_tag_links(entry.tags, tags_without_color: defined?(tags_without_color) ? tags_without_color : false)

View file

@ -0,0 +1,8 @@
- @settings = ActionController::Parameters.new(@settings) unless Rails.version >= '5.2'
' Need Help? :
= link_to(l(:label_additionals_doc),
'https://additionals.readthedocs.io/en/latest/',
class: 'external',
target: '_blank',
rel: 'noopener')
= render_tabs additionals_settings_tabs

View file

@ -0,0 +1,39 @@
br
h3 = l(:label_content_plural)
p
= content_tag(:label, l(:label_account_login))
= text_area_tag 'settings[account_login_bottom]', @settings[:account_login_bottom], class: 'wiki-edit', rows: 10
em.info
= l(:account_login_info)
p
= content_tag(:label, l(:label_global_sidebar))
= text_area_tag 'settings[global_sidebar]', @settings[:global_sidebar], class: 'wiki-edit', rows: 10
em.info
= l(:global_sidebar_info)
p
= content_tag(:label, l(:label_global_footer))
= text_area_tag 'settings[global_footer]', @settings[:global_footer], class: 'wiki-edit', rows: 5
em.info
= l(:global_footer_info)
br
h3 = l(:label_setting_plural)
p
= content_tag(:label, l(:label_external_urls))
= select_tag 'settings[external_urls]',
options_for_select({ l(:external_url_default) => '0',
l(:external_url_new_window) => '1',
l(:external_url_noreferrer) => '2' }, @settings['external_urls'])
em.info
= t(:external_urls_info_html)
p
= content_tag(:label, l(:label_add_go_to_top))
= check_box_tag 'settings[add_go_to_top]', 1, @settings[:add_go_to_top].to_i == 1
em.info
= t(:add_go_to_top_info)
p
= content_tag(:label, l(:label_legacy_smiley_support))
= check_box_tag 'settings[legacy_smiley_support]', 1, @settings[:legacy_smiley_support].to_i == 1
em.info
= t(:legacy_smiley_support_info_html)

View file

@ -0,0 +1,112 @@
br
h3 = l(:label_content_plural)
p
= content_tag(:label, l(:label_new_ticket_message))
= text_area_tag 'settings[new_ticket_message]', @settings[:new_ticket_message], class: 'wiki-edit', rows: 10
em.info = l(:new_ticket_message_info)
br
hr
h3 = l(:label_setting_plural)
.info = t(:top_rules_help)
br
p
= content_tag(:label, l(:label_new_issue_on_profile))
= check_box_tag 'settings[new_issue_on_profile]', 1, @settings[:new_issue_on_profile].to_i == 1
p
= content_tag(:label, l(:label_issue_assign_to_me))
= check_box_tag 'settings[issue_assign_to_me]', 1, @settings[:issue_assign_to_me].to_i == 1
p
= content_tag(:label, l(:label_issue_change_status_in_sidebar))
= check_box_tag 'settings[issue_change_status_in_sidebar]', 1, @settings[:issue_change_status_in_sidebar].to_i == 1
p
= content_tag(:label, l(:label_issue_autowatch_involved))
= check_box_tag 'settings[issue_autowatch_involved]', 1, @settings[:issue_autowatch_involved].to_i == 1
p
= content_tag(:label, l(:label_rule_issue_close_with_open_children))
= check_box_tag 'settings[issue_close_with_open_children]', 1, @settings[:issue_close_with_open_children].to_i == 1
p
= content_tag(:label, l(:label_rule_issue_freezed_with_close))
= check_box_tag 'settings[issue_freezed_with_close]', 1, @settings[:issue_freezed_with_close].to_i == 1
em.info = t(:rule_issue_freezed_with_close_info)
br
- rule_status = IssueStatus.sorted
p
= content_tag(:label, l(:label_rule_issue_status_change))
= check_box_tag 'settings[issue_status_change]', 1, @settings[:issue_status_change].to_i == 1
span[style="vertical-align: top; margin-left: 15px;"]
= l(:field_status)
| x:
= select_tag 'settings[issue_status_x]',
options_for_select(rule_status.collect { |column| [column.name, column.id] },
@settings[:issue_status_x]),
multiple: true, size: 6, style: 'width:150px'
'
= l(:field_status)
| y:
= select_tag 'settings[issue_status_y]',
options_for_select(rule_status.collect { |column| [column.name, column.id] },
@settings[:issue_status_y]),
multiple: false, style: 'width:150px; vertical-align: top'
em.info = t(:rule_issue_status_change_info)
br
br
p
= content_tag(:label, l(:label_rule_issue_current_user_status))
= check_box_tag 'settings[issue_current_user_status]', 1, @settings[:issue_current_user_status].to_i == 1
span[style="vertical-align: top; margin-left: 15px;"]
= l(:field_status)
| x:
= select_tag 'settings[issue_assign_to_x]',
options_for_select(rule_status.collect { |column| [column.name, column.id] },
@settings[:issue_assign_to_x]),
multiple: true, size: 6, style: 'width:150px'
em.info = t(:rule_issue_current_user_status_info_html)
br
br
p
= content_tag(:label, l(:label_rule_issue_auto_assign))
= check_box_tag 'settings[issue_auto_assign]', 1, @settings[:issue_auto_assign].to_i == 1
span[style="vertical-align: top; margin-left: 15px;"]
= l(:field_status)
| x:
= select_tag 'settings[issue_auto_assign_status]',
options_for_select(rule_status.collect { |column| [column.name, column.id] },
@settings[:issue_auto_assign_status]),
multiple: true, size: 6, style: 'width:150px'
'
= l(:label_role)
| :
= select_tag 'settings[issue_auto_assign_role]',
options_from_collection_for_select(Role.givable.sorted, :id, :name, @settings[:issue_auto_assign_role]),
multiple: false, style: 'width:150px; vertical-align: top'
em.info = t(:rule_issue_auto_assign_info)
br
br
p
= content_tag(:label, l(:label_rule_issue_timelog_required))
= check_box_tag 'settings[issue_timelog_required]', 1, @settings[:issue_timelog_required].to_i == 1
span[style="vertical-align: top; margin-left: 15px;"]
= l(:label_tracker_plural)
| :
= select_tag 'settings[issue_timelog_required_tracker]',
options_for_select(Tracker.all.sorted.collect { |column| [column.name, column.id] },
@settings[:issue_timelog_required_tracker]),
multiple: true, size: 6, style: 'width:150px'
'
= l(:field_status)
| :
= select_tag 'settings[issue_timelog_required_status]',
options_for_select(rule_status.collect { |column| [column.name, column.id] },
@settings[:issue_timelog_required_status]),
multiple: true, size: 6, style: 'width:150px'
em.info
= t(:rule_issue_timelog_required_info_html)

View file

@ -0,0 +1,15 @@
em.info
= l(:hidden_macros_in_toolbar_info)
br
p
= content_tag(:label, l(:label_hidden_macros_in_toolbar))
= hidden_field_tag('settings[hidden_macros_in_toolbar][]', '')
- @available_macros = AdditionalsMacro.all(only_names: true).each do |m|
label.block
- value = @settings[:hidden_macros_in_toolbar].present? ? @settings[:hidden_macros_in_toolbar].include?(m) : false
= check_box_tag('settings[hidden_macros_in_toolbar][]', m, value, id: nil)
= m
br

View file

@ -0,0 +1,44 @@
.info = t(:label_top_menu_help_html)
br
h3 = l(:label_custom_menu_items)
- 5.times do |i|
fieldset
legend
b = "#{l(:label_menu_entry)} ##{i + 1}"
div
p
label = h l(:field_name)
= text_field_tag('settings[custom_menu' + i.to_s + '_name]', @settings['custom_menu' + i.to_s + '_name'], size: 40)
p
label = h l(:field_url)
= text_field_tag('settings[custom_menu' + i.to_s + '_url]', @settings['custom_menu' + i.to_s + '_url'], size: 80)
p
label = h l(:field_title)
= text_field_tag('settings[custom_menu' + i.to_s + '_title]', @settings['custom_menu' + i.to_s + '_title'], size: 80)
i
| (
= l(:label_optional)
| )
p
label = h l(:label_permissions)
- permission_field = 'custom_menu' + i.to_s + '_roles'
- menu_roles = Struct.new(:id, :name)
= select_tag('settings[' + permission_field + ']',
options_from_collection_for_select(Role.sorted.collect { |m| menu_roles.new(m.id, m.name) },
:id,
:name,
@settings[permission_field]),
multiple: true, style: 'height: 100px;')
em.info = l(:menu_roles_info)
br
h3 = l(:label_setting_plural)
p
= content_tag(:label, l(:label_remove_help))
= check_box_tag 'settings[remove_help]', 1, @settings[:remove_help].to_i == 1
em.info = l(:remove_help_info)
p
= content_tag(:label, l(:label_remove_mypage))
= check_box_tag 'settings[remove_mypage]', 1, @settings[:remove_mypage].to_i == 1
em.info = l(:remove_mypage_info)

View file

@ -0,0 +1,29 @@
.info = t(:top_overview_help)
br
h3 = l(:label_content_plural)
p
= content_tag(:label, l(:label_overview_right))
= text_area_tag 'settings[overview_right]', @settings[:overview_right], class: 'wiki-edit', rows: 10
em.info
= l(:overview_right_info)
p
= content_tag(:label, l(:label_overview_top))
= text_area_tag 'settings[overview_top]', @settings[:overview_top], class: 'wiki-edit', rows: 10
em.info
= l(:overview_top_info)
p
= content_tag(:label, l(:label_overview_bottom))
= text_area_tag 'settings[overview_bottom]', @settings[:overview_bottom], class: 'wiki-edit', rows: 10
em.info
= l(:overview_bottom_info)
br
h3 = l(:label_setting_plural)
p
= content_tag(:label, l(:label_remove_news))
= check_box_tag 'settings[remove_news]', 1, @settings[:remove_news].to_i == 1
em.info
= l(:remove_news_info)

View file

@ -0,0 +1,26 @@
.info = t(:top_projects_help)
br
p
= content_tag(:label, l(:label_project_overview_content))
= text_area_tag 'settings[project_overview_content]',
@settings[:project_overview_content],
class: 'wiki-edit', rows: 10
em.info
= l(:project_overview_content_info)
hr
p
= content_tag(:label, l(:label_disabled_modules))
= hidden_field_tag('settings[disabled_modules][]', '')
- Redmine::AccessControl.available_project_modules_all.each do |m|
label.block
- value = @settings[:disabled_modules].present? ? @settings[:disabled_modules].include?(m.to_s) : false
= check_box_tag('settings[disabled_modules][]', m, value, id: nil)
= l_or_humanize(m, prefix: 'project_module_')
br
em.info
= l(:disabled_modules_info)

View file

@ -0,0 +1,10 @@
br
h3 = l(:label_user_plural)
p
= content_tag(:label, l(:label_invisible_captcha))
= check_box_tag 'settings[invisible_captcha]',
1,
@settings[:invisible_captcha].to_i == 1,
disabled: (true unless Setting.self_registration?)
em.info
= t(:invisible_captcha_info_html)

View file

@ -0,0 +1,7 @@
br
h3 = l(:label_web_apis)
p
= content_tag(:label, l(:label_google_maps_embed_api))
= text_field_tag('settings[google_maps_api_key]', @settings[:google_maps_api_key], size: 60)
em.info = t(:google_maps_embed_api_html)
= call_hook(:additionals_settings_web_apis, settings: @settings)

View file

@ -0,0 +1,34 @@
.info = t(:top_wiki_help)
br
h3 = l(:label_content_plural)
p
= content_tag(:label, l(:label_global_wiki_sidebar))
= text_area_tag 'settings[global_wiki_sidebar]', @settings[:global_wiki_sidebar], class: 'wiki-edit', rows: 10
em.info
= l(:global_wiki_sidebar_info)
p
= content_tag(:label, l(:label_global_wiki_header))
= text_area_tag 'settings[global_wiki_header]', @settings[:global_wiki_header], class: 'wiki-edit', rows: 5
em.info
= l(:global_wiki_header_info)
p
= content_tag(:label, l(:label_global_wiki_footer))
= text_area_tag 'settings[global_wiki_footer]', @settings[:global_wiki_footer], class: 'wiki-edit', rows: 5
em.info
= l(:global_wiki_footer_info)
br
h3 = l(:label_pdf_wiki_settings)
p
= content_tag(:label, l(:label_wiki_pdf_remove_title))
= check_box_tag 'settings[wiki_pdf_remove_title]', 1, @settings[:wiki_pdf_remove_title].to_i == 1
em.info
= l(:wiki_pdf_remove_title_info)
p
= content_tag(:label, l(:label_wiki_pdf_remove_attachments))
= check_box_tag 'settings[wiki_pdf_remove_attachments]', 1, @settings[:wiki_pdf_remove_attachments].to_i == 1
em.info
= l(:wiki_pdf_remove_attachments_info)

View file

@ -0,0 +1,11 @@
h2 = l(:label_settings_macros) + " (#{@available_macros.count})"
.info = t(:label_top_macros_help_html)
br
.box
- @available_macros.each do |macro, options|
.macro-box
.macro-title
= macro.to_s
.macro-desc
pre = options[:desc]

View file

@ -0,0 +1,11 @@
table.list
tr
td.name
= "#{l(:label_system_info)}:"
td.name
= system_info
tr
td.name
= "#{l(:label_uptime)}:"
td.name
= system_uptime

View file

@ -0,0 +1 @@
== @tags.collect { |tag| { 'id' => tag.name, 'text' => tag.name } }.to_json

View file

@ -0,0 +1,9 @@
<%= raw @users.map { |user| {
'id' => user.id,
'text' => user.name,
'name' => user.name,
'name_with_icon' => user_with_avatar(user, no_link: true),
'value' => user.id
}
}.to_json
%>

View file

@ -0,0 +1,11 @@
- if Additionals.setting?(:issue_freezed_with_close) && !User.current.allowed_to?(:edit_closed_issues, project)
- if @issues.detect(&:closed?)
ruby:
@safe_attributes = []
@can[:edit] = false
@can[:edit] = false
@allowed_statuses = nil
@trackers = nil
@can[:add_watchers] = nil
@can[:delete] = nil
@options_by_custom_field = []

View file

@ -0,0 +1 @@
= call_hook(:view_users_show_contextual, user: @user)

View file

@ -0,0 +1 @@
= call_hook(:view_users_show_info, user: @user)

View file

@ -0,0 +1,5 @@
- if User.current.logged? && @issue.editable? && Additionals.setting?(:issue_assign_to_me) && \
@issue.assigned_to_id != User.current.id && @project.assignable_users.detect { |u| u.id == User.current.id }
= link_to(font_awesome_icon('far_user-circle', post_text: l(:button_assign_to_me)),
issue_assign_to_me_path(@issue), method: :put,
class: 'assign-to-me')

View file

@ -0,0 +1,21 @@
- if Additionals.setting?(:issue_change_status_in_sidebar) && \
@issue && \
User.current.allowed_to?(:edit_issues, @project) && \
(!@issue.closed? || User.current.allowed_to?(:edit_closed_issues, @project))
- statuses = @issue.sidbar_change_status_allowed_to(User.current)
- if statuses.present?
h3 = l(:label_issue_change_status)
ul.issue-status-change-sidebar
- statuses.each do |s|
- if s != @issue.status
li
- if s.is_closed?
= link_to(font_awesome_icon('fas_caret-square-left', post_text: s.name),
issue_change_status_path(@issue, new_status_id: s.id),
method: :put, class: "status-switch status-#{s.id}")
- else
= link_to(font_awesome_icon('far_caret-square-left', post_text: s.name),
issue_change_status_path(@issue, new_status_id: s.id),
method: :put, class: "status-switch status-#{s.id}")
h3 = l(:label_planning)

View file

@ -0,0 +1,9 @@
- if show_issue_change_author?(issue) && issue.safe_attribute?('author_id')
- author_options = issue_author_options_for_select(issue.project, issue)
- if author_options.present?
p#change_author
= form.label_for_field :author_id
= link_to_function content_tag(:span, l(:button_edit), class: 'icon icon-edit'), '$(this).hide(); $("#issue_author_id").show()'
= form.select :author_id, author_options, { required: true, no_label: true }, style: 'display: none'
javascript:
$('#change_author').insertBefore($('#issue_tracker_id').parent());

View file

@ -0,0 +1,7 @@
- if @project && User.current.allowed_to?(:edit_issue_author, @project)
- author_options = issue_author_options_for_select(@project)
- if author_options.present?
p#change_author
= label_tag('issue[author_id]', l(:field_author))
= select_tag('issue[author_id]',
content_tag('option', l(:label_no_change_option), value: '') + author_options)

View file

@ -0,0 +1,3 @@
- if @issue.new_ticket_message.present?
.nodata.nodata-left
= textilizable(@issue.new_ticket_message).html_safe

View file

@ -0,0 +1,4 @@
- project_overview_content = Additionals.settings[:project_overview_content]
- if project_overview_content.present?
.project-content.wiki.box
= textilizable(project_overview_content)

View file

@ -0,0 +1,3 @@
- if @query.description?
.query-description
= textilizable @query, :description

View file

@ -0,0 +1,17 @@
p
= f.check_box :hide, disabled: @role.users_visibility != 'members_of_visible_projects'
em.info
= t(:info_hidden_roles_html)
javascript:
$(function() {
$('#role_users_visibility').change(function() {
var uv = $("#role_users_visibility").val();
if (uv == 'members_of_visible_projects') {
$("#role_hide").prop('disabled', false);
} else {
$("#role_hide").prop('checked', false);
$("#role_hide").prop('disabled', true);
}
});
});

View file

@ -0,0 +1,4 @@
- if Additionals.setting?(:new_issue_on_profile) && @memberships.present?
- project_url = memberships_new_issue_project_url(user, @memberships)
- if project_url.present?
= link_to(l(:label_issue_new), project_url, class: 'user-new-issue icon icon-add')

View file

@ -0,0 +1,4 @@
- if Additionals.setting?(:issue_autowatch_involved) && User.current.allowed_to?(:view_issues, nil, global: true)
= labelled_fields_for :pref, @user.pref do |pref_fields|
p
= pref_fields.check_box :autowatch_involved_issue

View file

@ -0,0 +1,5 @@
- overview_bottom = Additionals.settings[:overview_bottom]
- if overview_bottom.present?
.clear-both
.overview-bottom.wiki.box
= textilizable(overview_bottom)

View file

@ -0,0 +1,5 @@
- unless Additionals.setting?(:remove_news)
.news.box
h3 = l(:label_news_latest)
= render partial: 'news/news', collection: @news
= link_to l(:label_news_view_all), news_index_path

View file

@ -0,0 +1,4 @@
- overview_right = Additionals.settings[:overview_right]
- if overview_right.present?
.overview-right.wiki.box
= textilizable(overview_right)

View file

@ -0,0 +1,4 @@
- overview_top = Additionals.settings[:overview_top]
- if overview_top.present?
.overview-top.wiki.box
= textilizable(overview_top)

View file

@ -0,0 +1,17 @@
.month-calendar id="month-calendar-#{id}"
javascript:
$("#month-calendar-#{id}").datepicker({
language: "#{locale}",
calendarWeeks: #{options[:show_weeks]},
todayHighlight: true,
multidate: true,
disableTouchKeyboard: true,
defaultViewDate: {
year: #{options[:year]},
month: #{options[:month]},
day: 1
}
});
- unless selected.empty?
javascript:
$('#month-calendar-#{id}').datepicker('setDates', #{selected});

View file

@ -0,0 +1,16 @@
.cryptocompare
javascript:
var scripts = document.getElementsByTagName("script");
var embedder = scripts[scripts.length - 1];
(function() {
var appName = encodeURIComponent(window.location.hostname);
if (appName == "") {
appName = "local";
}
var s = document.createElement("script");
s.type = "text/javascript";
s.async = true;
var theUrl = "#{raw url}";
s.src = theUrl + (theUrl.indexOf("?") >= 0 ? "&" : "?") + "app=" + appName;
embedder.parentNode.appendChild(s);
})();

View file

@ -0,0 +1,5 @@
- sidebar = Additionals.settings[:global_sidebar]
- if sidebar.present?
.sidebar-additionals
= textilizable(sidebar)
br

View file

@ -0,0 +1,15 @@
.additionals-projects.box
- if list_title
h3 = list_title
ul
- @projects.each do |project|
li.project class="#{cycle('odd', 'even')}"
span[style='font-weight: bold;']
= link_to_project(project)
- if project.homepage?
' :
= link_to(project.homepage, project.homepage, @html_options)
- if with_create_issue && User.current.allowed_to?(:add_issues, project)
= link_to('',
new_project_issue_path(project_id: project),
class: 'icon icon-add', title: l(:label_issue_new))

View file

@ -0,0 +1,4 @@
- content_for :header_tags do
= stylesheet_link_tag 'bootstrap-datepicker3.standalone.min', plugin: 'additionals'
= javascript_include_tag('bootstrap-datepicker.min', plugin: 'additionals')
= bootstrap_datepicker_locale

View file

@ -0,0 +1,6 @@
/! TradingView Widget BEGIN
= javascript_include_tag 'https://s3.tradingview.com/tv.js'
div[style="display: inline-block;"]
javascript:
new TradingView.widget(#{raw options.to_json});
/! TradingView Widget END

View file

@ -0,0 +1,23 @@
.users.box
- if list_title
h3 = list_title
- users.each do |user|
.user.box class="#{cycle('odd', 'even')}"
div[style="float: left; display: block; margin-right: 5px;"]
= avatar(user, size: 50)
.user.line[style="font-weight: bold;"]
= link_to_user(user)
- if !user_roles.nil? && user_roles[user.id]
.user.line
= l(:field_role)
' :
= user_roles[user.id].join(', ').html_safe
.user.line
= l(:field_login)
' :
= link_to user.login, '/users/' + user.id.to_s
- unless user.pref.hide_mail
.user.line
= l(:field_mail)
' :
= mail_to(user.mail, nil, encode: 'javascript')