Actualiza el plugin Additionals a 3.0.2-master

This commit is contained in:
Manuel Cillero 2021-03-20 11:12:56 +01:00
parent 3b6a41320c
commit cfa0d58b18
164 changed files with 2027 additions and 58190 deletions

View file

@ -1,3 +1,5 @@
require 'additionals/version'
module Additionals
MAX_CUSTOM_MENU_ITEMS = 5
SELECT2_INIT_ENTRIES = 20
@ -5,17 +7,15 @@ module Additionals
GOTO_LIST = " \xc2\xbb".freeze
LIST_SEPARATOR = "#{GOTO_LIST} ".freeze
RenderAsync.configuration.jquery = true
class << self
def setup
incompatible_plugins %w[redmine_issue_control_panel
redmine_editauthor
RenderAsync.configuration.jquery = true
incompatible_plugins %w[redmine_editauthor
redmine_changeauthor
redmine_auto_watch]
patch %w[AccountController
ApplicationController
patch %w[ApplicationController
AutoCompletesController
Issue
IssuePriority
@ -60,22 +60,7 @@ module Additionals
require_dependency 'additionals/hooks'
# Macros
load_macros %w[cryptocompare date fa gist gmap google_docs group_users iframe
issue redmine_issue redmine_wiki
last_updated_at last_updated_by meteoblue member new_issue project
recently_updated reddit slideshare tradingview twitter user vimeo youtube asciinema]
end
def settings_compatible(plugin_name)
if Setting[plugin_name].class == Hash
# convert Rails 4 data (this runs only once)
new_settings = ActiveSupport::HashWithIndifferentAccess.new(Setting[plugin_name])
Setting.send("#{plugin_name}=", new_settings)
new_settings
else
# Rails 5 uses ActiveSupport::HashWithIndifferentAccess
Setting[plugin_name]
end
load_macros
end
# support with default setting as fall back
@ -91,6 +76,15 @@ module Additionals
true? setting(value)
end
# required multiple times because of this bug: https://www.redmine.org/issues/33290
def redmine_database_ready?(with_table = nil)
ActiveRecord::Base.connection
rescue ActiveRecord::NoDatabaseError
false
else
with_table.nil? || ActiveRecord::Base.connection.table_exists?(with_table)
end
def true?(value)
return false if value.is_a? FalseClass
return true if value.is_a?(TrueClass) || value.to_i == 1 || value.to_s.casecmp('true').zero?
@ -98,6 +92,17 @@ module Additionals
false
end
def debug(message)
return if Rails.env.production?
Rails.logger.debug "#{Time.current.strftime('%H:%M:%S')} DEBUG [#{caller_locations(1..1).first.label}]: #{message}"
end
def class_prefix(klass)
klass_name = klass.is_a?(String) ? klass : klass.name
klass_name.underscore.tr '/', '_'
end
def now_with_user_time_zone(user = User.current)
if user.time_zone.nil?
Time.zone.now
@ -108,7 +113,7 @@ module Additionals
def incompatible_plugins(plugins = [], title = 'additionals')
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
@ -124,10 +129,19 @@ module Additionals
end
end
def load_macros(macros = [], plugin_id = 'additionals')
macro_dir = Rails.root.join("plugins/#{plugin_id}/lib/#{plugin_id}/wiki_macros")
macros.each do |macro|
require_dependency "#{macro_dir}/#{macro.underscore}_macro"
def load_macros(plugin_id = 'additionals')
Dir[File.join(plugin_dir(plugin_id),
'lib',
plugin_id,
'wiki_macros',
'**/*_macro.rb')].sort.each { |f| require f }
end
def plugin_dir(plugin_id = 'additionals')
if Gem.loaded_specs[plugin_id].nil?
File.join Redmine::Plugin.directory, plugin_id
else
Gem.loaded_specs[plugin_id].full_gem_path
end
end
@ -135,7 +149,7 @@ module Additionals
cached_settings_name = "@load_settings_#{plugin_id}"
cached_settings = instance_variable_get cached_settings_name
if cached_settings.nil?
data = YAML.safe_load(ERB.new(IO.read(Rails.root.join("plugins/#{plugin_id}/config/settings.yml"))).result) || {}
data = YAML.safe_load(ERB.new(IO.read(File.join(plugin_dir(plugin_id), '/config/settings.yml'))).result) || {}
instance_variable_set cached_settings_name, data.symbolize_keys
else
cached_settings
@ -156,7 +170,31 @@ module Additionals
private
def settings
settings_compatible :plugin_additionals
Setting[:plugin_additionals]
end
end
# Run the classic redmine plugin initializer after rails boot
class Plugin < ::Rails::Engine
require 'deface'
require 'emoji'
require 'render_async'
require 'rss'
require 'slim'
config.after_initialize do
# engine_name could be used (additionals_plugin), but can
# create some side effencts
plugin_id = 'additionals'
# if plugin is already in plugins directory, use this and leave here
next if Redmine::Plugin.installed? plugin_id
# gem is used as redmine plugin
require File.expand_path '../init', __dir__
AdditionalTags.setup
Additionals::Gemify.install_assets plugin_id
Additionals::Gemify.create_plugin_hint plugin_id
end
end
end

View file

@ -5,7 +5,7 @@ module Additionals
users = prj.assignable_users_and_groups.to_a
users << author if author&.active?
if assigned_to_id_was.present?
assignee = Principal.find_by(id: assigned_to_id_was)
assignee = Principal.find_by id: assigned_to_id_was
users << assignee if assignee
end

View file

@ -0,0 +1,62 @@
module Additionals
class Gemify
class << self
# install assets from gem (without asset pipline)
def install_assets(plugin_id = 'additionals')
return unless Gem.loaded_specs[plugin_id]
source = File.join Gem.loaded_specs[plugin_id].full_gem_path, 'assets'
destination = File.join Dir.pwd, 'public', 'plugin_assets', plugin_id
return unless File.directory? source
source_files = Dir["#{source}/**/*"]
source_dirs = source_files.select { |d| File.directory?(d) }
source_files -= source_dirs
unless source_files.empty?
base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(source, ''))
begin
FileUtils.mkdir_p base_target_dir
rescue StandardError => e
raise "Could not create directory #{base_target_dir}: " + e.message
end
end
source_dirs.each do |dir|
target_dir = File.join(destination, dir.gsub(source, ''))
begin
FileUtils.mkdir_p(target_dir)
rescue StandardError => e
raise "Could not create directory #{target_dir}: " + e.message
end
end
source_files.each do |file|
begin
target = File.join destination, file.gsub(source, '')
FileUtils.cp(file, target) unless File.exist?(target) && FileUtils.identical?(file, target)
rescue StandardError => e
raise "Could not copy #{file} to #{target}: " + e.message
end
end
end
# Create text file to Redmine's plugins directory.
# The purpose is telling plugins directory to users.
def create_plugin_hint(plugin_id = 'additionals')
plugins_dir = File.join(Bundler.root, 'plugins')
path = File.join plugins_dir, plugin_id
return if File.exist? path
File.open(path, 'w') do |f|
f.write(<<PLUGIN_HINT)
This plugin was installed as gem wrote to Gemfile.local instead of putting Redmine's plugin directory.
See #{plugin_id} gem installed directory.
PLUGIN_HINT
end
rescue Errno::EACCES => e
Rails.logger.warn "Could not create plugin hint file: #{e.message}"
end
end
end
end

View file

@ -1,5 +1,15 @@
module Additionals
module Helpers
def link_to_external(name, link, options = {})
options[:class] ||= 'external'
options[:class] << ' external' if options[:class].exclude? 'external'
options[:rel] ||= 'noopener'
options[:target] ||= '_blank'
link_to name, link, options
end
def additionals_list_title(options)
title = []
if options[:issue]
@ -116,16 +126,11 @@ module Additionals
if uri.scheme.nil? && uri.path[0] != '/' && issue_id_parts.count == 2
rc[:issue_id] = url
else
if request.nil?
# this is used by mailer
return rc if url.exclude?(Setting.host_name)
elsif uri.host != URI.parse(request.original_url).host
return rc
end
s_pos = uri.path.rindex '/issues/'
return rc unless s_pos
s_pos = uri.path.rindex('/issues/')
id_string = uri.path[s_pos + 8..-1]
e_pos = id_string.index('/')
e_pos = id_string.index '/'
rc[:issue_id] = e_pos.nil? ? id_string : id_string[0..e_pos - 1]
# check for comment_id
rc[:comment_id] = uri.fragment[5..-1].to_i if comment_id.nil? && uri.fragment.present? && uri.fragment[0..4] == 'note-'
@ -142,51 +147,6 @@ module Additionals
safe_join s
end
def system_uptime
if windows_platform?
`net stats srv | find "Statist"`
elsif File.exist?('/proc/uptime')
secs = `cat /proc/uptime`.to_i
min = 0
hours = 0
days = 0
if secs.positive?
min = (secs / 60).round
hours = (secs / 3_600).round
days = (secs / 86_400).round
end
if days >= 1
"#{days} #{l(:days, count: days)}"
elsif hours >= 1
"#{hours} #{l(:hours, count: hours)}"
else
"#{min} #{l(:minutes, count: min)}"
end
else
# this should be mac os
seconds = `sysctl -n kern.boottime | awk '{print $4}'`.tr(',', '')
so = DateTime.strptime(seconds.strip, '%s')
if so.present?
time_tag(so)
else
days = `uptime | awk '{print $3}'`.to_i.round
"#{days} #{l(:days, count: days)}"
end
end
end
def system_info
if windows_platform?
'unknown'
else
`uname -a`
end
end
def windows_platform?
true if /cygwin|mswin|mingw|bccwin|wince|emx/.match?(RUBY_PLATFORM)
end
def autocomplete_select_entries(name, type, option_tags, options = {})
unless option_tags.is_a?(String) || option_tags.blank?
# if option_tags is not an array, it should be an object
@ -212,18 +172,25 @@ module Additionals
def project_list_css_classes(project, level)
classes = [cycle('odd', 'even')]
classes += project.css_classes.split(' ')
classes += project.css_classes.split
if level.positive?
classes << 'idnt'
classes << "idnt-#{level}"
end
classes.join(' ')
classes.join ' '
end
def addtionals_textarea_cols(text, options = {})
[[(options[:min].presence || 8), text.to_s.length / 50].max, (options[:max].presence || 20)].min
end
def title_with_fontawesome(title, symbole, wrapper = 'span')
tag.send(wrapper) do
concat tag.i class: "#{symbole} for-fa-title", 'aria-hidden': 'true'
concat title
end
end
private
def additionals_already_loaded(scope, js_name)
@ -261,10 +228,6 @@ module Additionals
additionals_include_js 'clipboard.min'
end
def additionals_load_observe_field
additionals_include_js 'additionals_observe_field'
end
def additionals_load_font_awesome
additionals_include_css 'fontawesome-all.min'
end
@ -311,12 +274,10 @@ module Additionals
return if user.nil?
if user.type == 'Group'
if options[:no_link]
if options[:no_link] || !Redmine::Plugin.installed?('redmine_hrm')
user.name
elsif Redmine::Plugin.installed? 'redmine_hrm'
link_to_hrm_group user
else
user.name
link_to_hrm_group user
end
else
options[:size] = 14 if options[:size].nil?

View file

@ -17,7 +17,7 @@ module Additionals
render_on :view_my_account_preferences, partial: 'users/autowatch_involved_issue'
render_on :view_users_form_preferences, partial: 'users/autowatch_involved_issue'
render_on :view_users_show_contextual, partial: 'users/additionals_contextual'
render_on :view_wiki_show_sidebar_bottom, partial: 'additionals_sidebar'
render_on :view_wiki_show_sidebar_bottom, partial: 'wiki/additionals_sidebar'
def helper_issues_show_detail_after_setting(context = {})
detail = context[:detail]

View file

@ -1,27 +0,0 @@
module Additionals
module Patches
module AccountControllerPatch
extend ActiveSupport::Concern
included do
include InstanceMethods
invisible_captcha(only: [:register],
on_timestamp_spam: :timestamp_spam_check,
if: -> { Additionals.setting?(:invisible_captcha) })
end
module InstanceMethods
def timestamp_spam_check
# redmine uses same action for _GET and _POST
return unless request.post?
if respond_to?(:redirect_back)
redirect_back(fallback_location: home_url, flash: { error: InvisibleCaptcha.timestamp_error_message })
else
redirect_to :back, flash: { error: InvisibleCaptcha.timestamp_error_message }
end
end
end
end
end
end

View file

@ -12,15 +12,15 @@ module Additionals
end
end
def issue_assignee
assignee_classes = ['User']
assignee_classes << 'Group' if Setting.issue_group_assignment?
def global_users
scope = Principal.assignable
@assignee = scope.like(params[:q]).sorted.limit(100).to_a
render layout: false, partial: 'issue_assignee'
end
scope = Principal.where(type: assignee_classes).limit(100)
scope = scope.member_of(project) if @project.present?
scope = scope.distinct
@assignee = scope.active.visible.sorted.like(params[:q]).to_a
@assignee = @assignee.sort! { |x, y| x.name <=> y.name }
def issue_assignee
scope = Principal.assignable_for_issues @project
@assignee = scope.like(params[:q]).sorted.limit(100).to_a
render layout: false, partial: 'issue_assignee'
end
end

View file

@ -54,18 +54,19 @@ module Additionals
!watcher.active? ||
watched_by?(watcher)
add_watcher(watcher)
add_watcher watcher
end
def autowatch_involved
return unless Additionals.setting?(:issue_autowatch_involved) &&
User.current.pref.autowatch_involved_issue
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
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
end
true
@ -105,14 +106,10 @@ module Additionals
end
def status_x_affected?(new_status_id)
return false unless Additionals.setting?(:issue_current_user_status)
return false unless Additionals.setting? :issue_current_user_status
return false if Additionals.setting(:issue_assign_to_x).blank?
if Additionals.setting(:issue_assign_to_x).include?(new_status_id.to_s)
true
else
false
end
Additionals.setting(:issue_assign_to_x).include? new_status_id.to_s
end
private

View file

@ -4,6 +4,19 @@ module Additionals
extend ActiveSupport::Concern
included do
scope :assignable, -> { active.visible.where(type: %w[User Group]) }
scope :assignable_for_issues, lambda { |*args|
project = args.first
users = assignable
users = users.where.not(type: 'Group') unless Setting.issue_group_assignment?
users = users.joins(members: :roles)
.where(roles: { assignable: true })
.distinct
users = users.member_of project if project.present?
users
}
# TODO: find better solution, which not requires overwrite visible
# to filter out hide role members
scope :visible, lambda { |*args|

View file

@ -37,26 +37,20 @@ module Additionals
end
end
end
def users_by_role_old
roles_with_users = if Redmine::VERSION.to_s >= '4.2'
principals_by_role
else
super
end
roles_with_users.each do |role_with_users|
role = role_with_users.first
next unless role.hide
roles_with_users.delete(role) unless User.current.allowed_to?(:show_hidden_roles_in_memberbox, project)
end
roles_with_users
end
end
module InstanceMethods
# without hidden roles!
def all_principals_by_role
memberships.includes(:principal, :roles).each_with_object({}) do |m, h|
m.roles.each do |r|
h[r] ||= []
h[r] << m.principal
end
h
end
end
def visible_principals
query = ::Query.new(project: self, name: '_')
query&.principals
@ -70,10 +64,9 @@ module Additionals
# assignable_users result depends on Setting.issue_group_assignment?
# this result is not depending on issue settings
def assignable_users_and_groups
Principal.active
Principal.assignable
.joins(members: :roles)
.where(type: %w[User Group],
members: { project_id: id },
.where(members: { project_id: id },
roles: { assignable: true })
.distinct
.sorted

View file

@ -0,0 +1,3 @@
module Additionals
VERSION = '3.0.2-master'.freeze
end

View file

@ -48,7 +48,7 @@ module Additionals
classes << "fa-#{values[0]}"
end
classes += options[:class].split(' ') if options[:class].present?
classes += options[:class].split if options[:class].present?
classes << "fa-#{options[:size]}" if options[:size].present?
content_options = { class: classes.uniq.join(' ') }

View file

@ -43,7 +43,7 @@ module Additionals
comment_id = info[:comment_id] if comment_id.nil?
info[:issue_id])
issue = Issue.find_by(id: issue_id)
issue = Issue.find_by id: issue_id
return if issue.nil? || !issue.visible?
text = case options[:format]

View file

@ -23,7 +23,7 @@ module Additionals
macro :projects do |_obj, args|
_args, options = extract_macro_options(args, :title, :with_create_issue)
@projects = Additionals.load_projects
@projects = User.current.projects.active.includes(:enabled_modules).sorted
return if @projects.nil?
@html_options = { class: 'external' }
@ -35,13 +35,4 @@ module Additionals
end
end
end
def self.load_projects
all_projects = Project.active.visible.sorted
my_projects = []
all_projects.each do |p|
my_projects << p if User.current.member_of?(p)
end
my_projects
end
end

View file

@ -33,6 +33,8 @@ module Additionals
.where("#{WikiContent.table_name}.updated_on > ?", User.current.today - days)
.order("#{WikiContent.table_name}.updated_on desc")
pages = pages.visible(User.current, project: project) if pages.respond_to? :visible
s = []
date = nil
pages.each do |page_raw|

View file

@ -4,7 +4,7 @@ module Additionals
Redmine::WikiFormatting::Macros.register do
desc "Display link to user profile\n\n" \
"Syntax:\n\n" \
"{{user(USER_NAME [, format=USER_FORMAT, avatar=BOOL])}}\n\n" \
"{{user(USER_NAME [, format=USER_FORMAT, text=BOOL], avatar=BOOL])}}\n\n" \
"USER_NAME can be user id or user name (login name)\n" \
"USER_FORMATS\n" \
"- system (use system settings) (default)\n- " +
@ -14,19 +14,26 @@ module Additionals
"...Link to user with user id 1\n\n" \
"{{user(1, avatar=true)}}\n" \
"...Link to user with user id 1 with avatar\n\n" \
"{{user(current_user, text=true)}}\n" \
"...Show only user (without link) of the current user\n\n" \
"{{user(admin)}}\n" \
"...Link to user with username 'admin'\n\n" \
"{{user(admin, format=firstname)}}\n" \
"...Link to user with username 'admin' and show firstname as link text"
macro :user do |_obj, args|
args, options = extract_macro_options(args, :format, :avatar)
args, options = extract_macro_options(args, :format, :avatar, :text)
raise 'The correct usage is {{user(<user_id or username>, format=USER_FORMAT)}}' if args.empty?
user_id = args[0]
user = User.find_by id: user_id
user ||= User.find_by(login: user_id)
user ||= if user_id == 'current_user'
User.current
else
User.find_by login: user_id
end
return if user.nil?
name = if options[:format].blank?
@ -41,10 +48,12 @@ module Additionals
s << ' '
end
s << if user.active?
link_to h(name), user_url(user, only_path: controller_path != 'mailer'), class: user.css_classes
link_css = "macro #{user.css_classes}"
s << if user.active? && (options[:text].blank? || !options[:text])
link_to h(name), user_url(user, only_path: controller_path != 'mailer'), class: link_css
else
h name
tag.span h(name), class: link_css
end
safe_join s
end

View file

@ -21,12 +21,7 @@ module Additionals
width = options[:width].presence || 640
height = options[:height].presence || 360
autoplay = if !options[:autoplay].nil? && Additionals.true?(options[:autoplay])
true
else
false
end
autoplay = !options[:autoplay].nil? && Additionals.true?(options[:autoplay])
raise 'The correct usage is {{vimeo(<video key>[, width=x, height=y])}}' if args.empty?

View file

@ -21,12 +21,7 @@ module Additionals
width = options[:width].presence || 640
height = options[:height].presence || 360
autoplay = if !options[:autoplay].nil? && Additionals.true?(options[:autoplay])
true
else
false
end
autoplay = !options[:autoplay].nil? && Additionals.true?(options[:autoplay])
raise 'The correct usage is {{youtube(<video key>[, width=x, height=y])}}' if args.empty?