Working on ruby 2.7 updates and warning fixes

This commit is contained in:
Alexander Meindl 2021-04-18 13:34:55 +02:00
parent a48ee5b4a7
commit 07c306fa66
172 changed files with 1035 additions and 751 deletions

View File

@ -14,7 +14,8 @@ jobs:
- name: Setup Gemfile - name: Setup Gemfile
run: | run: |
cp test/support/gemfile.rb Gemfile touch .enable_dev
sed -i "3isource 'https://rubygems.org'" Gemfile
- name: Setup Ruby - name: Setup Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1

2
.gitignore vendored
View File

@ -6,7 +6,9 @@ tmp/
Gemfile.lock Gemfile.lock
.project .project
.vscode .vscode
.bundle
.settings/ .settings/
docs/_build docs/_build
docs/_static docs/_static
docs/_templates docs/_templates
.enable_dev

View File

@ -16,6 +16,10 @@ Metrics/AbcSize:
Metrics/BlockLength: Metrics/BlockLength:
Enabled: false Enabled: false
Metrics/ParameterLists:
Enabled: true
CountKeywordArgs: false
Metrics/ClassLength: Metrics/ClassLength:
Enabled: false Enabled: false
@ -58,16 +62,45 @@ Performance/ChainArrayAllocation:
Style/AutoResourceCleanup: Style/AutoResourceCleanup:
Enabled: true Enabled: true
Style/ExpandPathArguments:
Enabled: true
Exclude:
- additionals.gemspec
- test/**/*
Style/FrozenStringLiteralComment: Style/FrozenStringLiteralComment:
Enabled: false Enabled: true
Exclude:
- '/**/*.rsb'
Style/OptionHash:
Enabled: true
SuspiciousParamNames:
- options
- api_options
- opts
- args
- params
- parameters
- settings
Exclude:
- lib/additionals/patches/*.rb
Style/ReturnNil:
Enabled: true
Style/UnlessLogicalOperators:
Enabled: true
Style/MethodCallWithArgsParentheses:
Enabled: true
AllowParenthesesInMultilineCall: true
AllowParenthesesInChaining: true
EnforcedStyle: omit_parentheses
Style/Documentation: Style/Documentation:
Enabled: false Enabled: false
# required for travis/ci (symbolic links problem)
Style/ExpandPathArguments:
Enabled: false
Style/HashTransformValues: Style/HashTransformValues:
Enabled: false Enabled: false

View File

@ -4,6 +4,7 @@ linters:
RuboCop: RuboCop:
ignored_cops: ignored_cops:
- Layout/ArgumentAlignment - Layout/ArgumentAlignment
- Layout/ArrayAlignment
- Layout/BlockEndNewline - Layout/BlockEndNewline
- Layout/EmptyLineAfterGuardClause - Layout/EmptyLineAfterGuardClause
- Layout/HashAlignment - Layout/HashAlignment
@ -21,6 +22,7 @@ linters:
- Lint/Void - Lint/Void
- Metrics/BlockNesting - Metrics/BlockNesting
- Rails/OutputSafety - Rails/OutputSafety
- Style/FrozenStringLiteralComment
- Style/IdenticalConditionalBranches - Style/IdenticalConditionalBranches
- Style/IfUnlessModifier - Style/IfUnlessModifier
- Style/Next - Style/Next

View File

@ -1,6 +1,11 @@
Changelog Changelog
========= =========
3.0.3
+++++
- Ruby 2.7 warnings fixed
3.0.2 3.0.2
+++++ +++++

18
Gemfile
View File

@ -1,2 +1,20 @@
# frozen_string_literal: true
# Specify your gem's dependencies in additionals.gemspec # Specify your gem's dependencies in additionals.gemspec
gemspec gemspec
group :development do
# this is only used for development.
# if you want to use it, do:
# - create .enable_dev file in additionals directory
# - remove rubocop entries from REDMINE/Gemfile
# - remove REDMINE/.rubocop* files
if File.file? File.expand_path './.enable_dev', __dir__
gem 'brakeman', require: false
gem 'pandoc-ruby', require: false
gem 'rubocop', require: false
gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false
gem 'slim_lint', require: false
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'bundler/gem_tasks' require 'bundler/gem_tasks'
require 'rake/testtask' require 'rake/testtask'

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
lib = File.expand_path '../lib', __FILE__ lib = File.expand_path '../lib', __FILE__
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) $LOAD_PATH.unshift lib unless $LOAD_PATH.include? lib
require 'additionals/version' require 'additionals/version'
Gem::Specification.new do |spec| Gem::Specification.new do |spec|
@ -22,9 +24,4 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'render_async' spec.add_runtime_dependency 'render_async'
spec.add_runtime_dependency 'rss' spec.add_runtime_dependency 'rss'
spec.add_runtime_dependency 'slim-rails' spec.add_runtime_dependency 'slim-rails'
spec.add_development_dependency 'brakeman'
spec.add_development_dependency 'bundler'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'slim_lint'
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AdditionalsAssignToMeController < ApplicationController class AdditionalsAssignToMeController < ApplicationController
before_action :find_issue before_action :find_issue
helper :additionals_issues helper :additionals_issues
@ -11,11 +13,11 @@ class AdditionalsAssignToMeController < ApplicationController
return return
end end
@issue.init_journal(User.current) @issue.init_journal User.current
@issue.assigned_to = User.current @issue.assigned_to = User.current
if !@issue.save || old_user == @issue.assigned_to if !@issue.save || old_user == @issue.assigned_to
flash[:error] = l(:error_issues_could_not_be_assigned_to_me) flash[:error] = l :error_issues_could_not_be_assigned_to_me
return redirect_to(issue_path(@issue)) return redirect_to(issue_path(@issue))
end end
@ -29,7 +31,7 @@ class AdditionalsAssignToMeController < ApplicationController
private private
def find_issue def find_issue
@issue = Issue.find(params[:issue_id]) @issue = Issue.find params[:issue_id]
raise Unauthorized unless @issue.visible? && @issue.editable? raise Unauthorized unless @issue.visible? && @issue.editable?
@project = @issue.project @project = @issue.project

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AdditionalsChangeStatusController < ApplicationController class AdditionalsChangeStatusController < ApplicationController
before_action :find_issue before_action :find_issue
helper :additionals_issues helper :additionals_issues
@ -6,7 +8,7 @@ class AdditionalsChangeStatusController < ApplicationController
issue_old_status_id = @issue.status.id issue_old_status_id = @issue.status.id
issue_old_user = @issue.assigned_to issue_old_user = @issue.assigned_to
new_status_id = params[:new_status_id].to_i new_status_id = params[:new_status_id].to_i
allowed_status = @issue.sidbar_change_status_allowed_to(User.current, new_status_id) 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? if new_status_id < 1 || @issue.status_id == new_status_id || allowed_status.nil?
redirect_to(issue_path(@issue)) redirect_to(issue_path(@issue))
@ -18,7 +20,7 @@ class AdditionalsChangeStatusController < ApplicationController
@issue.assigned_to = User.current if @issue.status_x_affected?(new_status_id) && issue_old_user != User.current @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 if !@issue.save || issue_old_status_id == @issue.status_id
flash[:error] = l(:error_issue_status_could_not_changed) flash[:error] = l :error_issue_status_could_not_changed
return redirect_to(issue_path(@issue)) return redirect_to(issue_path(@issue))
end end
@ -33,7 +35,7 @@ class AdditionalsChangeStatusController < ApplicationController
private private
def find_issue def find_issue
@issue = Issue.find(params[:issue_id]) @issue = Issue.find params[:issue_id]
raise Unauthorized unless @issue.visible? && @issue.editable? raise Unauthorized unless @issue.visible? && @issue.editable?
@project = @issue.project @project = @issue.project

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AdditionalsMacrosController < ApplicationController class AdditionalsMacrosController < ApplicationController
before_action :require_login before_action :require_login

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'open-uri' require 'open-uri'
class DashboardAsyncBlocksController < ApplicationController class DashboardAsyncBlocksController < ApplicationController
@ -84,7 +86,7 @@ class DashboardAsyncBlocksController < ApplicationController
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
deny_access unless User.current.allowed_to?(:view_project, @project) deny_access unless User.current.allowed_to? :view_project, @project
@project @project
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class DashboardsController < ApplicationController class DashboardsController < ApplicationController
menu_item :dashboards menu_item :dashboards
@ -63,7 +65,7 @@ class DashboardsController < ApplicationController
end end
def create def create
@dashboard = Dashboard.new(author: User.current) @dashboard = Dashboard.new author: User.current
@dashboard.safe_attributes = params[:dashboard] @dashboard.safe_attributes = params[:dashboard]
@dashboard.dashboard_type = assign_dashboard_type @dashboard.dashboard_type = assign_dashboard_type
@dashboard.role_ids = params[:dashboard][:role_ids] if params[:dashboard].present? @dashboard.role_ids = params[:dashboard][:role_ids] if params[:dashboard].present?
@ -71,7 +73,7 @@ class DashboardsController < ApplicationController
@allowed_projects = @dashboard.allowed_target_projects @allowed_projects = @dashboard.allowed_target_projects
if @dashboard.save if @dashboard.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l :notice_successful_create
respond_to do |format| respond_to do |format|
format.html { redirect_to dashboard_link_path(@project, @dashboard) } format.html { redirect_to dashboard_link_path(@project, @dashboard) }
@ -80,13 +82,13 @@ class DashboardsController < ApplicationController
else else
respond_to do |format| respond_to do |format|
format.html { render action: 'new' } format.html { render action: 'new' }
format.api { render_validation_errors(@dashboard) } format.api { render_validation_errors @dashboard }
end end
end end
end end
def edit def edit
return render_403 unless @dashboard.editable_by?(User.current) return render_403 unless @dashboard.editable_by? User.current
@allowed_projects = @dashboard.allowed_target_projects @allowed_projects = @dashboard.allowed_target_projects
@ -97,7 +99,7 @@ class DashboardsController < ApplicationController
end end
def update def update
return render_403 unless @dashboard.editable_by?(User.current) return render_403 unless @dashboard.editable_by? User.current
@dashboard.safe_attributes = params[:dashboard] @dashboard.safe_attributes = params[:dashboard]
@dashboard.role_ids = params[:dashboard][:role_ids] if params[:dashboard].present? @dashboard.role_ids = params[:dashboard][:role_ids] if params[:dashboard].present?
@ -106,9 +108,9 @@ class DashboardsController < ApplicationController
@allowed_projects = @dashboard.allowed_target_projects @allowed_projects = @dashboard.allowed_target_projects
if @dashboard.save if @dashboard.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l :notice_successful_update
respond_to do |format| respond_to do |format|
format.html { redirect_to dashboard_link_path @project, @dashboard } format.html { redirect_to dashboard_link_path(@project, @dashboard) }
format.api { head :ok } format.api { head :ok }
end end
else else
@ -124,20 +126,20 @@ class DashboardsController < ApplicationController
begin begin
@dashboard.destroy @dashboard.destroy
flash[:notice] = l(:notice_successful_delete) flash[:notice] = l :notice_successful_delete
respond_to do |format| respond_to do |format|
format.html { redirect_to @project.nil? ? home_path : project_path(@project) } format.html { redirect_to @project.nil? ? home_path : project_path(@project) }
format.api { head :ok } format.api { head :ok }
end end
rescue ActiveRecord::RecordNotDestroyed rescue ActiveRecord::RecordNotDestroyed
flash[:error] = l(:error_remove_db_entry) flash[:error] = l :error_remove_db_entry
redirect_to dashboard_path(@dashboard) redirect_to dashboard_path(@dashboard)
end end
end end
def query_statement_invalid(exception) def query_statement_invalid(exception)
logger&.error "Query::StatementInvalid: #{exception.message}" logger&.error "Query::StatementInvalid: #{exception.message}"
session.delete(additionals_query_session_key('dashboard')) session.delete additionals_query_session_key('dashboard')
render_error l(:error_query_statement_invalid) render_error l(:error_query_statement_invalid)
end end
@ -145,7 +147,7 @@ class DashboardsController < ApplicationController
block_settings = params[:settings] || {} block_settings = params[:settings] || {}
block_settings.each do |block, settings| block_settings.each do |block, settings|
@dashboard.update_block_settings(block, settings.to_unsafe_hash) @dashboard.update_block_settings block, settings.to_unsafe_hash
end end
@dashboard.save @dashboard.save
@updated_blocks = block_settings.keys @updated_blocks = block_settings.keys
@ -201,7 +203,7 @@ class DashboardsController < ApplicationController
end end
def find_dashboard def find_dashboard
@dashboard = Dashboard.find(params[:id]) @dashboard = Dashboard.find params[:id]
raise ::Unauthorized unless @dashboard.visible? raise ::Unauthorized unless @dashboard.visible?
if @dashboard.dashboard_type == DashboardContentProject::TYPE_NAME && @dashboard.project.nil? if @dashboard.dashboard_type == DashboardContentProject::TYPE_NAME && @dashboard.project.nil?

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module AdditionalsChartjsHelper module AdditionalsChartjsHelper
def chartjs_colorschemes_info_url def chartjs_colorschemes_info_url
link_to(l(:label_chartjs_colorscheme_info), link_to(l(:label_chartjs_colorscheme_info),
@ -7,6 +9,6 @@ module AdditionalsChartjsHelper
def select_options_for_chartjs_colorscheme(selected) def select_options_for_chartjs_colorscheme(selected)
data = YAML.safe_load(ERB.new(IO.read(File.join(Additionals.plugin_dir, 'config', 'colorschemes.yml'))).result) || {} data = YAML.safe_load(ERB.new(IO.read(File.join(Additionals.plugin_dir, 'config', 'colorschemes.yml'))).result) || {}
grouped_options_for_select(data, selected) grouped_options_for_select data, selected
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module AdditionalsClipboardjsHelper module AdditionalsClipboardjsHelper
def clipboardjs_button_for(target, clipboard_text_from_button = nil) def clipboardjs_button_for(target, clipboard_text_from_button = nil)
render_clipboardjs_button(target, clipboard_text_from_button) + render_clipboardjs_javascript(target) render_clipboardjs_button(target, clipboard_text_from_button) + render_clipboardjs_javascript(target)
@ -34,6 +36,6 @@ module AdditionalsClipboardjsHelper
end end
def render_clipboardjs_javascript(target) def render_clipboardjs_javascript(target)
javascript_tag("setClipboardJS('#zc_#{target}');") javascript_tag "setClipboardJS('#zc_#{target}');"
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module AdditionalsCustomFieldsHelper module AdditionalsCustomFieldsHelper
def custom_fields_with_full_with_layout def custom_fields_with_full_with_layout
['IssueCustomField'] ['IssueCustomField']

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module AdditionalsFontawesomeHelper module AdditionalsFontawesomeHelper
# name = TYPE-FA_NAME, eg. fas_car # name = TYPE-FA_NAME, eg. fas_car
# fas_cloud-upload-alt # fas_cloud-upload-alt
@ -7,7 +9,7 @@ module AdditionalsFontawesomeHelper
# pre_text # pre_text
# post_text # post_text
# title # title
def font_awesome_icon(name, options = {}) def font_awesome_icon(name, **options)
info = AdditionalsFontAwesome.value_info name info = AdditionalsFontAwesome.value_info name
return '' if info.blank? return '' if info.blank?
@ -29,7 +31,7 @@ module AdditionalsFontawesomeHelper
post_text = options[:post_text] post_text = options[:post_text]
options.delete :post_text options.delete :post_text
end end
s << tag.span(options) s << tag.span(**options)
if post_text.present? if post_text.present?
s << ' ' s << ' '
s << post_text s << post_text
@ -37,16 +39,16 @@ module AdditionalsFontawesomeHelper
safe_join s safe_join s
end end
def additionals_fontawesome_select(form, selected, options = {}) def additionals_fontawesome_select(form, selected, **options)
options[:include_blank] ||= true unless options[:required] options[:include_blank] ||= true unless options[:required]
html_options = {} html_options = {}
additionals_fontawesome_add_selected selected additionals_fontawesome_add_selected selected
name, options = Additionals.hash_remove_with_default(:name, options, :icon) name, options = Additionals.hash_remove_with_default :name, options, :icon
loader, options = Additionals.hash_remove_with_default(:loader, options, true) loader, options = Additionals.hash_remove_with_default :loader, options, true
html_options[:class], options = Additionals.hash_remove_with_default(:class, options, 'select2-fontawesome-field') html_options[:class], options = Additionals.hash_remove_with_default :class, options, 'select2-fontawesome-field'
html_options[:style], options = Additionals.hash_remove_with_default(:style, options) html_options[:style], options = Additionals.hash_remove_with_default :style, options
s = [] s = []
s << form.select(name, s << form.select(name,
@ -54,7 +56,7 @@ module AdditionalsFontawesomeHelper
options, options,
html_options) html_options)
s << additionals_fontawesome_loader(options, html_options) if loader s << additionals_fontawesome_loader(**options, field_class: html_options[:class]) if loader
safe_join s safe_join s
end end
@ -70,21 +72,20 @@ module AdditionalsFontawesomeHelper
'250px' '250px'
end end
def additionals_fontawesome_loader(options, html_options = {}) def additionals_fontawesome_loader(field_class: 'select2-fontawesome-field', **options)
html_options[:class] ||= 'select2-fontawesome-field'
options[:template_selection] = 'formatFontawesomeText' options[:template_selection] = 'formatFontawesomeText'
options[:template_result] = 'formatFontawesomeText' options[:template_result] = 'formatFontawesomeText'
if options[:include_blank] if options[:include_blank]
options[:placeholder] ||= l(:label_disabled) options[:placeholder] ||= l :label_disabled
options[:allow_clear] ||= true options[:allow_clear] ||= true
end end
options[:width] = additionals_fontawesome_default_select_width options[:width] = additionals_fontawesome_default_select_width
render(layout: false, render layout: false,
partial: 'additionals/select2_ajax_call.js', partial: 'additionals/select2_ajax_call',
formats: [:js], formats: [:js],
locals: { field_class: html_options[:class], locals: { field_class: field_class,
ajax_url: fontawesome_auto_completes_path(selected: @selected_store.join(',')), ajax_url: fontawesome_auto_completes_path(selected: @selected_store.join(',')),
options: options }) options: options }
end end
end end

View File

@ -1,7 +1,9 @@
# frozen_string_literal: true
module AdditionalsIssuesHelper module AdditionalsIssuesHelper
def author_options_for_select(project, entity = nil, permission = nil) def author_options_for_select(project, entity = nil, permission = nil)
scope = project.present? ? project.users.visible : User.active.visible scope = project.present? ? project.users.visible : User.active.visible
scope = scope.with_permission(permission, project) unless permission.nil? scope = scope.with_permission permission, project unless permission.nil?
authors = scope.sorted.to_a authors = scope.sorted.to_a
unless entity.nil? unless entity.nil?
@ -15,7 +17,7 @@ module AdditionalsIssuesHelper
s = [] s = []
return s unless authors.any? return s unless authors.any?
s << tag.option("<< #{l :label_me} >>", value: User.current.id) if authors.include?(User.current) s << tag.option("<< #{l :label_me} >>", value: User.current.id) if authors.include? User.current
if entity.nil? if entity.nil?
s << options_from_collection_for_select(authors, 'id', 'name') s << options_from_collection_for_select(authors, 'id', 'name')

View File

@ -1,13 +1,15 @@
# frozen_string_literal: true
module AdditionalsJournalsHelper module AdditionalsJournalsHelper
MultipleValuesDetail = Struct.new(:property, :prop_key, :custom_field, :old_value, :value) MultipleValuesDetail = Struct.new :property, :prop_key, :custom_field, :old_value, :value
# Returns the textual representation of a journal details # Returns the textual representation of a journal details
# as an array of strings # as an array of strings
def entity_details_to_strings(entity, details, options = {}) def entity_details_to_strings(entity, details, **options)
entity_type = entity.model_name.param_key entity_type = entity.model_name.param_key
show_detail_method = "#{entity_type}_show_detail" show_detail_method = "#{entity_type}_show_detail"
options[:only_path] = options[:only_path] != false options[:only_path] = options[:only_path] != false
no_html = options.delete(:no_html) no_html = options.delete :no_html
strings = [] strings = []
values_by_field = {} values_by_field = {}
@ -21,21 +23,21 @@ module AdditionalsJournalsHelper
next next
end end
end end
strings << send(show_detail_method, detail, no_html, options) strings << send(show_detail_method, detail, no_html, **options)
end end
if values_by_field.present? if values_by_field.present?
values_by_field.each do |field, changes| values_by_field.each do |field, changes|
if changes[:added].any? if changes[:added].any?
detail = MultipleValuesDetail.new('cf', field.id.to_s, field) detail = MultipleValuesDetail.new 'cf', field.id.to_s, field
detail.value = changes[:added] detail.value = changes[:added]
strings << send(show_detail_method, detail, no_html, options) strings << send(show_detail_method, detail, no_html, **options)
end end
next unless changes[:deleted].any? next unless changes[:deleted].any?
detail = MultipleValuesDetail.new('cf', field.id.to_s, field) detail = MultipleValuesDetail.new 'cf', field.id.to_s, field
detail.old_value = changes[:deleted] detail.old_value = changes[:deleted]
strings << send(show_detail_method, detail, no_html, options) strings << send(show_detail_method, detail, no_html, **options)
end end
end end
strings strings
@ -64,7 +66,7 @@ module AdditionalsJournalsHelper
# Returns the textual representation of a single journal detail # Returns the textual representation of a single journal detail
# rubocop: disable Rails/OutputSafety # rubocop: disable Rails/OutputSafety
def entity_show_detail(entity, detail, no_html = false, options = {}) # rubocop:disable Style/OptionalBooleanParameter: def entity_show_detail(entity, detail, no_html = false, **options) # rubocop:disable Style/OptionalBooleanParameter:
multiple = false multiple = false
no_detail = false no_detail = false
show_diff = false show_diff = false
@ -82,17 +84,17 @@ module AdditionalsJournalsHelper
if label || show_diff if label || show_diff
unless no_html unless no_html
label = tag.strong(label) label = tag.strong label
old_value = tag.i(old_value) if detail.old_value old_value = tag.i old_value if detail.old_value
old_value = tag.del(old_value) if detail.old_value && detail.value.blank? old_value = tag.del old_value if detail.old_value && detail.value.blank?
value = tag.i(value) if value value = tag.i value if value
end end
html = html =
if no_detail if no_detail
l(:text_journal_changed_no_detail, label: label) l :text_journal_changed_no_detail, label: label
elsif show_diff elsif show_diff
s = l(:text_journal_changed_no_detail, label: label) s = l :text_journal_changed_no_detail, label: label
unless no_html unless no_html
diff_link = link_to l(:label_diff), diff_link = link_to l(:label_diff),
send(diff_url_method, send(diff_url_method,
@ -105,11 +107,11 @@ module AdditionalsJournalsHelper
s.html_safe s.html_safe
elsif detail.value.present? elsif detail.value.present?
if detail.old_value.present? if detail.old_value.present?
l(:text_journal_changed, label: label, old: old_value, new: value) l :text_journal_changed, label: label, old: old_value, new: value
elsif multiple elsif multiple
l(:text_journal_added, label: label, value: value) l :text_journal_added, label: label, value: value
else else
l(:text_journal_set_to, label: label, value: value) l :text_journal_set_to, label: label, value: value
end end
else else
l(:text_journal_deleted, label: label, old: old_value).html_safe l(:text_journal_deleted, label: label, old: old_value).html_safe
@ -138,20 +140,20 @@ module AdditionalsJournalsHelper
prop = { label: custom_field.name } prop = { label: custom_field.name }
project = Project.visible.where(id: detail.value).first if detail.value.present? project = Project.visible.where(id: detail.value).first if detail.value.present?
old_project = Project.visible.where(id: detail.old_value).first if detail.old_value.present? old_project = Project.visible.where(id: detail.old_value).first if detail.old_value.present?
prop[:value] = link_to_project(project) if project.present? prop[:value] = link_to_project project if project.present?
prop[:old_value] = link_to_project(old_project) if old_project.present? prop[:old_value] = link_to_project old_project if old_project.present?
when 'db_entry' when 'db_entry'
prop = { label: custom_field.name } prop = { label: custom_field.name }
db_entry = DbEntry.visible.where(id: detail.value).first if detail.value.present? db_entry = DbEntry.visible.where(id: detail.value).first if detail.value.present?
old_db_entry = DbEntry.visible.where(id: detail.old_value).first if detail.old_value.present? old_db_entry = DbEntry.visible.where(id: detail.old_value).first if detail.old_value.present?
prop[:value] = link_to(db_entry.name, db_entry_url(db_entry)) if db_entry.present? prop[:value] = link_to db_entry.name, db_entry_url(db_entry) if db_entry.present?
prop[:old_value] = link_to(old_db_entry.name, db_entry_url(old_db_entry)) if old_db_entry.present? prop[:old_value] = link_to old_db_entry.name, db_entry_url(old_db_entry) if old_db_entry.present?
when 'password' when 'password'
prop = { label: custom_field.name } prop = { label: custom_field.name }
password = Password.visible.where(id: detail.value).first if detail.value.present? && defined?(Password) password = Password.visible.where(id: detail.value).first if detail.value.present? && defined?(Password)
old_password = Password.visible.where(id: detail.old_value).first if detail.old_value.present? && defined?(Password) old_password = Password.visible.where(id: detail.old_value).first if detail.old_value.present? && defined?(Password)
prop[:value] = link_to(password.name, password_url(password)) if password.present? prop[:value] = link_to password.name, password_url(password) if password.present?
prop[:old_value] = link_to(old_password.name, password_url(old_password)) if old_password.present? prop[:old_value] = link_to old_password.name, password_url(old_password) if old_password.present?
end end
prop prop

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module AdditionalsMenuHelper module AdditionalsMenuHelper
def additionals_top_menu_setup def additionals_top_menu_setup
return if Redmine::Plugin.installed? 'redmine_hrm' return if Redmine::Plugin.installed? 'redmine_hrm'
@ -18,11 +20,11 @@ module AdditionalsMenuHelper
end end
end end
def handle_top_submenu_item(menu_name, item) def handle_top_submenu_item(menu_name, **item)
handle_top_menu_item menu_name, item, with_submenu: true handle_top_menu_item menu_name, with_submenu: true, **item
end end
def handle_top_menu_item(menu_name, item, with_submenu: false) def handle_top_menu_item(menu_name, with_submenu: false, **item)
Redmine::MenuManager.map(:top_menu).delete(menu_name.to_sym) if Redmine::MenuManager.map(:top_menu).exists?(menu_name.to_sym) 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 = {}
@ -30,7 +32,7 @@ module AdditionalsMenuHelper
css_classes = [] css_classes = []
css_classes << 'top-submenu' if with_submenu css_classes << 'top-submenu' if with_submenu
css_classes << 'external' if item[:url].include? '://' css_classes << 'external' if item[:url].include? '://'
html_options[:class] = css_classes.join(' ') if css_classes.present? html_options[:class] = css_classes.join ' ' if css_classes.present?
html_options[:title] = item[:title] if item[:title].present? html_options[:title] = item[:title] if item[:title].present?
@ -40,9 +42,9 @@ module AdditionalsMenuHelper
menu_options[:if] = menu_options[:if] if menu_options[:if].present? menu_options[:if] = menu_options[:if] if menu_options[:if].present?
menu_options[:caption] = if item[:symbol].present? && item[:name].present? menu_options[:caption] = if item[:symbol].present? && item[:name].present?
font_awesome_icon(item[:symbol], post_text: item[:name]) font_awesome_icon item[:symbol], post_text: item[:name]
elsif item[:symbol].present? elsif item[:symbol].present?
font_awesome_icon(item[:symbol]) font_awesome_icon item[:symbol]
elsif item[:name].present? elsif item[:name].present?
item[:name].to_s item[:name].to_s
end end
@ -149,8 +151,8 @@ module AdditionalsMenuHelper
raise e unless e.class.to_s == 'NameError' raise e unless e.class.to_s == 'NameError'
end end
plugin_item = plugin_item_base.try(:additionals_help_items) unless plugin_item_base.nil? 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? plugin_item = additionals_help_items_fallbacks plugin.id if plugin_item.nil?
next if plugin_item.nil? next if plugin_item.nil?
@ -186,12 +188,12 @@ module AdditionalsMenuHelper
s << if item[:title] == '-' s << if item[:title] == '-'
tag.li tag.hr tag.li tag.hr
else else
html_options = { class: "help_item_#{idx}" } html_options = { class: +"help_item_#{idx}" }
if item[:url].include? '://' if item[:url].include? '://'
html_options[:class] << ' external' html_options[:class] << ' external'
html_options[:target] = '_blank' html_options[:target] = '_blank'
end end
tag.li(link_to(item[:title], item[:url], html_options)) tag.li link_to(item[:title], item[:url], html_options)
end end
end end
safe_join s safe_join s
@ -200,13 +202,8 @@ module AdditionalsMenuHelper
# Plugin help items definition for plugins, # Plugin help items definition for plugins,
# which do not have additionals_help_menu_items integration # which do not have additionals_help_menu_items integration
def additionals_help_items_fallbacks(plugin_id) def additionals_help_items_fallbacks(plugin_id)
plugins = { redmine_wiki_lists: [{ title: 'Wiki Lists Macros', plugins = { redmine_drawio: [{ title: 'draw.io usage',
url: 'https://www.r-labs.org/projects/wiki_lists/wiki/Wiki_Lists_en' }], url: 'https://github.com/mikitex70/redmine_drawio#usage' }],
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', redmine_contacts: [{ title: 'Redmine CRM',
url: 'https://www.redmineup.com/pages/help/crm', url: 'https://www.redmineup.com/pages/help/crm',
admin: true }], admin: true }],

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module AdditionalsProjectsHelper module AdditionalsProjectsHelper
def project_overview_name(_project, dashboard = nil) def project_overview_name(_project, dashboard = nil)
name = [l(:label_overview)] name = [l(:label_overview)]

View File

@ -1,22 +1,28 @@
# frozen_string_literal: true
module AdditionalsQueriesHelper module AdditionalsQueriesHelper
def additionals_query_session_key(object_type) def additionals_query_session_key(object_type)
"#{object_type}_query".to_sym "#{object_type}_query".to_sym
end end
def additionals_retrieve_query(object_type, options = {}) def additionals_retrieve_query(object_type, user_filter: nil)
session_key = additionals_query_session_key object_type session_key = additionals_query_session_key object_type
query_class = Object.const_get "#{object_type.camelcase}Query" query_class = Object.const_get "#{object_type.camelcase}Query"
if params[:query_id].present? if params[:query_id].present?
additionals_load_query_id(query_class, session_key, params[:query_id], options, object_type) additionals_load_query_id query_class,
session_key,
params[:query_id],
object_type,
user_filter: user_filter
elsif api_request? || elsif api_request? ||
params[:set_filter] || params[:set_filter] ||
session[session_key].nil? || session[session_key].nil? ||
session[session_key][:project_id] != (@project ? @project.id : nil) session[session_key][:project_id] != (@project ? @project.id : nil)
# Give it a name, required to be valid # Give it a name, required to be valid
@query = query_class.new(name: '_') @query = query_class.new name: '_'
@query.project = @project @query.project = @project
@query.user_filter = options[:user_filter] if options[:user_filter] @query.user_filter = user_filter if user_filter
@query.build_from_params(params) @query.build_from_params params
session[session_key] = { project_id: @query.project_id } 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 # 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), Rails.cache.write(additionals_query_cache_key(object_type),
@ -27,7 +33,7 @@ module AdditionalsQueriesHelper
sort_criteria: params[:sort].presence || @query.sort_criteria.to_a) sort_criteria: params[:sort].presence || @query.sort_criteria.to_a)
else else
# retrieve from session # retrieve from session
@query = query_class.find_by(id: session[session_key][:id]) if session[session_key][:id] @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) session_data = Rails.cache.read additionals_query_cache_key(object_type)
@query ||= query_class.new(name: '_', @query ||= query_class.new(name: '_',
filters: session_data.nil? ? nil : session_data[:filters], filters: session_data.nil? ? nil : session_data[:filters],
@ -51,14 +57,14 @@ module AdditionalsQueriesHelper
end end
end end
def additionals_load_query_id(query_class, session_key, query_id, options, object_type) def additionals_load_query_id(query_class, session_key, query_id, object_type, user_filter: nil)
scope = query_class.where(project_id: nil) scope = query_class.where project_id: nil
scope = scope.or(query_class.where(project_id: @project.id)) if @project scope = scope.or query_class.where(project_id: @project.id) if @project
@query = scope.find(query_id) @query = scope.find query_id
raise ::Unauthorized unless @query.visible? raise ::Unauthorized unless @query.visible?
@query.project = @project @query.project = @project
@query.user_filter = options[:user_filter] if options[:user_filter] @query.user_filter = user_filter if user_filter
session[session_key] = { id: @query.id, project_id: @query.project_id } session[session_key] = { id: @query.id, project_id: @query.project_id }
@query.sort_criteria = params[:sort] if params[:sort].present? @query.sort_criteria = params[:sort] if params[:sort].present?
@ -76,62 +82,58 @@ module AdditionalsQueriesHelper
"#{object_type}_query_data_#{session.id}_#{project_id}" "#{object_type}_query_data_#{session.id}_#{project_id}"
end end
def additionals_select2_search_users(options = {}) def additionals_select2_search_users(all_visible: false, where_filter: nil, where_params: nil)
q = params[:q].to_s.strip q = params[:q].to_s.strip
exclude_id = params[:user_id].to_i exclude_id = params[:user_id].to_i
scope = User.active.where type: 'User' scope = User.active.where type: 'User'
scope = scope.visible unless options[:all_visible] scope = scope.visible unless all_visible
scope = scope.where.not(id: exclude_id) if exclude_id.positive? scope = scope.where.not id: exclude_id if exclude_id.positive?
scope = scope.where(options[:where_filter], options[:where_params]) if options[:where_filter] scope = scope.where where_filter, where_params if where_filter
q.split.map { |search_string| scope = scope.like(search_string) } if q.present? q.split.map { |search_string| scope = scope.like search_string } if q.present?
scope = scope.order(last_login_on: :desc) scope = scope.order(last_login_on: :desc)
.limit(Additionals::SELECT2_INIT_ENTRIES) .limit(Additionals::SELECT2_INIT_ENTRIES)
@users = scope.to_a.sort! { |x, y| x.name <=> y.name } @users = scope.to_a.sort! { |x, y| x.name <=> y.name }
render layout: false, partial: 'auto_completes/additionals_users' render layout: false, partial: 'auto_completes/additionals_users'
end end
def additionals_query_to_xlsx(items, query, options = {}) def additionals_query_to_xlsx(items, query, no_id_link: false)
require 'write_xlsx' require 'write_xlsx'
columns = if options[:columns].present? || options[:c].present?
query.available_columns
else
query.columns
end
options[:filename] = StringIO.new('') options = { no_id_link: no_id_link,
filename: StringIO.new(+'') }
export_to_xlsx(items, columns, options) export_to_xlsx items, query.columns, options
options[:filename].string options[:filename].string
end end
def additionals_result_to_xlsx(items, columns, options = {}) def additionals_result_to_xlsx(items, columns, options)
raise 'option filename is mission' if options[:filename].blank? raise 'option filename is mission' if options[:filename].blank?
require 'write_xlsx' require 'write_xlsx'
export_to_xlsx(items, columns, options) export_to_xlsx items, columns, options
end end
def export_to_xlsx(items, columns, options) def export_to_xlsx(items, columns, options)
workbook = WriteXLSX.new(options[:filename]) workbook = WriteXLSX.new options[:filename]
worksheet = workbook.add_worksheet worksheet = workbook.add_worksheet
# Freeze header row and # column. # Freeze header row and # column.
freeze_row = options[:freeze_first_row].nil? || options[:freeze_first_row] ? 1 : 0 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 freeze_column = options[:freeze_first_column].nil? || options[:freeze_first_column] ? 1 : 0
worksheet.freeze_panes(freeze_row, freeze_column) worksheet.freeze_panes freeze_row, freeze_column
options[:columns_width] = if options[:xlsx_write_header_row].present? options[:columns_width] = if options[:xlsx_write_header_row].present?
send(options[:xlsx_write_header_row], workbook, worksheet, columns) send options[:xlsx_write_header_row], workbook, worksheet, columns
else else
xlsx_write_header_row(workbook, worksheet, columns) xlsx_write_header_row workbook, worksheet, columns
end end
options[:columns_width] = if options[:xlsx_write_item_rows].present? options[:columns_width] = if options[:xlsx_write_item_rows].present?
send(options[:xlsx_write_item_rows], workbook, worksheet, columns, items, options) send options[:xlsx_write_item_rows], workbook, worksheet, columns, items, options
else else
xlsx_write_item_rows(workbook, worksheet, columns, items, options) xlsx_write_item_rows workbook, worksheet, columns, items, options
end end
columns.size.times do |index| columns.size.times do |index|
worksheet.set_column(index, index, options[:columns_width][index]) worksheet.set_column index, index, options[:columns_width][index]
end end
workbook.close workbook.close
@ -152,20 +154,20 @@ module AdditionalsQueriesHelper
columns_width columns_width
end end
def xlsx_write_item_rows(workbook, worksheet, columns, items, options = {}) def xlsx_write_item_rows(workbook, worksheet, columns, items, options)
hyperlink_format = workbook.add_format(xlsx_cell_format(:link)) hyperlink_format = workbook.add_format xlsx_cell_format(:link)
even_text_format = workbook.add_format(xlsx_cell_format(:text, '', 0)) even_text_format = workbook.add_format xlsx_cell_format(:text, '', 0)
even_text_format.set_num_format(0x31) even_text_format.set_num_format 0x31
odd_text_format = workbook.add_format(xlsx_cell_format(:text, '', 1)) odd_text_format = workbook.add_format xlsx_cell_format(:text, '', 1)
odd_text_format.set_num_format(0x31) odd_text_format.set_num_format 0x31
items.each_with_index do |line, line_index| items.each_with_index do |line, line_index|
columns.each_with_index do |c, column_index| columns.each_with_index do |c, column_index|
value = csv_content(c, line) value = csv_content(c, line).dup
if c.name == :id # ID if c.name == :id # ID
if options[:no_id_link].blank? if options[:no_id_link].blank?
link = url_for(controller: line.class.name.underscore.pluralize, action: 'show', id: line.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) worksheet.write line_index + 1, column_index, link, hyperlink_format, value
else else
# id without link # id without link
worksheet.write(line_index + 1, worksheet.write(line_index + 1,
@ -173,13 +175,13 @@ module AdditionalsQueriesHelper
value, value,
workbook.add_format(xlsx_cell_format(:cell, value, line_index))) workbook.add_format(xlsx_cell_format(:cell, value, line_index)))
end end
elsif xlsx_hyperlink_cell?(value) elsif xlsx_hyperlink_cell? value
worksheet.write(line_index + 1, column_index, value[0..254], hyperlink_format, value) worksheet.write line_index + 1, column_index, value[0..254], hyperlink_format, value
elsif !c.inline? elsif !c.inline?
# block column can be multiline strings # block column can be multiline strings
value.gsub!("\r\n", "\n") value.gsub! "\r\n", "\n"
text_format = line_index.even? ? even_text_format : odd_text_format text_format = line_index.even? ? even_text_format : odd_text_format
worksheet.write_rich_string(line_index + 1, column_index, value, text_format) worksheet.write_rich_string line_index + 1, column_index, value, text_format
else else
worksheet.write(line_index + 1, worksheet.write(line_index + 1,
column_index, column_index,
@ -187,7 +189,7 @@ module AdditionalsQueriesHelper
workbook.add_format(xlsx_cell_format(:cell, value, line_index))) workbook.add_format(xlsx_cell_format(:cell, value, line_index)))
end end
width = xlsx_get_column_width(value) width = xlsx_get_column_width value
options[:columns_width][column_index] = width if options[:columns_width][column_index] < width options[:columns_width][column_index] = width if options[:columns_width][column_index] < width
end end
end end
@ -238,7 +240,7 @@ module AdditionalsQueriesHelper
# TODO: this is a temporary fix and should be removed # TODO: this is a temporary fix and should be removed
# after https://www.redmine.org/issues/29830 is in Redmine core. # after https://www.redmine.org/issues/29830 is in Redmine core.
def query_as_hidden_field_tags(query) def query_as_hidden_field_tags(query)
tags = hidden_field_tag('set_filter', '1', id: nil) tags = hidden_field_tag 'set_filter', '1', id: nil
if query.filters.present? if query.filters.present?
query.filters.each do |field, filter| query.filters.each do |field, filter|
@ -254,7 +256,7 @@ module AdditionalsQueriesHelper
ignored_block_columns = query.block_columns.map(&:name) ignored_block_columns = query.block_columns.map(&:name)
query.columns.each do |column| query.columns.each do |column|
next if ignored_block_columns.include?(column.name) next if ignored_block_columns.include? column.name
tags << hidden_field_tag('c[]', column.name, id: nil) tags << hidden_field_tag('c[]', column.name, id: nil)
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module AdditionalsRoutesHelper module AdditionalsRoutesHelper
def _dashboards_path(project, *args) def _dashboards_path(project, *args)
if project if project
@ -71,7 +73,7 @@ module AdditionalsRoutesHelper
end end
end end
def dashboard_link_path(project, dashboard, options = {}) def dashboard_link_path(project, dashboard, **options)
options[:dashboard_id] = dashboard.id options[:dashboard_id] = dashboard.id
if dashboard.dashboard_type == DashboardContentProject::TYPE_NAME if dashboard.dashboard_type == DashboardContentProject::TYPE_NAME
project_path project, options project_path project, options

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
module AdditionalsSelect2Helper module AdditionalsSelect2Helper
def additionals_select2_tag(name, option_tags = nil, options = {}) def additionals_select2_tag(name, option_tags, options)
s = select_tag(name, option_tags, options) s = select_tag name, option_tags, options
id = options.delete(:id) || sanitize_to_id(name) id = options.delete(:id) || sanitize_to_id(name)
s << hidden_field_tag("#{name}[]", '') if options[:multiple] && options.fetch(:include_hidden, true) s << hidden_field_tag("#{name}[]", '') if options[:multiple] && options.fetch(:include_hidden, true)
@ -8,7 +10,7 @@ module AdditionalsSelect2Helper
end end
# Transforms select filters of +type+ fields into select2 # Transforms select filters of +type+ fields into select2
def additionals_transform_to_select2(type, options = {}) def additionals_transform_to_select2(type, options)
javascript_tag("setSelect2Filter('#{type}', #{options.to_json});") unless type.empty? javascript_tag "setSelect2Filter('#{type}', #{options.to_json});" unless type.empty?
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module AdditionalsSettingsHelper module AdditionalsSettingsHelper
def additionals_settings_tabs def additionals_settings_tabs
tabs = [{ name: 'general', partial: 'additionals/settings/general', label: :label_general }, tabs = [{ name: 'general', partial: 'additionals/settings/general', label: :label_general },
@ -13,7 +15,7 @@ module AdditionalsSettingsHelper
tabs tabs
end end
def additionals_settings_checkbox(name, options = {}) def additionals_settings_checkbox(name, **options)
active_value = options.delete(:active_value).presence || @settings.present? && @settings[name] active_value = options.delete(:active_value).presence || @settings.present? && @settings[name]
tag_name = options.delete(:tag_name).presence || "settings[#{name}]" tag_name = options.delete(:tag_name).presence || "settings[#{name}]"
@ -36,50 +38,50 @@ module AdditionalsSettingsHelper
s = [label_tag(tag_name, label_title)] s = [label_tag(tag_name, label_title)]
s << hidden_field_tag(tag_name, 0, id: nil) if !custom_value || value_is_bool s << hidden_field_tag(tag_name, 0, id: nil) if !custom_value || value_is_bool
s << check_box_tag(tag_name, value, checked, options) s << check_box_tag(tag_name, value, checked, **options)
safe_join s safe_join s
end end
def additionals_settings_numberfield(name, options = {}) def additionals_settings_numberfield(name, **options)
additionals_settings_input_field :number_field_tag, name, options additionals_settings_input_field :number_field_tag, name, **options
end end
def additionals_settings_textfield(name, options = {}) def additionals_settings_textfield(name, **options)
additionals_settings_input_field :text_field_tag, name, options additionals_settings_input_field :text_field_tag, name, **options
end end
def additionals_settings_passwordfield(name, options = {}) def additionals_settings_passwordfield(name, **options)
additionals_settings_input_field :password_field_tag, name, options additionals_settings_input_field :password_field_tag, name, **options
end end
def additionals_settings_urlfield(name, options = {}) def additionals_settings_urlfield(name, **options)
additionals_settings_input_field :url_field_tag, name, options additionals_settings_input_field :url_field_tag, name, **options
end end
def additionals_settings_select(name, values, options = {}) def additionals_settings_select(name, values, **options)
tag_name = options.delete(:tag_name).presence || "settings[#{name}]" tag_name = options.delete(:tag_name).presence || "settings[#{name}]"
label_title = [options.delete(:label).presence || l("label_#{name}")] label_title = [options.delete(:label).presence || l("label_#{name}")]
label_title << tag.span('*', class: 'required') if options[:required].present? label_title << tag.span('*', class: 'required') if options[:required].present?
safe_join [label_tag(tag_name, safe_join(label_title, ' ')), safe_join [label_tag(tag_name, safe_join(label_title, ' ')),
select_tag(tag_name, values, options)] select_tag(tag_name, values, **options)]
end end
def additionals_settings_textarea(name, options = {}) def additionals_settings_textarea(name, **options)
label_title = options.delete(:label).presence || l("label_#{name}") label_title = options.delete(:label).presence || l("label_#{name}")
value = options.delete(:value).presence || @settings[name] value = options.delete(:value).presence || @settings[name]
options[:class] = 'wiki-edit' unless options.key?(:class) options[:class] = 'wiki-edit' unless options.key? :class
options[:rows] = addtionals_textarea_cols(value) unless options.key?(:rows) options[:rows] = addtionals_textarea_cols value unless options.key? :rows
safe_join [label_tag("settings[#{name}]", label_title), safe_join [label_tag("settings[#{name}]", label_title),
text_area_tag("settings[#{name}]", value, options)] text_area_tag("settings[#{name}]", value, **options)]
end end
private private
def additionals_settings_input_field(tag_field, name, options = {}) def additionals_settings_input_field(tag_field, name, **options)
tag_name = options.delete(:tag_name).presence || "settings[#{name}]" tag_name = options.delete(:tag_name).presence || "settings[#{name}]"
value = if options.key? :value value = if options.key? :value
options.delete(:value).presence options.delete(:value).presence
@ -91,6 +93,6 @@ module AdditionalsSettingsHelper
label_title << tag.span('*', class: 'required') if options[:required].present? label_title << tag.span('*', class: 'required') if options[:required].present?
safe_join [label_tag(tag_name, safe_join(label_title, ' ')), safe_join [label_tag(tag_name, safe_join(label_title, ' ')),
send(tag_field, tag_name, value, options)], ' ' send(tag_field, tag_name, value, **options)], ' '
end end
end end

View File

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

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module DashboardsHelper module DashboardsHelper
def dashboard_async_cache(dashboard, block, async, settings, &content_block) def dashboard_async_cache(dashboard, block, async, settings, &content_block)
cache render_async_cache_key(_dashboard_async_blocks_path(@project, dashboard.async_params(block, async, settings))), cache render_async_cache_key(_dashboard_async_blocks_path(@project, dashboard.async_params(block, async, settings))),
@ -32,7 +34,7 @@ module DashboardsHelper
def sidebar_dashboards(dashboard, project = nil, user = nil) def sidebar_dashboards(dashboard, project = nil, user = nil)
user ||= User.current user ||= User.current
scope = Dashboard.visible.includes([:author]) scope = Dashboard.visible.includes [:author]
scope = if project.present? scope = if project.present?
scope = scope.project_only scope = scope.project_only
@ -111,7 +113,7 @@ module DashboardsHelper
return '' unless dashboards.any? return '' unless dashboards.any?
tag.h3(title, class: 'dashboards') + tag.h3(title, class: 'dashboards') +
tag.ul(class: 'dashboards') do tag.ul(class: 'dashboards') do # rubocop: disable Style/MethodCallWithArgsParentheses
dashboards.each do |dashboard| dashboards.each do |dashboard|
selected = dashboard.id == if params[:dashboard_id].present? selected = dashboard.id == if params[:dashboard_id].present?
params[:dashboard_id].to_i params[:dashboard_id].to_i
@ -119,7 +121,7 @@ module DashboardsHelper
active_dashboard.id active_dashboard.id
end end
css = 'dashboard' css = +'dashboard'
css << ' selected' if selected css << ' selected' if selected
li_class = nil li_class = nil
@ -143,14 +145,14 @@ module DashboardsHelper
end end
end end
def dashboard_link(dashboard, project, options = {}) def dashboard_link(dashboard, project, **options)
if options[:title].blank? && dashboard.public? if options[:title].blank? && dashboard.public?
author = if dashboard.author_id == User.current.id author = if dashboard.author_id == User.current.id
l :label_me l :label_me
else else
dashboard.author dashboard.author
end end
options[:title] = l(:label_dashboard_author, name: author) options[:title] = l :label_dashboard_author, name: author
end end
name = options.delete(:name) || dashboard.name name = options.delete(:name) || dashboard.name
@ -171,10 +173,10 @@ module DashboardsHelper
end end
end end
def delete_dashboard_link(url, options = {}) def delete_dashboard_link(url)
options = { method: :delete, options = { method: :delete,
data: { confirm: l(:text_are_you_sure) }, data: { confirm: l(:text_are_you_sure) },
class: 'icon icon-del' }.merge(options) class: 'icon icon-del' }
link_to l(:button_dashboard_delete), url, options link_to l(:button_dashboard_delete), url, options
end end
@ -252,7 +254,7 @@ module DashboardsHelper
exposed_params: %i[sort], exposed_params: %i[sort],
partial: 'dashboards/blocks/query_list' } partial: 'dashboards/blocks/query_list' }
partial_locals[:async][:unique_params] = [Redmine::Utils.random_hex(16)] if params[:refresh].present? partial_locals[:async][:unique_params] = [Redmine::Utils.random_hex(16)] if params[:refresh].present?
partial_locals[:async] = partial_locals[:async].merge(block_definition[:async]) if block_definition[:async] partial_locals[:async] = partial_locals[:async].merge block_definition[:async] if block_definition[:async]
elsif block_definition[:async] elsif block_definition[:async]
partial_locals[:async] = block_definition[:async] partial_locals[:async] = block_definition[:async]
end end
@ -277,9 +279,9 @@ module DashboardsHelper
title << query_block[:label] title << query_block[:label]
title << if query_block[:with_project] title << if query_block[:with_project]
link_to(query.name, send(query_block[:link_helper], project, query.as_params)) link_to query.name, send(query_block[:link_helper], project, query.as_params)
else else
link_to(query.name, send(query_block[:link_helper], query.as_params)) link_to query.name, send(query_block[:link_helper], query.as_params)
end end
safe_join title, Additionals::LIST_SEPARATOR safe_join title, Additionals::LIST_SEPARATOR
@ -289,9 +291,9 @@ module DashboardsHelper
return if dashboard.visibility == Dashboard::VISIBILITY_PRIVATE return if dashboard.visibility == Dashboard::VISIBILITY_PRIVATE
title = if query.visibility == Query::VISIBILITY_PRIVATE title = if query.visibility == Query::VISIBILITY_PRIVATE
l(:alert_only_visible_by_yourself) l :alert_only_visible_by_yourself
elsif block_definition.key?(:admin_only) && block_definition[:admin_only] elsif block_definition.key?(:admin_only) && block_definition[:admin_only]
l(:alert_only_visible_by_admins) l :alert_only_visible_by_admins
end end
return if title.nil? return if title.nil?
@ -322,7 +324,7 @@ module DashboardsHelper
max_entries = settings[:max_entries] || DashboardContent::DEFAULT_MAX_ENTRIES max_entries = settings[:max_entries] || DashboardContent::DEFAULT_MAX_ENTRIES
scope = Document.visible scope = Document.visible
scope = scope.where(project: dashboard.project) if dashboard.project scope = scope.where project: dashboard.project if dashboard.project
documents = scope.order(created_on: :desc) documents = scope.order(created_on: :desc)
.limit(max_entries) .limit(max_entries)
@ -357,10 +359,10 @@ module DashboardsHelper
days = 7 if days < 1 || days > 365 days = 7 if days < 1 || days > 365
scope = TimeEntry.where user_id: User.current.id scope = TimeEntry.where user_id: User.current.id
scope = scope.where(project_id: dashboard.content_project.id) unless dashboard.content_project.nil? scope = scope.where project_id: dashboard.content_project.id unless dashboard.content_project.nil?
entries_today = scope.where(spent_on: User.current.today) entries_today = scope.where spent_on: User.current.today
entries_days = scope.where(spent_on: User.current.today - (days - 1)..User.current.today) entries_days = scope.where spent_on: User.current.today - (days - 1)..User.current.today
render partial: 'dashboards/blocks/my_spent_time', render partial: 'dashboards/blocks/my_spent_time',
locals: { block: block, locals: { block: block,
@ -379,7 +381,7 @@ module DashboardsHelper
Redmine::Activity::Fetcher.new(user, options) Redmine::Activity::Fetcher.new(user, options)
.events(nil, nil, limit: max_entries) .events(nil, nil, limit: max_entries)
.group_by { |event| user.time_to_date(event.event_datetime) } .group_by { |event| user.time_to_date event.event_datetime }
end end
def dashboard_feed_catcher(url, max_entries) def dashboard_feed_catcher(url, max_entries)
@ -391,7 +393,7 @@ module DashboardsHelper
begin begin
URI.parse(url).open do |rss_feed| URI.parse(url).open do |rss_feed|
rss = RSS::Parser.parse(rss_feed) rss = RSS::Parser.parse rss_feed
rss.items.each do |item| rss.items.each do |item|
cnt += 1 cnt += 1
feed[:items] << { title: item.title.try(:content)&.presence || item.title, feed[:items] << { title: item.title.try(:content)&.presence || item.title,
@ -430,7 +432,7 @@ module DashboardsHelper
# Renders a single block content # Renders a single block content
def render_dashboard_block_content(block, block_definition, dashboard, overwritten_settings = {}) def render_dashboard_block_content(block, block_definition, dashboard, overwritten_settings = {})
settings = dashboard.layout_settings block settings = dashboard.layout_settings block
settings = settings.merge(overwritten_settings) if overwritten_settings.present? settings = settings.merge overwritten_settings if overwritten_settings.present?
partial = block_definition[:partial] partial = block_definition[:partial]
partial_locals = build_dashboard_partial_locals block, block_definition, settings, dashboard partial_locals = build_dashboard_partial_locals block, block_definition, settings, dashboard
@ -441,7 +443,7 @@ module DashboardsHelper
begin begin
render partial: partial, locals: partial_locals render partial: partial, locals: partial_locals
rescue ActionView::MissingTemplate rescue ActionView::MissingTemplate
Rails.logger.warn("Partial \"#{partial}\" missing for block \"#{block}\" found in #{dashboard.name} (id=#{dashboard.id})") Rails.logger.warn "Partial \"#{partial}\" missing for block \"#{block}\" found in #{dashboard.name} (id=#{dashboard.id})"
nil nil
end end
else else

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AdditionalsJob < ActiveJob::Base class AdditionalsJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock # Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked # retry_on ActiveRecord::Deadlocked

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AdditionalsChart class AdditionalsChart
include ActiveRecord::Sanitization include ActiveRecord::Sanitization
include Redmine::I18n include Redmine::I18n
@ -15,7 +17,7 @@ class AdditionalsChart
end end
# build return value # build return value
def build_chart_data(datasets, options = {}) def build_chart_data(datasets, **options)
cached_labels = labels cached_labels = labels
data = { datasets: datasets.to_json, data = { datasets: datasets.to_json,
labels: cached_labels.keys, labels: cached_labels.keys,
@ -23,13 +25,13 @@ class AdditionalsChart
required_labels = options.key?(:required_labels) ? options.delete(:required_labels) : 2 required_labels = options.key?(:required_labels) ? options.delete(:required_labels) : 2
data[:valid] = cached_labels.any? && cached_labels.count >= required_labels unless options.key?(:valid) data[:valid] = cached_labels.any? && cached_labels.count >= required_labels unless options.key? :valid
data[:width] = self::CHART_DEFAULT_WIDTH unless options.key?(:width) data[:width] = self::CHART_DEFAULT_WIDTH unless options.key? :width
data[:height] = self::CHART_DEFAULT_HEIGHT unless options.key?(:height) data[:height] = self::CHART_DEFAULT_HEIGHT unless options.key? :height
data[:value_link_method] = '_project_issues_path' unless options.key?(:value_link_method) data[:value_link_method] = '_project_issues_path' unless options.key? :value_link_method
data[:color_schema] = color_schema data[:color_schema] = color_schema
data.merge(options) data.merge options
end end
private private
@ -37,7 +39,7 @@ class AdditionalsChart
def build_values_without_gaps(data, gap_value = 0) def build_values_without_gaps(data, gap_value = 0)
values = [] values = []
labels.each do |label, _label_id| labels.each do |label, _label_id|
values << if data.key?(label) values << if data.key? label
data[label] data[label]
else else
gap_value gap_value

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AdditionalsFontAwesome class AdditionalsFontAwesome
include Redmine::I18n include Redmine::I18n
@ -9,7 +11,7 @@ class AdditionalsFontAwesome
data = YAML.safe_load(ERB.new(IO.read(File.join(Additionals.plugin_dir, 'config', 'fontawesome_icons.yml'))).result) || {} data = YAML.safe_load(ERB.new(IO.read(File.join(Additionals.plugin_dir, 'config', 'fontawesome_icons.yml'))).result) || {}
icons = {} icons = {}
data.each do |key, values| data.each do |key, values|
icons[key] = { unicode: values['unicode'], label: values['label'] } if values['styles'].include?(convert_type2style(type)) icons[key] = { unicode: values['unicode'], label: values['label'] } if values['styles'].include? convert_type2style(type)
end end
icons icons
end end
@ -48,7 +50,7 @@ class AdditionalsFontAwesome
end end
def classes(value) def classes(value)
info = value_info(value) info = value_info value
return '' if info.blank? return '' if info.blank?
info[:classes] info[:classes]
@ -65,27 +67,27 @@ class AdditionalsFontAwesome
# show only one value as current selected # show only one value as current selected
# (all other options are retrieved by select2 # (all other options are retrieved by select2
def active_option_for_select(selected) def active_option_for_select(selected)
info = value_info(selected, with_details: true) info = value_info selected, with_details: true
return [] if info.blank? return [] if info.blank?
[[info[:label], selected]] [[info[:label], selected]]
end end
def value_info(value, options = {}) def value_info(value, with_details: false)
return {} if value.blank? return {} if value.blank?
values = value.split('_') values = value.split '_'
return {} unless values.count == 2 return {} unless values.count == 2
info = { type: values[0].to_sym, info = { type: values[0].to_sym,
name: "fa-#{values[1]}" } name: "fa-#{values[1]}" }
info[:classes] = "#{info[:type]} #{info[:name]}" info[:classes] = "#{info[:type]} #{info[:name]}"
info[:font_weight] = font_weight(info[:type]) info[:font_weight] = font_weight info[:type]
info[:font_family] = font_family(info[:type]) info[:font_family] = font_family info[:type]
if options[:with_details] if with_details
info.merge!(load_details(info[:type], values[1])) info.merge! load_details(info[:type], values[1])
return {} if info[:unicode].blank? return {} if info[:unicode].blank?
end end
@ -94,12 +96,12 @@ class AdditionalsFontAwesome
def search_for_select(search, selected = nil) def search_for_select(search, selected = nil)
# could be more then one # could be more then one
selected_store = selected.to_s.split(',') selected_store = selected.to_s.split ','
icons = search_in_type(:far, search, selected_store) icons = search_in_type :far, search, selected_store
cnt = icons.count cnt = icons.count
return icons if cnt >= SEARCH_LIMIT return icons if cnt >= SEARCH_LIMIT
icons += search_in_type(:fas, search, selected_store, cnt) icons += search_in_type :fas, search, selected_store, cnt
cnt = icons.count cnt = icons.count
return icons if cnt >= SEARCH_LIMIT return icons if cnt >= SEARCH_LIMIT
@ -109,7 +111,7 @@ class AdditionalsFontAwesome
def convert2mermaid(icon) def convert2mermaid(icon)
return if icon.blank? return if icon.blank?
parts = icon.split('_') parts = icon.split '_'
return unless parts.count == 2 return unless parts.count == 2
"#{parts.first}:fa-#{parts.last}" "#{parts.first}:fa-#{parts.last}"
@ -125,7 +127,7 @@ class AdditionalsFontAwesome
search[0].downcase search[0].downcase
elsif search_length.zero? && selected_store.any? elsif search_length.zero? && selected_store.any?
selected = selected_store.first selected = selected_store.first
fa = selected.split('_') fa = selected.split '_'
search = fa[1][0] if fa.count > 1 search = fa[1][0] if fa.count > 1
search search
end end
@ -147,7 +149,7 @@ class AdditionalsFontAwesome
end end
def load_details(type, name) def load_details(type, name)
return {} unless FONTAWESOME_ICONS.key?(type) return {} unless FONTAWESOME_ICONS.key? type
values = FONTAWESOME_ICONS[type][name] values = FONTAWESOME_ICONS[type][name]
return {} if values.blank? return {} if values.blank?

View File

@ -1,9 +1,11 @@
# frozen_string_literal: true
class AdditionalsImport < Import class AdditionalsImport < Import
class_attribute :import_class class_attribute :import_class
# Returns the objects that were imported # Returns the objects that were imported
def saved_objects def saved_objects
object_ids = saved_items.pluck(:obj_id) object_ids = saved_items.pluck :obj_id
import_class.where(id: object_ids).order(:id) import_class.where(id: object_ids).order(:id)
end end
@ -24,17 +26,17 @@ class AdditionalsImport < Import
object.custom_field_values.each_with_object({}) do |v, h| object.custom_field_values.each_with_object({}) do |v, h|
value = case v.custom_field.field_format value = case v.custom_field.field_format
when 'date' when 'date'
row_date(row, "cf_#{v.custom_field.id}") row_date row, "cf_#{v.custom_field.id}"
else else
row_value(row, "cf_#{v.custom_field.id}") row_value row, "cf_#{v.custom_field.id}"
end end
next unless value next unless value
h[v.custom_field.id.to_s] = h[v.custom_field.id.to_s] =
if value.is_a? Array if value.is_a? Array
value.map { |val| v.custom_field.value_from_keyword(val.strip, object) }.flatten!&.compact value.map { |val| v.custom_field.value_from_keyword val.strip, object }.flatten!&.compact
else else
v.custom_field.value_from_keyword(value, object) v.custom_field.value_from_keyword value, object
end end
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AdditionalsInfo class AdditionalsInfo
include Redmine::I18n include Redmine::I18n
@ -43,11 +45,11 @@ class AdditionalsInfo
days = (secs / 86_400).round days = (secs / 86_400).round
end end
if days >= 1 if days >= 1
"#{days} #{l(:days, count: days)}" "#{days} #{l :days, count: days}"
elsif hours >= 1 elsif hours >= 1
"#{hours} #{l(:hours, count: hours)}" "#{hours} #{l :hours, count: hours}"
else else
"#{min} #{l(:minutes, count: min)}" "#{min} #{l :minutes, count: min}"
end end
else else
# this should be work on macOS # this should be work on macOS
@ -61,7 +63,7 @@ class AdditionalsInfo
end end
else else
days = `uptime | awk '{print $3}'`.to_i.round days = `uptime | awk '{print $3}'`.to_i.round
"#{days} #{l(:days, count: days)}" "#{days} #{l :days, count: days}"
end end
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AdditionalsJournal class AdditionalsJournal
class << self class << self
def save_journal_history(journal, prop_key, ids_old, ids) def save_journal_history(journal, prop_key, ids_old, ids)
@ -6,7 +8,7 @@ class AdditionalsJournal
ids_all.each do |id| ids_all.each do |id|
next if ids_old.include?(id) && ids.include?(id) next if ids_old.include?(id) && ids.include?(id)
if ids.include?(id) if ids.include? id
value = id value = id
old_value = nil old_value = nil
else else
@ -29,10 +31,10 @@ class AdditionalsJournal
new_entries = entries.select { |entry| entry.id.blank? } new_entries = entries.select { |entry| entry.id.blank? }
return true if new_entries.blank? return true if new_entries.blank?
new_entries.map! { |entry| entry.send(entry_id) } new_entries.map! { |entry| entry.send entry_id }
return false if new_entries.count != new_entries.uniq.count return false if new_entries.count != new_entries.uniq.count
old_entries.map! { |entry| entry.send(entry_id) } old_entries.map! { |entry| entry.send entry_id }
return false unless (old_entries & new_entries).count.zero? return false unless (old_entries & new_entries).count.zero?
true true

View File

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

View File

@ -1,17 +1,19 @@
# frozen_string_literal: true
module AdditionalsQuery module AdditionalsQuery
def column_with_prefix?(prefix) def column_with_prefix?(prefix)
columns.detect { |c| c.name.to_s.start_with?("#{prefix}.") }.present? columns.detect { |c| c.name.to_s.start_with? "#{prefix}." }.present?
end end
def available_column_names(options = {}) def available_column_names(only_sortable: false)
names = available_columns.dup names = available_columns.dup
names.flatten! names.flatten!
names.select! { |col| col.sortable.present? } if options[:only_sortable] names.select! { |col| col.sortable.present? } if only_sortable
names.map(&:name) names.map(&:name)
end end
def sql_for_enabled_module(table_field, module_names) def sql_for_enabled_module(table_field, module_names)
module_names = Array(module_names) module_names = Array module_names
sql = [] sql = []
module_names.each do |module_name| module_names.each do |module_name|
@ -19,7 +21,7 @@ module AdditionalsQuery
" AND #{EnabledModule.table_name}.name='#{module_name}')" " AND #{EnabledModule.table_name}.name='#{module_name}')"
end end
sql.join(' AND ') sql.join ' AND '
end end
def fix_sql_for_text_field(field, operator, value, table_name = nil, target_field = nil) def fix_sql_for_text_field(field, operator, value, table_name = nil, target_field = nil)
@ -27,15 +29,15 @@ module AdditionalsQuery
target_field = field if target_field.blank? target_field = field if target_field.blank?
sql = [] sql = []
sql << "(#{sql_for_field(field, operator, value, table_name, target_field)})" sql << "(#{sql_for_field field, operator, value, table_name, target_field})"
sql << "#{table_name}.#{target_field} != ''" if operator == '*' sql << "#{table_name}.#{target_field} != ''" if operator == '*'
sql.join(' AND ') sql.join ' AND '
end end
def initialize_ids_filter(options = {}) def initialize_ids_filter(label: nil)
if options[:label] if label
add_available_filter 'ids', type: :integer, label: options[:label] add_available_filter 'ids', type: :integer, label: label
else else
add_available_filter 'ids', type: :integer, name: '#' add_available_filter 'ids', type: :integer, name: '#'
end end
@ -82,35 +84,29 @@ module AdditionalsQuery
values: -> { project_statuses_values } values: -> { project_statuses_values }
end end
def initialize_project_filter(options = {}) def initialize_project_filter(always: false, position: nil)
if project.nil? || options[:always] if project.nil? || always
add_available_filter 'project_id', order: options[:position], add_available_filter 'project_id', order: position,
type: :list, type: :list,
values: -> { project_values } values: -> { project_values }
end end
return if project.nil? || project.leaf? || subproject_values.empty? return if project.nil? || project.leaf? || subproject_values.empty?
add_available_filter 'subproject_id', order: options[:position], add_available_filter 'subproject_id', order: position,
type: :list_subprojects, type: :list_subprojects,
values: -> { subproject_values } values: -> { subproject_values }
end end
def initialize_created_filter(options = {}) def initialize_created_filter(position: nil, label: nil)
add_available_filter 'created_on', order: options[:position], add_available_filter 'created_on', order: position,
type: :date_past, type: :date_past,
label: options[:label].presence label: label
end end
def initialize_updated_filter(options = {}) def initialize_updated_filter(position: nil, label: nil)
add_available_filter 'updated_on', order: options[:position], add_available_filter 'updated_on', order: position,
type: :date_past, type: :date_past,
label: options[:label].presence label: label
end
def initialize_tags_filter(options = {})
add_available_filter 'tags', order: options[:position],
type: :list_optional,
values: -> { tag_values(project) }
end end
def initialize_approved_filter def initialize_approved_filter
@ -123,38 +119,26 @@ module AdditionalsQuery
label: :field_approved label: :field_approved
end end
def initialize_author_filter(options = {}) def initialize_author_filter(position: nil)
add_available_filter 'author_id', order: options[:position], add_available_filter 'author_id', order: position,
type: :list_optional, type: :list_optional,
values: -> { author_values } values: -> { author_values }
end end
def initialize_assignee_filter(options = {}) def initialize_assignee_filter(position: nil)
add_available_filter 'assigned_to_id', order: options[:position], add_available_filter 'assigned_to_id', order: position,
type: :list_optional, type: :list_optional,
values: -> { assigned_to_all_values } values: -> { assigned_to_all_values }
end end
def initialize_watcher_filter(options = {}) def initialize_watcher_filter(position: nil)
return unless User.current.logged? return unless User.current.logged?
add_available_filter 'watcher_id', order: options[:position], add_available_filter 'watcher_id', order: position,
type: :list, type: :list,
values: -> { watcher_values_for_manage_public_queries } values: -> { watcher_values_for_manage_public_queries }
end end
def tag_values(project)
values = if project
queried_class.available_tags project: project.id
else
queried_class.available_tags
end
return [] if values.blank?
values.collect { |t| [t.name, t.name] }
end
# issue independend values. Use assigned_to_values from Redmine, if you want it only for issues # issue independend values. Use assigned_to_values from Redmine, if you want it only for issues
def assigned_to_all_values def assigned_to_all_values
assigned_to_values = [] assigned_to_values = []
@ -168,7 +152,7 @@ module AdditionalsQuery
# and with users (not groups) # and with users (not groups)
def watcher_values_for_manage_public_queries def watcher_values_for_manage_public_queries
watcher_values = [["<< #{l :label_me} >>", 'me']] 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 += users.collect { |s| [s.name, s.id.to_s] } if User.current.allowed_to? :manage_public_queries, project, global: true
watcher_values watcher_values
end end
@ -181,12 +165,8 @@ module AdditionalsQuery
" #{sql_for_field field, '=', value, db_table, 'user_id'})" " #{sql_for_field field, '=', value, db_table, 'user_id'})"
end end
def sql_for_tags_field(field, _operator, value)
AdditionalTags.sql_for_tags_field queried_class, operator_for(field), value
end
def sql_for_is_private_field(_field, operator, value) def sql_for_is_private_field(_field, operator, value)
if bool_operator(operator, value) if bool_operator operator, value
return '' if value.count > 1 return '' if value.count > 1
"#{queried_table_name}.is_private = #{self.class.connection.quoted_true}" "#{queried_table_name}.is_private = #{self.class.connection.quoted_true}"
@ -215,10 +195,10 @@ module AdditionalsQuery
raise ::Query::StatementInvalid, e.message raise ::Query::StatementInvalid, e.message
end end
def results_scope(options = {}) def results_scope(**options)
order_option = [group_by_sort_order, (options[:order] || sort_clause)].flatten!.to_a.reject(&:blank?) order_option = [group_by_sort_order, (options[:order] || sort_clause)].flatten!.to_a.reject(&:blank?)
objects_scope(options) objects_scope(**options.except(:order, :limit, :offset))
.order(order_option) .order(order_option)
.joins(joins_for_order_statement(order_option.join(','))) .joins(joins_for_order_statement(order_option.join(',')))
.limit(options[:limit]) .limit(options[:limit])

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class Dashboard < ActiveRecord::Base class Dashboard < ActiveRecord::Base
include Redmine::I18n include Redmine::I18n
include Redmine::SafeAttributes include Redmine::SafeAttributes
@ -22,10 +24,10 @@ class Dashboard < ActiveRecord::Base
VISIBILITY_ROLES = 1 VISIBILITY_ROLES = 1
VISIBILITY_PUBLIC = 2 VISIBILITY_PUBLIC = 2
scope :by_project, (->(project_id) { where(project_id: project_id) if project_id.present? }) scope :by_project, (->(project_id) { where project_id: project_id if project_id.present? })
scope :sorted, (-> { order("#{Dashboard.table_name}.name") }) scope :sorted, (-> { order "#{Dashboard.table_name}.name" })
scope :welcome_only, (-> { where(dashboard_type: DashboardContentWelcome::TYPE_NAME) }) scope :welcome_only, (-> { where dashboard_type: DashboardContentWelcome::TYPE_NAME })
scope :project_only, (-> { where(dashboard_type: DashboardContentProject::TYPE_NAME) }) scope :project_only, (-> { where dashboard_type: DashboardContentProject::TYPE_NAME })
safe_attributes 'name', 'description', 'enable_sidebar', safe_attributes 'name', 'description', 'enable_sidebar',
'always_expose', 'project_id', 'author_id', 'always_expose', 'project_id', 'author_id',
@ -47,7 +49,7 @@ class Dashboard < ActiveRecord::Base
safe_attributes 'system_default', safe_attributes 'system_default',
if: (lambda do |dashboard, user| if: (lambda do |dashboard, user|
user.allowed_to?(:set_system_dashboards, dashboard.project, global: true) user.allowed_to? :set_system_dashboards, dashboard.project, global: true
end) end)
before_save :dashboard_type_check, :visibility_check, :set_options_hash, :clear_unused_block_settings before_save :dashboard_type_check, :visibility_check, :set_options_hash, :clear_unused_block_settings
@ -73,10 +75,10 @@ class Dashboard < ActiveRecord::Base
def default(dashboard_type, project = nil, user = User.current) def default(dashboard_type, project = nil, user = User.current)
recently_id = User.current.pref.recently_used_dashboard dashboard_type, project recently_id = User.current.pref.recently_used_dashboard dashboard_type, project
scope = where(dashboard_type: dashboard_type) scope = where dashboard_type: dashboard_type
scope = scope.where(project_id: project.id).or(scope.where(project_id: nil)) if project.present? scope = scope.where(project_id: project.id).or(scope.where(project_id: nil)) if project.present?
dashboard = scope.visible.find_by(id: recently_id) if recently_id.present? dashboard = scope.visible.find_by id: recently_id if recently_id.present?
if dashboard.blank? if dashboard.blank?
scope = scope.where(system_default: true).or(scope.where(author_id: user.id)) scope = scope.where(system_default: true).or(scope.where(author_id: user.id))
@ -86,7 +88,7 @@ class Dashboard < ActiveRecord::Base
Rails.logger.debug 'default cleanup required' Rails.logger.debug 'default cleanup required'
# Remove invalid recently_id # Remove invalid recently_id
if project.present? if project.present?
User.current.pref.recently_used_dashboards[dashboard_type].delete(project.id) User.current.pref.recently_used_dashboards[dashboard_type].delete project.id
else else
User.current.pref.recently_used_dashboards[dashboard_type] = nil User.current.pref.recently_used_dashboards[dashboard_type] = nil
end end
@ -101,7 +103,7 @@ class Dashboard < ActiveRecord::Base
["#{table}.name"] ["#{table}.name"]
end end
def visible(user = User.current, options = {}) def visible(user = User.current, **options)
scope = left_outer_joins :project scope = left_outer_joins :project
scope = scope.where(projects: { id: nil }).or(scope.where(Project.allowed_to_condition(user, :view_project, options))) scope = scope.where(projects: { id: nil }).or(scope.where(Project.allowed_to_condition(user, :view_project, options)))
@ -152,7 +154,7 @@ class Dashboard < ActiveRecord::Base
super super
else else
h = (self[:options] || {}).dup h = (self[:options] || {}).dup
h.update(attr_name => value) h.update attr_name => value
self[:options] = h self[:options] = h
value value
end end
@ -236,7 +238,7 @@ class Dashboard < ActiveRecord::Base
return if content.groups.exclude?(group) || blocks.blank? return if content.groups.exclude?(group) || blocks.blank?
blocks = blocks.map(&:underscore) & layout.values.flatten blocks = blocks.map(&:underscore) & layout.values.flatten
blocks.each { |block| remove_block(block) } blocks.each { |block| remove_block block }
layout[group] = blocks layout[group] = blocks
end end
@ -261,7 +263,7 @@ class Dashboard < ActiveRecord::Base
end end
def editable?(usr = User.current) def editable?(usr = User.current)
@editable ||= editable_by?(usr) @editable ||= editable_by? usr
end end
def destroyable_by?(usr = User.current) def destroyable_by?(usr = User.current)
@ -274,7 +276,7 @@ class Dashboard < ActiveRecord::Base
end end
def destroyable? def destroyable?
@destroyable ||= destroyable_by?(User.current) @destroyable ||= destroyable_by? User.current
end end
def to_s def to_s
@ -285,7 +287,7 @@ class Dashboard < ActiveRecord::Base
def css_classes(user = User.current) def css_classes(user = User.current)
s = ['dashboard'] s = ['dashboard']
s << 'created-by-me' if author_id == user.id s << 'created-by-me' if author_id == user.id
s.join(' ') s.join ' '
end end
def allowed_target_projects(user = User.current) def allowed_target_projects(user = User.current)
@ -293,7 +295,7 @@ class Dashboard < ActiveRecord::Base
end end
# this is used to get unique cache for blocks # this is used to get unique cache for blocks
def async_params(block, options, settings = {}) def async_params(block, options, settings)
if block.blank? if block.blank?
msg = 'block is missing for dashboard_async' msg = 'block is missing for dashboard_async'
Rails.log.error msg Rails.log.error msg
@ -326,7 +328,7 @@ class Dashboard < ActiveRecord::Base
unique_params += options[:unique_params].reject(&:blank?) if options[:unique_params].present? unique_params += options[:unique_params].reject(&:blank?) if options[:unique_params].present?
# Rails.logger.debug "debug async_params for #{block}: unique_params=#{unique_params.inspect}" # Rails.logger.debug "debug async_params for #{block}: unique_params=#{unique_params.inspect}"
config[:unique_key] = Digest::SHA256.hexdigest(unique_params.join('_')) config[:unique_key] = Digest::SHA256.hexdigest unique_params.join('_')
end end
# Rails.logger.debug "debug async_params for #{block}: config=#{config.inspect}" # Rails.logger.debug "debug async_params for #{block}: config=#{config.inspect}"
@ -345,7 +347,7 @@ class Dashboard < ActiveRecord::Base
def clear_unused_block_settings def clear_unused_block_settings
blocks = layout.values.flatten blocks = layout.values.flatten
layout_settings.keep_if { |block, _settings| blocks.include?(block) } layout_settings.keep_if { |block, _settings| blocks.include? block }
end end
def remove_unused_role_relations def remove_unused_role_relations
@ -391,7 +393,7 @@ class Dashboard < ActiveRecord::Base
.where(dashboard_type: dashboard_type) .where(dashboard_type: dashboard_type)
.where.not(id: id) .where.not(id: id)
scope = scope.where(project: project) if dashboard_type == DashboardContentProject::TYPE_NAME scope = scope.where project: project if dashboard_type == DashboardContentProject::TYPE_NAME
scope.update_all system_default: false scope.update_all system_default: false
end end
@ -409,22 +411,22 @@ class Dashboard < ActiveRecord::Base
end end
def validate_visibility def validate_visibility
errors.add(:visibility, :must_be_for_everyone) if system_default? && visibility != VISIBILITY_PUBLIC errors.add :visibility, :must_be_for_everyone if system_default? && visibility != VISIBILITY_PUBLIC
end end
def validate_name def validate_name
return if name.blank? return if name.blank?
scope = self.class.visible.where(name: name) scope = self.class.visible.where name: name
if dashboard_type == DashboardContentProject::TYPE_NAME if dashboard_type == DashboardContentProject::TYPE_NAME
scope = scope.project_only scope = scope.project_only
scope = scope.where project_id: project_id scope = scope.where project_id: project_id
scope = scope.or(scope.where(project_id: nil)) if project_id.present? scope = scope.or scope.where(project_id: nil) if project_id.present?
else else
scope = scope.welcome_only scope = scope.welcome_only
end end
scope = scope.where.not(id: id) unless new_record? scope = scope.where.not id: id unless new_record?
errors.add(:name, :name_not_unique) if scope.count.positive? errors.add :name, :name_not_unique if scope.count.positive?
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class DashboardContent class DashboardContent
include Redmine::I18n include Redmine::I18n
@ -100,7 +102,7 @@ class DashboardContent
def find_block(block) def find_block(block)
block.to_s =~ /\A(.*?)(__\d+)?\z/ block.to_s =~ /\A(.*?)(__\d+)?\z/
name = Regexp.last_match(1) name = Regexp.last_match 1
available_blocks.key?(name) ? available_blocks[name].merge(name: name) : nil available_blocks.key?(name) ? available_blocks[name].merge(name: name) : nil
end end

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
class DashboardContentProject < DashboardContent class DashboardContentProject < DashboardContent
TYPE_NAME = 'ProjectDashboard'.freeze TYPE_NAME = 'ProjectDashboard'
def block_definitions def block_definitions
blocks = super blocks = super

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
class DashboardContentWelcome < DashboardContent class DashboardContentWelcome < DashboardContent
TYPE_NAME = 'WelcomeDashboard'.freeze TYPE_NAME = 'WelcomeDashboard'
def block_definitions def block_definitions
blocks = super blocks = super

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class DashboardRole < ActiveRecord::Base class DashboardRole < ActiveRecord::Base
include Redmine::SafeAttributes include Redmine::SafeAttributes

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Deface::Override.new virtual_path: 'admin/info', Deface::Override.new virtual_path: 'admin/info',
name: 'add-system_info', name: 'add-system_info',
insert_after: 'table.list', insert_after: 'table.list',

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
unless Redmine::Plugin.installed? 'redmine_servicedesk' unless Redmine::Plugin.installed? 'redmine_servicedesk'
if defined?(CONTACTS_VERSION_TYPE) && CONTACTS_VERSION_TYPE == 'PRO version' if defined?(CONTACTS_VERSION_TYPE) && CONTACTS_VERSION_TYPE == 'PRO version'
Deface::Override.new virtual_path: 'contacts/_form', Deface::Override.new virtual_path: 'contacts/_form',

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Deface::Override.new virtual_path: 'custom_fields/formats/_text', Deface::Override.new virtual_path: 'custom_fields/formats/_text',
name: 'custom_fields-formats-text', name: 'custom_fields-formats-text',
replace: 'erb[silent]:contains(\'if @custom_field.class.name == "IssueCustomField"\')', replace: 'erb[silent]:contains(\'if @custom_field.class.name == "IssueCustomField"\')',

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Deface::Override.new virtual_path: 'issues/_edit', Deface::Override.new virtual_path: 'issues/_edit',
name: 'edit-issue-permission', name: 'edit-issue-permission',
replace: 'erb[silent]:contains("User.current.allowed_to?(:log_time, @project)")', replace: 'erb[silent]:contains("User.current.allowed_to?(:log_time, @project)")',

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Deface::Override.new virtual_path: 'issues/_list', Deface::Override.new virtual_path: 'issues/_list',
name: 'list-issue-back-url', name: 'list-issue-back-url',
replace: 'erb[loud]:contains("hidden_field_tag \'back_url\'")', replace: 'erb[loud]:contains("hidden_field_tag \'back_url\'")',

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Deface::Override.new virtual_path: 'issues/_action_menu', Deface::Override.new virtual_path: 'issues/_action_menu',
name: 'show-issue-log-time', name: 'show-issue-log-time',
replace: 'erb[loud]:contains("User.current.allowed_to?(:log_time, @project)")', replace: 'erb[loud]:contains("User.current.allowed_to?(:log_time, @project)")',

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Deface::Override.new virtual_path: 'reports/_simple', Deface::Override.new virtual_path: 'reports/_simple',
name: 'report-simple-user-scope', name: 'report-simple-user-scope',
insert_before: 'erb[silent]:contains("rows.empty?")', insert_before: 'erb[silent]:contains("rows.empty?")',

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Deface::Override.new virtual_path: 'roles/_form', Deface::Override.new virtual_path: 'roles/_form',
name: 'roles-form-hide', name: 'roles-form-hide',
insert_before: 'p.manage_members_shown', insert_before: 'p.manage_members_shown',

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
unless Redmine::Plugin.installed? 'redmine_hrm' unless Redmine::Plugin.installed? 'redmine_hrm'
if Redmine::VERSION.to_s >= '4.2' if Redmine::VERSION.to_s >= '4.2'
Deface::Override.new virtual_path: 'users/show', Deface::Override.new virtual_path: 'users/show',

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Deface::Override.new virtual_path: 'wiki/edit', Deface::Override.new virtual_path: 'wiki/edit',
name: 'wiki-edit-bottom', name: 'wiki-edit-bottom',
insert_before: 'fieldset', insert_before: 'fieldset',

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Deface::Override.new virtual_path: 'wiki/show', Deface::Override.new virtual_path: 'wiki/show',
name: 'wiki-show-bottom', name: 'wiki-show-bottom',
insert_before: 'p.wiki-update-info', insert_before: 'p.wiki-update-info',

View File

@ -1,32 +1,32 @@
div id="#{export_format}-export-options" style="display: none" div id="#{export_format}-export-options" style="display: none"
h3.title = l(:label_export_options, export_format: export_format.upcase) h3.title = l :label_export_options, export_format: export_format.upcase
= form_tag(url, method: :get, id: "#{export_format}-export-form") do = form_tag url, method: :get, id: "#{export_format}-export-form" do
= query_as_hidden_field_tags @query = query_as_hidden_field_tags @query
- if defined?(selected_columns_only) && selected_columns_only - if defined?(selected_columns_only) && selected_columns_only
= hidden_field_tag 'c[]', '' = hidden_field_tag 'c[]', ''
= l(:description_selected_columns) = l :description_selected_columns
- else - else
p p
label label
= radio_button_tag 'c[]', '', true = radio_button_tag 'c[]', '', true
= l(:description_selected_columns) = l :description_selected_columns
br br
label label
= radio_button_tag 'c[]', 'all_inline' = radio_button_tag 'c[]', 'all_inline'
= l(:description_all_columns) = l :description_all_columns
hr hr
- if @query.available_filters.key?('description') - if @query.available_filters.key? 'description'
p p
label label
= check_box_tag 'c[]', 'description', @query.has_column?(:description) = check_box_tag 'c[]', 'description', @query.has_column?(:description)
= l(:field_description) = l :field_description
- if defined?(with_last_notes) && with_last_notes - if defined?(with_last_notes) && with_last_notes
label label
= check_box_tag 'c[]', 'last_notes', @query.has_column?(:last_notes) = check_box_tag 'c[]', 'last_notes', @query.has_column?(:last_notes)
= l(:label_last_notes) = l :label_last_notes
= export_csv_encoding_select_tag = export_csv_encoding_select_tag

View File

@ -1,18 +1,18 @@
fieldset.box fieldset.box
legend = l(:additionals_query_list_defaults) legend = l :additionals_query_list_defaults
- setting_name_columns = "#{query_type}_list_defaults" - setting_name_columns = "#{query_type}_list_defaults"
- query = query_class.new(@settings[setting_name_columns.to_sym]) - query = query_class.new @settings[setting_name_columns.to_sym]
.default-query-settings-label .default-query-settings-label
= render_query_columns_selection(query, name: "settings[#{setting_name_columns}][column_names]") = render_query_columns_selection query, name: "settings[#{setting_name_columns}][column_names]"
- columns = query_class.new.available_totalable_columns - columns = query_class.new.available_totalable_columns
- if columns.count.positive? - if columns.count.positive?
fieldset.box fieldset.box
legend = l(:additionals_query_list_default_totals) legend = l :additionals_query_list_default_totals
.default-query-settings-totals .default-query-settings-totals
- setting_name_totals = "#{query_type}_list_default_totals" - setting_name_totals = "#{query_type}_list_default_totals"
= hidden_field_tag("settings[#{setting_name_totals}][]", '') = hidden_field_tag "settings[#{setting_name_totals}][]", ''
- columns.each do |s| - columns.each do |s|
label.inline label.inline
- value = @settings[setting_name_totals.to_sym].present? ? @settings[setting_name_totals.to_sym].include?(s.name.to_s) : false - value = @settings[setting_name_totals.to_sym].present? ? @settings[setting_name_totals.to_sym].include?(s.name.to_s) : false

View File

@ -1,47 +1,47 @@
fieldset.settings fieldset.settings
legend = l(:label_content_plural) legend = l :label_content_plural
p p
= additionals_settings_textarea :account_login_bottom = additionals_settings_textarea :account_login_bottom
em.info em.info
= l(:account_login_info) = l :account_login_info
p p
= additionals_settings_textarea :global_sidebar = additionals_settings_textarea :global_sidebar
em.info em.info
= l(:global_sidebar_info) = l :global_sidebar_info
p p
= additionals_settings_textarea :global_footer = additionals_settings_textarea :global_footer
em.info em.info
= l(:global_footer_info) = l :global_footer_info
fieldset.settings fieldset.settings
legend = l(:label_settings) legend = l :label_settings
p p
= additionals_settings_checkbox :open_external_urls = additionals_settings_checkbox :open_external_urls
em.info em.info
= t(:open_external_urls_info) = t :open_external_urls_info
p p
= additionals_settings_checkbox :add_go_to_top = additionals_settings_checkbox :add_go_to_top
em.info em.info
= t(:add_go_to_top_info) = t :add_go_to_top_info
p p
= additionals_settings_checkbox :legacy_smiley_support = additionals_settings_checkbox :legacy_smiley_support
em.info em.info
= t(:legacy_smiley_support_info_html) = t :legacy_smiley_support_info_html
fieldset.settings fieldset.settings
legend = l(:label_disabled_modules) legend = l :label_disabled_modules
p p
= tag.label l(:label_disabled_modules) = tag.label l(:label_disabled_modules)
= hidden_field_tag('settings[disabled_modules][]', '') = hidden_field_tag 'settings[disabled_modules][]', ''
- Redmine::AccessControl.available_project_modules_all.sort.each do |m| - Redmine::AccessControl.available_project_modules_all.sort.each do |m|
label.block label.block
- value = @settings[:disabled_modules].present? ? @settings[:disabled_modules].include?(m.to_s) : false - value = @settings[:disabled_modules].present? ? @settings[:disabled_modules].include?(m.to_s) : false
= check_box_tag('settings[disabled_modules][]', m, value, id: nil) = check_box_tag 'settings[disabled_modules][]', m, value, id: nil
= l_or_humanize(m, prefix: 'project_module_') = l_or_humanize m, prefix: 'project_module_'
br br
em.info em.info
= l(:disabled_modules_info) = l :disabled_modules_info

View File

@ -38,7 +38,7 @@ p
options_for_select(rule_status.collect { |column| [column.name, column.id] }, options_for_select(rule_status.collect { |column| [column.name, column.id] },
@settings[:issue_status_y]), @settings[:issue_status_y]),
multiple: false, style: 'width:150px; vertical-align: top' multiple: false, style: 'width:150px; vertical-align: top'
em.info = t(:rule_issue_status_change_info) em.info = t :rule_issue_status_change_info
br br
br br
@ -51,7 +51,7 @@ p
options_for_select(rule_status.collect { |column| [column.name, column.id] }, options_for_select(rule_status.collect { |column| [column.name, column.id] },
@settings[:issue_assign_to_x]), @settings[:issue_assign_to_x]),
multiple: true, size: 6, style: 'width:150px' multiple: true, size: 6, style: 'width:150px'
em.info = t(:rule_issue_current_user_status_info_html) em.info = t :rule_issue_current_user_status_info_html
br br
br br
@ -77,7 +77,7 @@ br
p p
= additionals_settings_checkbox :issue_timelog_required = additionals_settings_checkbox :issue_timelog_required
span[style="vertical-align: top; margin-left: 15px;"] span[style="vertical-align: top; margin-left: 15px;"]
= l(:label_tracker_plural) = l :label_tracker_plural
| : | :
= select_tag 'settings[issue_timelog_required_tracker]', = select_tag 'settings[issue_timelog_required_tracker]',
options_for_select(Tracker.all.sorted.collect { |column| [column.name, column.id] }, options_for_select(Tracker.all.sorted.collect { |column| [column.name, column.id] },

View File

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

View File

@ -20,14 +20,14 @@ h3 = l :label_custom_menu_items
p p
label = h l(:label_permissions) label = h l(:label_permissions)
- permission_field = "custom_menu#{i}_roles" - permission_field = "custom_menu#{i}_roles"
- menu_roles = Struct.new(:id, :name) - menu_roles = Struct.new :id, :name
= select_tag("settings[#{permission_field}]", = select_tag("settings[#{permission_field}]",
options_from_collection_for_select(Role.sorted.collect { |m| menu_roles.new(m.id, m.name) }, options_from_collection_for_select(Role.sorted.collect { |m| menu_roles.new m.id, m.name },
:id, :id,
:name, :name,
@settings[permission_field]), @settings[permission_field]),
multiple: true, style: 'height: 100px;') multiple: true, style: 'height: 100px;')
em.info = l(:menu_roles_info) em.info = l :menu_roles_info
br br

View File

@ -1,5 +1,5 @@
p p
= additionals_settings_textfield :google_maps_api_key, size: 60 = additionals_settings_textfield :google_maps_api_key, size: 60
em.info = t(:google_maps_embed_api_html) em.info = t :google_maps_embed_api_html
= call_hook :additionals_settings_web_apis, settings: @settings = call_hook :additionals_settings_web_apis, settings: @settings

View File

@ -1,23 +1,23 @@
em.info = t(:top_wiki_help) em.info = t :top_wiki_help
br br
fieldset.settings fieldset.settings
legend = l(:label_content_plural) legend = l :label_content_plural
p p
= additionals_settings_textarea :global_wiki_sidebar = additionals_settings_textarea :global_wiki_sidebar
em.info em.info
= l(:global_wiki_sidebar_info) = l :global_wiki_sidebar_info
fieldset.settings fieldset.settings
legend = l(:label_pdf_wiki_settings) legend = l :label_pdf_wiki_settings
p p
= additionals_settings_checkbox :wiki_pdf_remove_title = additionals_settings_checkbox :wiki_pdf_remove_title
em.info em.info
= l(:wiki_pdf_remove_title_info) = l :wiki_pdf_remove_title_info
p p
= additionals_settings_checkbox :wiki_pdf_remove_attachments = additionals_settings_checkbox :wiki_pdf_remove_attachments
em.info em.info
= l(:wiki_pdf_remove_attachments_info) = l :wiki_pdf_remove_attachments_info

View File

@ -1,6 +1,6 @@
= call_hook :view_dashboard_top, dashboard: dashboard, project: @project = call_hook :view_dashboard_top, dashboard: dashboard, project: @project
#my-page.splitcontent class="#{dashboard_css_classes(dashboard)}" #my-page.splitcontent class="#{dashboard_css_classes dashboard}"
- dashboard.available_groups.each do |group| - dashboard.available_groups.each do |group|
.block-receiver id="list-#{group}" class="splitcontent#{group}" .block-receiver id="list-#{group}" class="splitcontent#{group}"
= render_dashboard_blocks dashboard.layout[group], dashboard = render_dashboard_blocks dashboard.layout[group], dashboard

View File

@ -19,7 +19,7 @@
{}, {},
disabled: !@dashboard.project_id_can_change? disabled: !@dashboard.project_id_can_change?
em.info em.info
= l(:info_dashboard_project_select) = l :info_dashboard_project_select
- else - else
= hidden_field_tag 'dashboard[project_id]', @project&.id = hidden_field_tag 'dashboard[project_id]', @project&.id
@ -27,19 +27,19 @@
User.current.allowed_to?(:set_system_dashboards, @project, global: true) User.current.allowed_to?(:set_system_dashboards, @project, global: true)
p p
label = l(:field_visible) label = l :field_visible
label.block label.block
= radio_button 'dashboard', 'visibility', Dashboard::VISIBILITY_PRIVATE = radio_button 'dashboard', 'visibility', Dashboard::VISIBILITY_PRIVATE
' '
= l(:label_visibility_private) = l :label_visibility_private
label.block label.block
= radio_button 'dashboard', 'visibility', Dashboard::VISIBILITY_PUBLIC = radio_button 'dashboard', 'visibility', Dashboard::VISIBILITY_PUBLIC
' '
= l(:label_visibility_public) = l :label_visibility_public
label.block label.block
= radio_button 'dashboard', 'visibility', Dashboard::VISIBILITY_ROLES = radio_button 'dashboard', 'visibility', Dashboard::VISIBILITY_ROLES
' '
= l(:label_visibility_roles) = l :label_visibility_roles
' : ' :
- Role.givable.sorted.each do |role| - Role.givable.sorted.each do |role|
label.block.role-visibility label.block.role-visibility

View File

@ -11,7 +11,7 @@ h3 = block_definition[:label]
= l :field_homepage = l :field_homepage
' : ' :
= link_to_if uri_with_safe_scheme?(@project.homepage), @project.homepage, @project.homepage, class: 'external' = link_to_if uri_with_safe_scheme?(@project.homepage), @project.homepage, @project.homepage, class: 'external'
- render_custom_field_values(@project) do |custom_field, formatted| - render_custom_field_values @project do |custom_field, formatted|
li class="#{custom_field.css_classes}" li class="#{custom_field.css_classes}"
span.label span.label
= custom_field.name = custom_field.name

View File

@ -1,6 +1,6 @@
- dashboard_async_cache dashboard, block, async, settings do - dashboard_async_cache dashboard, block, async, settings do
- query = klass.visible.find_by(id: settings[:query_id]) - query = klass.visible.find_by id: settings[:query_id]
- if query - if query
ruby: ruby:
query.project = @project if query_block[:with_project] query.project = @project if query_block[:with_project]

View File

@ -1,4 +1,4 @@
h2 = l(:button_dashboard_edit) h2 = l :button_dashboard_edit
= labelled_form_for :dashboard, = labelled_form_for :dashboard,
@dashboard, @dashboard,
html: { multipart: true, id: 'dashboard-form' } do |f| html: { multipart: true, id: 'dashboard-form' } do |f|

View File

@ -1,4 +1,4 @@
h2 = l(:label_new_additional_dashboard) h2 = l :label_new_additional_dashboard
= labelled_form_for :dashboard, = labelled_form_for :dashboard,
@dashboard, @dashboard,
url: { action: 'create', project_id: @project }, url: { action: 'create', project_id: @project },

View File

@ -1,5 +1,5 @@
- if show_issue_change_author?(issue) && issue.safe_attribute?('author_id') - if show_issue_change_author?(issue) && issue.safe_attribute?('author_id')
- author_options = author_options_for_select(issue.project, issue) - author_options = author_options_for_select issue.project, issue
- if author_options.present? - if author_options.present?
p#change_author p#change_author
= form.label_for_field :author_id = form.label_for_field :author_id

View File

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

View File

@ -16,7 +16,7 @@
= actions_dropdown do = actions_dropdown do
- if User.current.allowed_to? :add_subprojects, @project - if User.current.allowed_to? :add_subprojects, @project
= link_to l(:label_subproject_new), new_project_path(parent_id: @project), class: 'icon icon-add' = link_to l(:label_subproject_new), new_project_path(parent_id: @project), class: 'icon icon-add'
- if User.current.allowed_to?(:close_project, @project) - if User.current.allowed_to? :close_project, @project
- if @project.active? - if @project.active?
= link_to l(:button_close), = link_to l(:button_close),
close_project_path(@project), close_project_path(@project),
@ -46,14 +46,13 @@
class: 'icon icon-add new-additionals-dashboard' class: 'icon icon-add new-additionals-dashboard'
- if @dashboard&.destroyable? - if @dashboard&.destroyable?
= delete_dashboard_link project_dashboard_path(@project, @dashboard), = delete_dashboard_link project_dashboard_path(@project, @dashboard)
class: 'icon icon-del'
= sidebar_action_toggle @dashboard_sidebar, @dashboard, @project = sidebar_action_toggle @dashboard_sidebar, @dashboard, @project
= render_dashboard_actionlist @dashboard, @project unless @dashboard_sidebar = render_dashboard_actionlist @dashboard, @project unless @dashboard_sidebar
= call_hook :view_project_actions_dropdown, project: @project = call_hook :view_project_actions_dropdown, project: @project
- if User.current.allowed_to?(:edit_project, @project) - if User.current.allowed_to? :edit_project, @project
= link_to_if_authorized l(:label_settings), = link_to_if_authorized l(:label_settings),
{ controller: 'projects', action: 'settings', id: @project }, { controller: 'projects', action: 'settings', id: @project },
class: 'icon icon-settings' class: 'icon icon-settings'
@ -63,7 +62,7 @@ h2 = project_overview_name @project, @dashboard
- unless @project.active? - unless @project.active?
p.warning p.warning
span.icon.icon-lock span.icon.icon-lock
= l(:text_project_closed) = l :text_project_closed
= render partial: 'common/dashboard', locals: { dashboard: @dashboard } = render partial: 'common/dashboard', locals: { dashboard: @dashboard }

View File

@ -9,5 +9,5 @@ tr.group.open
span.badge.badge-count.count = group_count span.badge.badge-count.count = group_count
' '
span.totals = group_totals span.totals = group_totals
= link_to_function("#{l :button_collapse_all}/#{l :button_expand_all}", = link_to_function "#{l :button_collapse_all}/#{l :button_expand_all}",
'toggleAllRowGroups(this)', class: 'toggle-all') 'toggleAllRowGroups(this)', class: 'toggle-all'

View File

@ -1,7 +1,7 @@
p p
= f.check_box :hide, disabled: @role.users_visibility != 'members_of_visible_projects' = f.check_box :hide, disabled: @role.users_visibility != 'members_of_visible_projects'
em.info em.info
= t(:info_hidden_roles_html) = t :info_hidden_roles_html
javascript: javascript:
$(function() { $(function() {

View File

@ -18,8 +18,7 @@
new_dashboard_path, new_dashboard_path,
class: 'icon icon-add new-additionals-dashboard' class: 'icon icon-add new-additionals-dashboard'
- if @dashboard&.destroyable? - if @dashboard&.destroyable?
= delete_dashboard_link dashboard_path(@dashboard), = delete_dashboard_link dashboard_path(@dashboard)
class: 'icon icon-del'
= sidebar_action_toggle @dashboard_sidebar, @dashboard = sidebar_action_toggle @dashboard_sidebar, @dashboard
= render_dashboard_actionlist @dashboard unless @dashboard_sidebar = render_dashboard_actionlist @dashboard unless @dashboard_sidebar

View File

@ -3,7 +3,7 @@
h3 = list_title h3 = list_title
table.list.projects table.list.projects
- project_tree(@projects, init_level: false) do |project, level| - project_tree @projects, init_level: false do |project, level|
tr id="project-#{project.id}" class="#{project_list_css_classes project, level}" tr id="project-#{project.id}" class="#{project_list_css_classes project, level}"
td.name td.name
span[style='font-weight: bold;'] span[style='font-weight: bold;']
@ -13,7 +13,7 @@
= link_to_project project = link_to_project project
- if project.homepage? - if project.homepage?
' : ' :
= link_to(project.homepage, project.homepage, @html_options) = link_to project.homepage, project.homepage, @html_options
- if with_create_issue && User.current.allowed_to?(:add_issues, project) - if with_create_issue && User.current.allowed_to?(:add_issues, project)
= link_to '', = link_to '',
new_project_issue_path(project_id: project), new_project_issue_path(project_id: project),

View File

@ -4,7 +4,7 @@
- users.each do |user| - users.each do |user|
.user.box class="#{cycle 'odd', 'even'}" .user.box class="#{cycle 'odd', 'even'}"
div[style="float: left; display: block; margin-right: 5px;"] div[style="float: left; display: block; margin-right: 5px;"]
= avatar(user, size: 50) = avatar user, size: 50
.user.line[style="font-weight: bold;"] .user.line[style="font-weight: bold;"]
= link_to_user user = link_to_user user

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Rails.application.routes.draw do Rails.application.routes.draw do
resources :issues, only: [] do resources :issues, only: [] do
resource 'assign_to_me', only: %i[update], controller: 'additionals_assign_to_me' resource 'assign_to_me', only: %i[update], controller: 'additionals_assign_to_me'

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# see https://github.com/nagix/chartjs-plugin-colorschemes # see https://github.com/nagix/chartjs-plugin-colorschemes
# see https://nagix.github.io/chartjs-plugin-colorschemes/colorchart.html # see https://nagix.github.io/chartjs-plugin-colorschemes/colorchart.html
# #
@ -16,8 +18,8 @@ colorschemes_files = [{ name: 'brewer', src: "#{working_path}/src/colorschemes/c
FileUtils.rm_rf working_path FileUtils.rm_rf working_path
system "git clone https://github.com/nagix/chartjs-plugin-colorschemes.git #{working_path}" system "git clone https://github.com/nagix/chartjs-plugin-colorschemes.git #{working_path}"
File.open(target_file, 'w') do |file| File.open target_file, 'w' do |file|
file.write("---\n") file.write "---\n"
colorschemes_files.each do |color_scheme_file| colorschemes_files.each do |color_scheme_file|
file.write "#{color_scheme_file[:name].capitalize}:\n" file.write "#{color_scheme_file[:name].capitalize}:\n"
File.readlines(color_scheme_file[:src]).each do |line| File.readlines(color_scheme_file[:src]).each do |line|

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AddAutowatchInvolvedIssueToUser < ActiveRecord::Migration[4.2] class AddAutowatchInvolvedIssueToUser < ActiveRecord::Migration[4.2]
def change def change
add_column :user_preferences, :autowatch_involved_issue, :boolean, default: true, null: false add_column :user_preferences, :autowatch_involved_issue, :boolean, default: true, null: false

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AddHideToRoles < ActiveRecord::Migration[4.2] class AddHideToRoles < ActiveRecord::Migration[4.2]
def change def change
add_column :roles, :hide, :boolean, default: false, null: false add_column :roles, :hide, :boolean, default: false, null: false

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class CreateDashboards < ActiveRecord::Migration[5.2] class CreateDashboards < ActiveRecord::Migration[5.2]
def change def change
create_table :dashboards do |t| create_table :dashboards do |t|

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class CreateDashboardRoles < ActiveRecord::Migration[5.2] class CreateDashboardRoles < ActiveRecord::Migration[5.2]
def change def change
create_table :dashboard_roles do |t| create_table :dashboard_roles do |t|

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true
class CreateDashboardDefaults < ActiveRecord::Migration[5.2] class CreateDashboardDefaults < ActiveRecord::Migration[5.2]
def up def up
User.current = User.find_by(id: ENV['DEFAULT_USER_ID'].presence || User.admin.active.first.id) User.current = User.find_by(id: ENV['DEFAULT_USER_ID'].presence || User.admin.active.first.id)
unless Dashboard.exists?(dashboard_type: DashboardContentWelcome::TYPE_NAME) unless Dashboard.exists? dashboard_type: DashboardContentWelcome::TYPE_NAME
Dashboard.create! name: 'Welcome dashboard', Dashboard.create! name: 'Welcome dashboard',
dashboard_type: DashboardContentWelcome::TYPE_NAME, dashboard_type: DashboardContentWelcome::TYPE_NAME,
system_default: true, system_default: true,
@ -10,7 +12,7 @@ class CreateDashboardDefaults < ActiveRecord::Migration[5.2]
visibility: 2 visibility: 2
end end
return if Dashboard.exists?(dashboard_type: DashboardContentProject::TYPE_NAME) return if Dashboard.exists? dashboard_type: DashboardContentProject::TYPE_NAME
Dashboard.create! name: 'Project dashboard', Dashboard.create! name: 'Project dashboard',
dashboard_type: DashboardContentProject::TYPE_NAME, dashboard_type: DashboardContentProject::TYPE_NAME,

View File

@ -24,13 +24,13 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = u'additionals' project = u'additionals'
copyright = u'2013-2020, AlphaNodes GmbH' copyright = u'2013-2021, AlphaNodes GmbH'
author = u'Alexander Meindl' author = u'Alexander Meindl'
# The short X.Y version. # The short X.Y version.
version = u'3.0.0' version = u'3.0.3'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = u'3.0.0' release = u'3.0.3'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Redmine::Plugin.register :additionals do Redmine::Plugin.register :additionals do
name 'Additionals' name 'Additionals'
author 'AlphaNodes GmbH' author 'AlphaNodes GmbH'
@ -14,7 +16,7 @@ Redmine::Plugin.register :additionals do
default_settings["custom_menu#{i}_title"] = '' default_settings["custom_menu#{i}_title"] = ''
end end
settings(default: default_settings, partial: 'additionals/settings/additionals') settings default: default_settings, partial: 'additionals/settings/additionals'
permission :show_hidden_roles_in_memberbox, {} permission :show_hidden_roles_in_memberbox, {}
permission :set_system_dashboards, permission :set_system_dashboards,
@ -56,7 +58,7 @@ end
Rails.application.paths['app/overrides'] ||= [] Rails.application.paths['app/overrides'] ||= []
Dir.glob(Rails.root.join('plugins/*/app/overrides')).each do |dir| Dir.glob(Rails.root.join('plugins/*/app/overrides')).each do |dir|
Rails.application.paths['app/overrides'] << dir unless Rails.application.paths['app/overrides'].include?(dir) Rails.application.paths['app/overrides'] << dir unless Rails.application.paths['app/overrides'].include? dir
end end
Rails.configuration.to_prepare do Rails.configuration.to_prepare do

View File

@ -1,11 +1,13 @@
# frozen_string_literal: true
require 'additionals/version' require 'additionals/version'
module Additionals module Additionals
MAX_CUSTOM_MENU_ITEMS = 5 MAX_CUSTOM_MENU_ITEMS = 5
SELECT2_INIT_ENTRIES = 20 SELECT2_INIT_ENTRIES = 20
DEFAULT_MODAL_WIDTH = '350px'.freeze DEFAULT_MODAL_WIDTH = '350px'
GOTO_LIST = " \xc2\xbb".freeze GOTO_LIST = " \xc2\xbb"
LIST_SEPARATOR = "#{GOTO_LIST} ".freeze LIST_SEPARATOR = "#{GOTO_LIST} "
class << self class << self
def setup def setup
@ -27,6 +29,7 @@ module Additionals
ReportsController ReportsController
Principal Principal
QueryFilter QueryFilter
QueriesHelper
Role Role
User User
UserPreference] UserPreference]
@ -95,7 +98,8 @@ module Additionals
def debug(message) def debug(message)
return if Rails.env.production? return if Rails.env.production?
Rails.logger.debug "#{Time.current.strftime('%H:%M:%S')} DEBUG [#{caller_locations(1..1).first.label}]: #{message}" msg = message.is_a?(String) ? message : message.inspect
Rails.logger.debug "#{Time.current.strftime '%H:%M:%S'} DEBUG [#{caller_locations(1..1).first.label}]: #{msg}"
end end
def class_prefix(klass) def class_prefix(klass)
@ -113,19 +117,19 @@ module Additionals
def incompatible_plugins(plugins = [], title = 'additionals') def incompatible_plugins(plugins = [], title = 'additionals')
plugins.each do |plugin| plugins.each do |plugin|
raise "\n\033[31m#{title} plugin cannot be used with #{plugin} plugin.\033[0m" if Redmine::Plugin.installed?(plugin) raise "\n\033[31m#{title} plugin cannot be used with #{plugin} plugin.\033[0m" if Redmine::Plugin.installed? plugin
end end
end end
def patch(patches = [], plugin_id = 'additionals') def patch(patches = [], plugin_id = 'additionals')
patches.each do |name| patches.each do |name|
patch_dir = Rails.root.join("plugins/#{plugin_id}/lib/#{plugin_id}/patches") patch_dir = Rails.root.join "plugins/#{plugin_id}/lib/#{plugin_id}/patches"
require "#{patch_dir}/#{name.underscore}_patch" require "#{patch_dir}/#{name.underscore}_patch"
target = name.constantize target = name.constantize
patch = "#{plugin_id.camelize}::Patches::#{name}Patch".constantize patch = "#{plugin_id.camelize}::Patches::#{name}Patch".constantize
target.include(patch) unless target.included_modules.include?(patch) target.include patch unless target.included_modules.include? patch
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
module EntityMethods module EntityMethods
def assignable_users(prj = nil) def assignable_users(prj = nil)

View File

@ -1,4 +1,5 @@
# Formater # frozen_string_literal: true
module Additionals module Additionals
module Formatter module Formatter
SMILEYS = { 'smiley' => ':-?\)', # :) SMILEYS = { 'smiley' => ':-?\)', # :)
@ -27,16 +28,16 @@ module Additionals
def render_inline_smileys(text) def render_inline_smileys(text)
return text if text.blank? return text if text.blank?
inline_smileys(text) inline_smileys text
text text
end end
def inline_smileys(text) def inline_smileys(text)
SMILEYS.each do |name, regexp| SMILEYS.each do |name, regexp|
text.gsub!(/(\s|^|>|\))(!)?(#{regexp})(?=\W|$|<)/m) do text.gsub!(/(\s|^|>|\))(!)?(#{regexp})(?=\W|$|<)/m) do
leading = Regexp.last_match(1) leading = Regexp.last_match 1
esc = Regexp.last_match(2) esc = Regexp.last_match 2
smiley = Regexp.last_match(3) smiley = Regexp.last_match 3
if esc.nil? if esc.nil?
leading + tag.span(class: "additionals smiley smiley-#{name}", leading + tag.span(class: "additionals smiley smiley-#{name}",
title: smiley) title: smiley)
@ -49,8 +50,8 @@ module Additionals
def inline_emojify(text) def inline_emojify(text)
text.gsub!(/:([\w+-]+):/) do |match| text.gsub!(/:([\w+-]+):/) do |match|
emoji_code = Regexp.last_match(1) emoji_code = Regexp.last_match 1
emoji = Emoji.find_by_alias(emoji_code) # rubocop:disable Rails/DynamicFindBy emoji = Emoji.find_by_alias emoji_code # rubocop:disable Rails/DynamicFindBy
if emoji.present? if emoji.present?
tag.img src: inline_emojify_image_path(emoji.image_filename), tag.img src: inline_emojify_image_path(emoji.image_filename),
title: ":#{emoji_code}:", title: ":#{emoji_code}:",

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
class Gemify class Gemify
class << self class << self
@ -10,11 +12,11 @@ module Additionals
return unless File.directory? source return unless File.directory? source
source_files = Dir["#{source}/**/*"] source_files = Dir["#{source}/**/*"]
source_dirs = source_files.select { |d| File.directory?(d) } source_dirs = source_files.select { |d| File.directory? d }
source_files -= source_dirs source_files -= source_dirs
unless source_files.empty? unless source_files.empty?
base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(source, '')) base_target_dir = File.join destination, File.dirname(source_files.first).gsub(source, '')
begin begin
FileUtils.mkdir_p base_target_dir FileUtils.mkdir_p base_target_dir
rescue StandardError => e rescue StandardError => e
@ -23,9 +25,9 @@ module Additionals
end end
source_dirs.each do |dir| source_dirs.each do |dir|
target_dir = File.join(destination, dir.gsub(source, '')) target_dir = File.join destination, dir.gsub(source, '')
begin begin
FileUtils.mkdir_p(target_dir) FileUtils.mkdir_p target_dir
rescue StandardError => e rescue StandardError => e
raise "Could not create directory #{target_dir}: " + e.message raise "Could not create directory #{target_dir}: " + e.message
end end
@ -33,7 +35,7 @@ module Additionals
source_files.each do |file| source_files.each do |file|
target = File.join destination, file.gsub(source, '') target = File.join destination, file.gsub(source, '')
FileUtils.cp(file, target) unless File.exist?(target) && FileUtils.identical?(file, target) FileUtils.cp file, target unless File.exist?(target) && FileUtils.identical?(file, target)
rescue StandardError => e rescue StandardError => e
raise "Could not copy #{file} to #{target}: " + e.message raise "Could not copy #{file} to #{target}: " + e.message
end end
@ -42,12 +44,12 @@ module Additionals
# Create text file to Redmine's plugins directory. # Create text file to Redmine's plugins directory.
# The purpose is telling plugins directory to users. # The purpose is telling plugins directory to users.
def create_plugin_hint(plugin_id = 'additionals') def create_plugin_hint(plugin_id = 'additionals')
plugins_dir = File.join(Bundler.root, 'plugins') plugins_dir = File.join Bundler.root, 'plugins'
path = File.join plugins_dir, plugin_id path = File.join plugins_dir, plugin_id
return if File.exist? path return if File.exist? path
File.open(path, 'w') do |f| File.open path, 'w' do |f|
f.write(<<PLUGIN_HINT) f.write <<PLUGIN_HINT
This plugin was installed as gem wrote to Gemfile.local instead of putting Redmine's plugin directory. This plugin was installed as gem wrote to Gemfile.local instead of putting Redmine's plugin directory.
See #{plugin_id} gem installed directory. See #{plugin_id} gem installed directory.
PLUGIN_HINT PLUGIN_HINT

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
module Helpers module Helpers
def live_search_title_info(entity) def live_search_title_info(entity)
@ -6,7 +8,7 @@ module Additionals
l :label_live_search_hints, value: all_fields l :label_live_search_hints, value: all_fields
end end
def link_to_external(name, link, options = {}) def link_to_external(name, link, **options)
options[:class] ||= 'external' options[:class] ||= 'external'
options[:class] = "#{options[:class]} external" if options[:class].exclude? 'external' options[:class] = "#{options[:class]} external" if options[:class].exclude? 'external'
@ -16,17 +18,17 @@ module Additionals
link_to name, link, options link_to name, link, options
end end
def additionals_list_title(options) def additionals_list_title(name: nil, issue: nil, user: nil, query: nil)
title = [] title = []
if options[:issue] if issue
title << link_to(h("#{options[:issue].subject} ##{options[:issue].id}"), title << link_to(h("#{issue.subject} ##{issue.id}"),
issue_path(options[:issue]), issue_path(issue),
class: options[:issue].css_classes) class: issue.css_classes)
elsif options[:user] elsif user
title << safe_join([avatar(options[:user], size: 50), options[:user].name], ' ') title << safe_join([avatar(user, size: 50), user.name], ' ')
end end
title << options[:name] if options[:name] title << name if name
title << h(options[:query].name) if options[:query] && !options[:query].new_record? title << h(query.name) if query && !query.new_record?
safe_join title, Additionals::LIST_SEPARATOR safe_join title, Additionals::LIST_SEPARATOR
end end
@ -53,7 +55,7 @@ module Additionals
def render_issue_macro_link(issue, text, comment_id = nil) def render_issue_macro_link(issue, text, comment_id = nil)
only_path = controller_path.split('_').last != 'mailer' only_path = controller_path.split('_').last != 'mailer'
content = link_to(text, issue_url(issue, only_path: only_path), class: issue.css_classes) content = link_to text, issue_url(issue, only_path: only_path), class: issue.css_classes
if comment_id.nil? if comment_id.nil?
content content
else else
@ -76,7 +78,7 @@ module Additionals
comment = 'N/A' comment = 'N/A'
comment_link = comment_id comment_link = comment_id
else else
comment_link = link_to(comment_id, issue_url(issue, only_path: only_path, anchor: "note-#{comment_id}")) comment_link = link_to comment_id, issue_url(issue, only_path: only_path, anchor: "note-#{comment_id}")
end end
tag.div class: 'issue-macro box' do tag.div class: 'issue-macro box' do
@ -105,14 +107,14 @@ module Additionals
# if more than one projects available, we do not use project url for a new issue # if more than one projects available, we do not use project url for a new issue
if project_count > 1 if project_count > 1
if permission == :edit_issues if permission == :edit_issues
new_issue_path('issue[assigned_to_id]' => user.id, 'issue[project_id]' => project_id) new_issue_path 'issue[assigned_to_id]' => user.id, 'issue[project_id]' => project_id
else else
new_issue_path('issue[project_id]' => project_id) new_issue_path 'issue[project_id]' => project_id
end end
elsif permission == :edit_issues elsif permission == :edit_issues
new_project_issue_path(project_id, 'issue[assigned_to_id]' => user.id) new_project_issue_path project_id, 'issue[assigned_to_id]' => user.id
else else
new_project_issue_path(project_id) new_project_issue_path project_id
end end
end end
@ -125,10 +127,10 @@ module Additionals
return rc return rc
end end
uri = URI.parse(url) uri = URI.parse url
# support issue_id plugin # support issue_id plugin
# see https://www.redmine.org/plugins/issue_id # see https://www.redmine.org/plugins/issue_id
issue_id_parts = url.split('-') issue_id_parts = url.split '-'
if uri.scheme.nil? && uri.path[0] != '/' && issue_id_parts.count == 2 if uri.scheme.nil? && uri.path[0] != '/' && issue_id_parts.count == 2
rc[:issue_id] = url rc[:issue_id] = url
else else
@ -153,10 +155,10 @@ module Additionals
safe_join s safe_join s
end end
def autocomplete_select_entries(name, type, option_tags, options = {}) def autocomplete_select_entries(name, type, option_tags, **options)
unless option_tags.is_a?(String) || option_tags.blank? unless option_tags.is_a?(String) || option_tags.blank?
# if option_tags is not an array, it should be an object # if option_tags is not an array, it should be an object
option_tags = options_for_select([[option_tags.try(:name), option_tags.try(:id)]], option_tags.try(:id)) option_tags = options_for_select [[option_tags.try(:name), option_tags.try(:id)]], option_tags.try(:id)
end end
options[:project] = @project if @project && options[:project].blank? options[:project] = @project if @project && options[:project].blank?
@ -168,7 +170,7 @@ module Additionals
multiple: options[:multiple], multiple: options[:multiple],
disabled: options[:disabled]) disabled: options[:disabled])
s << render(layout: false, s << render(layout: false,
partial: 'additionals/select2_ajax_call.js', partial: 'additionals/select2_ajax_call',
formats: [:js], formats: [:js],
locals: { field_id: sanitize_to_id(name), locals: { field_id: sanitize_to_id(name),
ajax_url: send("#{type}_path", project_id: options[:project], user_id: options[:user_id]), ajax_url: send("#{type}_path", project_id: options[:project], user_id: options[:user_id]),
@ -186,12 +188,12 @@ module Additionals
classes.join ' ' classes.join ' '
end end
def addtionals_textarea_cols(text, options = {}) def addtionals_textarea_cols(text, min: 8, max: 20)
[[(options[:min].presence || 8), text.to_s.length / 50].max, (options[:max].presence || 20)].min [[min, text.to_s.length / 50].max, max].min
end end
def title_with_fontawesome(title, symbole, wrapper = 'span') def title_with_fontawesome(title, symbole, wrapper = 'span')
tag.send(wrapper) do tag.send wrapper do
concat tag.i class: "#{symbole} for-fa-title", 'aria-hidden': 'true' concat tag.i class: "#{symbole} for-fa-title", 'aria-hidden': 'true'
concat title concat title
end end
@ -202,7 +204,7 @@ module Additionals
def additionals_already_loaded(scope, js_name) def additionals_already_loaded(scope, js_name)
locked = "#{js_name}.#{scope}" locked = "#{js_name}.#{scope}"
@alreaded_loaded = [] if @alreaded_loaded.nil? @alreaded_loaded = [] if @alreaded_loaded.nil?
return true if @alreaded_loaded.include?(locked) return true if @alreaded_loaded.include? locked
@alreaded_loaded << locked @alreaded_loaded << locked
false false
@ -276,21 +278,19 @@ module Additionals
additionals_include_js 'd3plus-network.full.min' additionals_include_js 'd3plus-network.full.min'
end end
def user_with_avatar(user, options = {}) def user_with_avatar(user, no_link: false, css_class: 'additionals-avatar', size: 14)
return if user.nil? return if user.nil?
if user.type == 'Group' if user.type == 'Group'
if options[:no_link] || !Redmine::Plugin.installed?('redmine_hrm') if no_link || !Redmine::Plugin.installed?('redmine_hrm')
user.name user.name
else else
link_to_hrm_group user link_to_hrm_group user
end end
else else
options[:size] = 14 if options[:size].nil?
options[:class] = 'additionals-avatar' if options[:class].nil?
s = [] s = []
s << avatar(user, options) s << avatar(user, { size: size, class: css_class })
s << if options[:no_link] s << if no_link
user.name user.name
else else
link_to_user user link_to_user user
@ -305,10 +305,10 @@ module Additionals
l(:label_app_menu) => 'app' }, active) l(:label_app_menu) => 'app' }, active)
end end
def human_float_number(value, options = {}) def human_float_number(value, precision: 2, separator: '.')
ActionController::Base.helpers.number_with_precision(value, ActionController::Base.helpers.number_with_precision(value,
precision: options[:precision].presence || 2, precision: precision,
separator: options[:separator].presence || '.', separator: separator,
strip_insignificant_zeros: true) strip_insignificant_zeros: true)
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
class AdditionalsHookListener < Redmine::Hook::ViewListener class AdditionalsHookListener < Redmine::Hook::ViewListener
include IssuesHelper include IssuesHelper

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
module Patches module Patches
module AccessControlPatch module AccessControlPatch

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
module Patches module Patches
module ApplicationControllerPatch module ApplicationControllerPatch

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
module Patches module Patches
module AutoCompletesControllerPatch module AutoCompletesControllerPatch

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
module Patches module Patches
module FormatterMarkdownPatch module FormatterMarkdownPatch
@ -8,7 +10,7 @@ module Additionals
# Add a postprocess hook to redcarpet's html formatter # Add a postprocess hook to redcarpet's html formatter
def postprocess(text) def postprocess(text)
if Additionals.setting?(:legacy_smiley_support) if Additionals.setting? :legacy_smiley_support
render_inline_smileys(inline_emojify(text)) render_inline_smileys(inline_emojify(text))
else else
text text

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
module Patches module Patches
module FormatterTextilePatch module FormatterTextilePatch

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
module Patches module Patches
module FormattingHelperPatch module FormattingHelperPatch

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
module Patches module Patches
module IssuePatch module IssuePatch
@ -23,27 +25,22 @@ module Additionals
end end
class_methods do class_methods do
def join_issue_status(options = {}) def join_issue_status(is_closed: nil)
sql = "JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{table_name}.status_id" sql = +"JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{table_name}.status_id"
return sql unless options.key?(:is_closed) return sql if is_closed.nil?
sql << " AND #{IssueStatus.table_name}.is_closed =" sql << " AND #{IssueStatus.table_name}.is_closed = #{is_closed ? connection.quoted_true : connection.quoted_false}"
sql << if options[:is_closed]
" #{connection.quoted_true}"
else
" #{connection.quoted_false}"
end
sql sql
end end
end end
module InstanceMethods module InstanceMethods
def sidbar_change_status_allowed_to(user, new_status_id = nil) def sidbar_change_status_allowed_to(user, new_status_id = nil)
statuses = new_statuses_allowed_to(user) statuses = new_statuses_allowed_to user
if new_status_id.present? if new_status_id.present?
statuses.detect { |s| new_status_id == s.id && !timelog_required?(s.id) } statuses.detect { |s| new_status_id == s.id && !timelog_required?(s.id) }
else else
statuses.reject { |s| timelog_required?(s.id) } statuses.reject { |s| timelog_required? s.id }
end end
end end
@ -63,7 +60,7 @@ module Additionals
return if Redmine::Plugin.installed?('redmine_automation') && author_id == RedmineAutomation.bot_user_id return if Redmine::Plugin.installed?('redmine_automation') && author_id == RedmineAutomation.bot_user_id
add_autowatcher User.current add_autowatcher User.current
add_autowatcher(author) if (new_record? || author_id != author_id_was) && author != User.current add_autowatcher author if (new_record? || author_id != author_id_was) && author != User.current
if !assigned_to_id.nil? && assigned_to_id != User.current.id && (new_record? || assigned_to_id != assigned_to_id_was) if !assigned_to_id.nil? && assigned_to_id != User.current.id && (new_record? || assigned_to_id != assigned_to_id_was)
add_autowatcher assigned_to add_autowatcher assigned_to
@ -127,7 +124,7 @@ module Additionals
end end
def auto_assigned_to_user def auto_assigned_to_user
manager_role = Role.builtin.find_by(id: Additionals.setting(:issue_auto_assign_role)) manager_role = Role.builtin.find_by id: Additionals.setting(:issue_auto_assign_role)
groups = autoassign_get_group_list groups = autoassign_get_group_list
return groups[manager_role].first.id unless groups.nil? || groups[manager_role].blank? return groups[manager_role].first.id unless groups.nil? || groups[manager_role].blank?
@ -150,7 +147,7 @@ module Additionals
end end
def validate_timelog_required def validate_timelog_required
return true unless timelog_required?(status_id) return true unless timelog_required? status_id
errors.add :base, :issue_requires_timelog errors.add :base, :issue_requires_timelog
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
module Patches module Patches
module IssuePriorityPatch module IssuePriorityPatch
@ -13,7 +15,7 @@ module Additionals
module InstanceMethods module InstanceMethods
def css_classes_with_additionals def css_classes_with_additionals
classes = [css_classes_without_additionals, css_name_based_class] classes = [css_classes_without_additionals, css_name_based_class]
classes.join(' ') classes.join ' '
end end
# css class based on priority name # css class based on priority name

View File

@ -1,15 +1,17 @@
# frozen_string_literal: true
module Additionals module Additionals
module Patches module Patches
module PrincipalPatch module PrincipalPatch
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
scope :assignable, -> { active.visible.where(type: %w[User Group]) } scope :assignable, -> { active.visible.where type: %w[User Group] }
scope :assignable_for_issues, lambda { |*args| scope :assignable_for_issues, lambda { |*args|
project = args.first project = args.first
users = assignable users = assignable
users = users.where.not(type: 'Group') unless Setting.issue_group_assignment? users = users.where.not type: 'Group' unless Setting.issue_group_assignment?
users = users.joins(members: :roles) users = users.joins(members: :roles)
.where(roles: { assignable: true }) .where(roles: { assignable: true })
.distinct .distinct
@ -35,7 +37,7 @@ module Additionals
active active
else else
# self and members of visible projects # self and members of visible projects
scope = if user.allowed_to?(:show_hidden_roles_in_memberbox, nil, global: true) scope = if user.allowed_to? :show_hidden_roles_in_memberbox, nil, global: true
active.where("#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id " \ active.where("#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id " \
"FROM #{Member.table_name} WHERE project_id IN (?))", "FROM #{Member.table_name} WHERE project_id IN (?))",
user.id, user.visible_project_ids) user.id, user.visible_project_ids)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Additionals module Additionals
module Patches module Patches
module ProjectPatch module ProjectPatch
@ -52,12 +54,12 @@ module Additionals
end end
def visible_principals def visible_principals
query = ::Query.new(project: self, name: '_') query = ::Query.new project: self, name: '_'
query&.principals query&.principals
end end
def visible_users def visible_users
query = ::Query.new(project: self, name: '_') query = ::Query.new project: self, name: '_'
query&.users query&.users
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require_dependency 'projects_controller' require_dependency 'projects_controller'
module Additionals module Additionals
@ -27,7 +29,7 @@ module Additionals
def find_dashboard def find_dashboard
if params[:dashboard_id].present? if params[:dashboard_id].present?
begin begin
@dashboard = Dashboard.project_only.find(params[:dashboard_id]) @dashboard = Dashboard.project_only.find params[:dashboard_id]
raise ::Unauthorized unless @dashboard.visible? raise ::Unauthorized unless @dashboard.visible?
raise ::Unauthorized unless @dashboard.project.nil? || @dashboard.project == @project raise ::Unauthorized unless @dashboard.project.nil? || @dashboard.project == @project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Additionals
module Patches
module QueriesHelperPatch
extend ActiveSupport::Concern
included do
def additional_csv_separator
l(:general_csv_separator) == ',' ? ';' : ','
end
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More