redmineup 1.0.4 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/icons.svg +8 -0
  3. data/app/assets/javascripts/chart.min.js +13 -0
  4. data/{vendor → app}/assets/javascripts/select2_helpers.js +1 -1
  5. data/{vendor → app}/assets/stylesheets/calendars.css +3 -3
  6. data/{vendor → app}/assets/stylesheets/money.css +1 -2
  7. data/{vendor → app}/assets/stylesheets/select2.css +1 -1
  8. data/doc/CHANGELOG +26 -4
  9. data/lib/redmineup/acts_as_votable/votable.rb +2 -5
  10. data/lib/redmineup/assets_manager.rb +12 -1
  11. data/lib/redmineup/currency.rb +3 -1
  12. data/lib/redmineup/helpers/external_assets_helper.rb +1 -1
  13. data/lib/redmineup/liquid/drops/attachment_drop.rb +8 -0
  14. data/lib/redmineup/liquid/drops/custom_field_enumeration_drop.rb +11 -0
  15. data/lib/redmineup/liquid/drops/issues_drop.rb +11 -19
  16. data/lib/redmineup/liquid/drops/users_drop.rb +2 -3
  17. data/lib/redmineup/liquid/drops/version_drop.rb +40 -0
  18. data/lib/redmineup/liquid/filters/additional.rb +31 -0
  19. data/lib/redmineup/liquid/filters/base.rb +16 -1
  20. data/lib/redmineup/patches/compatibility/sprite_patch.rb +21 -0
  21. data/lib/redmineup/patches/compatibility_patch.rb +4 -1
  22. data/lib/redmineup/version.rb +1 -1
  23. data/lib/redmineup.rb +22 -2
  24. data/test/liquid/drops/issues_drop_test.rb +5 -0
  25. data/test/liquid/filters/additional_filter_test.rb +31 -0
  26. data/test/models/attachment.rb +5 -0
  27. data/test/models/issue.rb +5 -0
  28. data/test/models/journal.rb +6 -0
  29. data/test/schema.rb +64 -57
  30. metadata +20 -10
  31. /data/{vendor → app}/assets/images/money.png +0 -0
  32. /data/{vendor → app}/assets/images/vcard.png +0 -0
  33. /data/{vendor/assets/javascripts/Chart.bundle.min.js → app/assets/javascripts/Chart.bundle.min.js.bak} +0 -0
  34. /data/{vendor → app}/assets/javascripts/select2.js +0 -0
data/doc/CHANGELOG CHANGED
@@ -1,11 +1,33 @@
1
- == Redmine UP gem changelog
1
+ == RedmineUP gem changelog
2
2
 
3
3
  Redmine UP gem - general functions for plugins (tags, vote, viewing, currency)
4
- Copyright (C) 2011-2024 Kirill Bezrukov (RedmineUP)
4
+ Copyright (C) 2011-2025 Kirill Bezrukov (RedmineUP)
5
5
  https://www.redmineup.com/
6
6
 
7
- == 2024-01-30 v1.0.4
7
+ == 2025-02-04 v1.0.9
8
8
 
9
+ * Added regex_replace Liquid filter
10
+ * Added svg icons for Redmine 6
11
+
12
+ == 2025-01-31 v1.0.8
13
+
14
+ * Fixed compatibility patch for Redmine < 5
15
+
16
+ == 2025-01-29 v1.0.7
17
+
18
+ * Fixed parse_inline_attachments Liquid filter
19
+ * Added compatibility patch for Redmine 6 sprite_icon
20
+
21
+ == 2024-05-07 v1.0.6
22
+
23
+ * Updated Chart.js to v3.9.1
24
+ * Fixed assets paths for Redmine 5.3+
25
+ * Added attachments to IssueDrop and JournalDrop
26
+ * Added filter parse_inline_attachments for replace image urls
27
+
28
+ == 2024-01-31 v1.0.5
29
+
30
+ * Added plugin_installed? method
9
31
  * Compatibility patches for ApplicationRecord
10
32
 
11
33
  == 2023-10-12 v1.0.1
@@ -15,4 +37,4 @@ https://www.redmineup.com/
15
37
  == 2023-09-27 v1.0.0
16
38
 
17
39
  * Initial release
18
- * Mirgated from redmineup gem
40
+ * Mirgated from redmineup gem
@@ -232,11 +232,8 @@ module Redmineup
232
232
  end
233
233
  end
234
234
  self.record_timestamps = false
235
- if (::ActiveRecord::VERSION::MAJOR == 3) && (::ActiveRecord::VERSION::MINOR != 0)
236
- self.assign_attributes(updates, :without_protection => true) && self.save if !updates.empty?
237
- else
238
- self.assign_attributes(updates) && self.save if !updates.empty?
239
- end
235
+
236
+ self.update(updates) if !updates.empty?
240
237
  end
241
238
 
242
239
  # results
@@ -2,7 +2,8 @@ module Redmineup
2
2
  class AssetsManager
3
3
  def self.install_assets
4
4
  return unless Gem.loaded_specs[GEM_NAME]
5
- source = File.join(Gem.loaded_specs[GEM_NAME].full_gem_path, 'vendor', 'assets')
5
+
6
+ source = File.join(Gem.loaded_specs[GEM_NAME].full_gem_path, 'app', 'assets')
6
7
  destination = File.join(Dir.pwd, 'public', 'plugin_assets', GEM_NAME)
7
8
  return unless File.directory?(source)
8
9
 
@@ -39,5 +40,15 @@ module Redmineup
39
40
  end
40
41
  end
41
42
  end
43
+
44
+ def self.base_path
45
+ Gem.loaded_specs[GEM_NAME].full_gem_path
46
+ end
47
+
48
+ def self.assets_paths
49
+ return [] unless Gem.loaded_specs[GEM_NAME]
50
+
51
+ Dir[File.join(base_path, 'app', 'assets', '*')]
52
+ end
42
53
  end
43
54
  end
@@ -40,7 +40,9 @@ module Redmineup
40
40
  Redmine::MenuManager.map(:admin_menu).push(:redmineup_money,
41
41
  { controller: 'redmineup', action: 'settings', id: 'money' },
42
42
  caption: :label_redmineup_money,
43
- html: { class: 'icon icon-redmineup-money' })
43
+ html: { class: 'icon icon-redmineup-money' },
44
+ icon: 'money',
45
+ plugin: :redmineup)
44
46
 
45
47
  end
46
48
 
@@ -13,7 +13,7 @@ module Redmineup
13
13
  def chartjs_assets
14
14
  return if @chartjs_tag_included
15
15
  @chartjs_tag_included = true
16
- javascript_include_tag('Chart.bundle.min', plugin: GEM_NAME)
16
+ javascript_include_tag('chart.min', plugin: GEM_NAME)
17
17
  end
18
18
 
19
19
  end
@@ -42,6 +42,14 @@ module Redmineup
42
42
  end
43
43
  @content
44
44
  end
45
+
46
+ def to_s
47
+ filename
48
+ end
49
+
50
+ def to_base64_string
51
+ Base64.strict_encode64(File.read(@attachment.diskfile))
52
+ end
45
53
  end
46
54
  end
47
55
  end
@@ -0,0 +1,11 @@
1
+ module Redmineup
2
+ module Liquid
3
+ class CustomFieldEnumerationDrop < ::Liquid::Drop
4
+ delegate :id, :to_s, to: :@enumeration
5
+
6
+ def initialize(enumeration)
7
+ @enumeration = enumeration
8
+ end
9
+ end
10
+ end
11
+ end
@@ -35,6 +35,7 @@ module Redmineup
35
35
 
36
36
  delegate :id,
37
37
  :subject,
38
+ :description,
38
39
  :visible?,
39
40
  :closed?,
40
41
  :start_date,
@@ -71,6 +72,10 @@ module Redmineup
71
72
  @assignee ||= UserDrop.new(@issue.assigned_to)
72
73
  end
73
74
 
75
+ def attachments
76
+ @attachments ||= @issue.attachments.map { |attachment| AttachmentDrop.new(attachment) }
77
+ end
78
+
74
79
  def tracker
75
80
  @tracker ||= @issue.tracker && @issue.tracker.name
76
81
  end
@@ -103,10 +108,6 @@ module Redmineup
103
108
  @project ||= ProjectDrop.new @issue.project if @issue.project
104
109
  end
105
110
 
106
- def description
107
- @description ||= replace_images_urls(@issue.description)
108
- end
109
-
110
111
  def subtasks
111
112
  @subtasks ||= IssuesDrop.new @issue.children
112
113
  end
@@ -120,11 +121,11 @@ module Redmineup
120
121
  end
121
122
 
122
123
  def notes
123
- @notes ||= @issue.journals.where.not(notes: [nil, '']).order(:created_on).map(&:notes).map { |note| replace_images_urls(note) }
124
+ @notes ||= @issue.journals.where.not(notes: [nil, '']).order(:created_on).map(&:notes)
124
125
  end
125
126
 
126
127
  def journals
127
- @journals ||= JournalsDrop.new(@issue.journals.where.not(notes: nil).find_each { |journal| journal.notes = replace_images_urls(journal.notes) })
128
+ @journals ||= JournalsDrop.new @issue.journals.where.not(notes: [nil, ''])
128
129
  end
129
130
 
130
131
  def tags
@@ -158,19 +159,6 @@ module Redmineup
158
159
  def custom_field_values
159
160
  @issue.custom_field_values
160
161
  end
161
-
162
- private
163
-
164
- def replace_images_urls(text)
165
- text.gsub(/\!.*\!/) do |i_name|
166
- i_name = i_name.delete('!')
167
- i_name_css = i_name.scan(/^\{.*\}/).first.to_s
168
- attachment = @issue.attachments.find_by(filename: i_name.gsub(i_name_css, ''))
169
- image = AttachmentDrop.new attachment if attachment
170
- attach_url = image.try(:url)
171
- attach_url ? "!#{i_name_css}#{attach_url}!" : i_name
172
- end
173
- end
174
162
  end
175
163
 
176
164
  class JournalsDrop < ::Liquid::Drop
@@ -212,6 +200,10 @@ module Redmineup
212
200
  def issue
213
201
  @issue ||= IssueDrop.new @journal.issue if @journal.issue
214
202
  end
203
+
204
+ def attachments
205
+ @attachments ||= @journal.attachments.map { |attachment| AttachmentDrop.new(attachment) }
206
+ end
215
207
  end
216
208
  end
217
209
  end
@@ -35,7 +35,7 @@ module Redmineup
35
35
  end
36
36
 
37
37
  class UserDrop < ::Liquid::Drop
38
- delegate :id, :name, :firstname, :lastname, :mail, :active?, :admin?, :logged?, :language, :to => :@user, allow_nil: true
38
+ delegate :id, :name, :firstname, :lastname, :mail, :active?, :admin?, :logged?, :language, :to_s, :to => :@user, allow_nil: true
39
39
 
40
40
  def initialize(user)
41
41
  @user = user
@@ -61,8 +61,7 @@ module Redmineup
61
61
 
62
62
  def custom_field_values
63
63
  @user.custom_field_values
64
- end
65
-
64
+ end
66
65
  end
67
66
  end
68
67
  end
@@ -0,0 +1,40 @@
1
+ module Redmineup
2
+ module Liquid
3
+ class VersionDrop < ::Liquid::Drop
4
+ delegate :id,
5
+ :name,
6
+ :description,
7
+ :effective_date,
8
+ :due_date,
9
+ :wiki_page_title,
10
+ :status,
11
+ :sharing,
12
+ :default_project_version,
13
+ :start_date,
14
+ :due_date,
15
+ :estimated_hours,
16
+ :spent_hours,
17
+ :closed?,
18
+ :open?,
19
+ :completed?,
20
+ :completed_percent,
21
+ :closed_percent,
22
+ :overdue?,
23
+ :issues_count,
24
+ :open_issues_count,
25
+ :closed_issues_count,
26
+ :visible_fixed_issues,
27
+ :wiki_page,
28
+ :to_s_with_project,
29
+ :shared?,
30
+ :deletable?,
31
+ :default_project_version,
32
+ :to_s,
33
+ to: :@version
34
+
35
+ def initialize(version)
36
+ @version = version
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ require 'liquid'
2
+
3
+ module Redmineup
4
+ module Liquid
5
+ module Filters
6
+ module Additional
7
+ def parse_inline_attachments(text, obj)
8
+ attachments = obj.attachments if obj.respond_to?(:attachments)
9
+
10
+ if attachments.present?
11
+ text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
12
+ filename, ext, alt, alttext = $1, $2, $3, $4
13
+ # search for the picture in attachments
14
+ if found = Attachment.latest_attach(attachments, CGI.unescape(filename))
15
+ desc = found.description.to_s.delete('"')
16
+ alt = " title=\"#{desc}\" alt=\"#{desc}\"" if !desc.blank? && alttext.blank?
17
+ "src='data:image/#{found.content_type};base64,#{found.to_base64_string}' #{alt}"
18
+ else
19
+ m
20
+ end
21
+ end
22
+ end
23
+
24
+ text
25
+ end
26
+ end
27
+
28
+ ::Liquid::Template.register_filter(Redmineup::Liquid::Filters::Additional)
29
+ end
30
+ end
31
+ end
@@ -38,6 +38,16 @@ module Redmineup
38
38
  Digest::MD5.hexdigest(input) unless input.blank?
39
39
  end
40
40
 
41
+ def regex_replace(str, regex_search, value_replace)
42
+ regex = /#{regex_search}/
43
+ return str.gsub(regex, value_replace)
44
+ end
45
+
46
+ def regex_replace_once(str, regex_search, value_replace)
47
+ regex = /#{regex_search}/
48
+ return str.sub(regex, value_replace)
49
+ end
50
+
41
51
  # example:
42
52
  # {{ "http:://www.example.com?key=hello world" | encode }}
43
53
  #
@@ -100,7 +110,12 @@ module Redmineup
100
110
  def custom_field(input, field_name)
101
111
  if input.respond_to?(:custom_field_values)
102
112
  custom_value = input.custom_field_values.detect { |cfv| cfv.custom_field.name == field_name }
103
- custom_value.custom_field.format.formatted_custom_value(nil, custom_value) if custom_value
113
+ if custom_value
114
+ result = custom_value.custom_field.format.formatted_custom_value(nil, custom_value)
115
+ return result if result.respond_to?(:to_liquid)
116
+ drop_class = "Redmineup::Liquid::#{result.class.name}Drop"
117
+ Object.const_defined?(drop_class) ? drop_class.constantize.new(result) : result
118
+ end
104
119
  end
105
120
  end
106
121
 
@@ -0,0 +1,21 @@
1
+ module Redmineup
2
+ module Patches
3
+ module Compatibility
4
+ module SpritePatch
5
+ def self.included(base)
6
+ base.send(:include, InstanceMethods)
7
+ end
8
+
9
+ module InstanceMethods
10
+ def sprite_icon(icon_name, label = nil, icon_only: false, size: '18', css_class: nil, sprite: "icons", plugin: nil)
11
+ label
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ unless ActionView::Base.included_modules.include?(Redmineup::Patches::Compatibility::SpritePatch)
20
+ ActionView::Base.send(:include, Redmineup::Patches::Compatibility::SpritePatch)
21
+ end
@@ -4,4 +4,7 @@ end
4
4
  if Redmine::VERSION.to_s < "5"
5
5
  require 'redmineup/patches/compatibility/user_patch'
6
6
  end
7
- require 'redmineup/patches/compatibility/routing_mapper_patch'
7
+ if Redmine::VERSION.to_s < "6"
8
+ require 'redmineup/patches/compatibility/sprite_patch'
9
+ end
10
+ require 'redmineup/patches/compatibility/routing_mapper_patch'
@@ -1,3 +1,3 @@
1
1
  module Redmineup
2
- VERSION = '1.0.4'
2
+ VERSION = '1.0.9'
3
3
  end
data/lib/redmineup.rb CHANGED
@@ -27,6 +27,7 @@ require 'redmineup/money_helper'
27
27
  require 'redmineup/colors_helper'
28
28
 
29
29
  require 'liquid'
30
+ require 'redmineup/liquid/filters/additional'
30
31
  require 'redmineup/liquid/filters/base'
31
32
  require 'redmineup/liquid/filters/arrays'
32
33
  require 'redmineup/liquid/filters/colors'
@@ -37,16 +38,22 @@ require 'redmineup/liquid/drops/users_drop'
37
38
  require 'redmineup/liquid/drops/time_entries_drop'
38
39
  require 'redmineup/liquid/drops/attachment_drop'
39
40
  require 'redmineup/liquid/drops/issue_relations_drop'
41
+ require 'redmineup/liquid/drops/version_drop'
42
+ require 'redmineup/liquid/drops/custom_field_enumeration_drop'
40
43
 
41
44
  require 'redmineup/helpers/external_assets_helper'
42
45
  require 'redmineup/helpers/form_tag_helper'
43
46
  require 'redmineup/helpers/calendars_helper'
44
47
  require 'redmineup/assets_manager'
45
48
 
46
- require 'redmineup/patches/liquid_patch' unless BigDecimal.respond_to?(:new)
49
+ require 'redmineup/patches/liquid_patch'
47
50
 
48
51
  module Redmineup
49
52
  GEM_NAME = 'redmineup'.freeze
53
+
54
+ def self.plugin_installed?(plugin_id)
55
+ Rails.root.join("plugins/#{plugin_id}/init.rb").exist?
56
+ end
50
57
  end
51
58
 
52
59
  require 'application_record' unless defined?(ApplicationRecord)
@@ -57,7 +64,20 @@ if defined?(ActiveRecord::Base)
57
64
  ActiveRecord::Base.extend(Redmineup::ActsAsVotable::Votable)
58
65
  end
59
66
 
60
- Redmineup::AssetsManager.install_assets
67
+ if defined?(Propshaft::Assembly)
68
+ Propshaft::Assembly.prepend(Module.new do
69
+ def initialize(config)
70
+ base_dir = Pathname.new(Redmineup::AssetsManager.base_path)
71
+ paths = Redmineup::AssetsManager.assets_paths.map { |path| Pathname.new(path)}
72
+ asset_prefix = "plugin_assets/#{Redmineup::GEM_NAME}"
73
+
74
+ config[:redmine_extension_paths] << Redmine::AssetPath.new(base_dir, paths, asset_prefix)
75
+ super
76
+ end
77
+ end)
78
+ else
79
+ Redmineup::AssetsManager.install_assets
80
+ end
61
81
 
62
82
  if defined?(ActionView::Base)
63
83
  ActionView::Base.send :include, Redmineup::CalendarsHelper
@@ -26,6 +26,11 @@ module Redmineup
26
26
  assert_equal @user.name, @liquid_render.render('{{ issue.author.name }}')
27
27
  end
28
28
 
29
+ def test_issue_attachments
30
+ attachment_author = @issue.attachments.first.author.name
31
+ assert_equal attachment_author, @liquid_render.render('{% assign attach = issue.attachments | first %}{{ attach.author.name }}')
32
+ end
33
+
29
34
  def test_issue_delegated
30
35
  assert_equal [@issue.id, @issue.subject, @issue.description].join('|'),
31
36
  @liquid_render.render('{{ issue.id }}|{{ issue.subject }}|{{ issue.description }}')
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/../liquid_helper'
2
+ include LiquidHelperMethods
3
+
4
+ module Redmineup
5
+ class AdditionalFilterTest < ActiveSupport::TestCase
6
+ def setup
7
+ @issue = Issue.first
8
+ @issue_drop = Liquid::IssueDrop.new(@issue)
9
+ @strainer = ::Liquid::Context.new.strainer
10
+ end
11
+
12
+ def test_parse_inline_attachments
13
+ text = '<p>description with image <img src="screenshot.png" /></p>'
14
+ attachment = mock_attachment
15
+ attachments = [attachment]
16
+ @issue_drop.define_singleton_method(:attachments) { attachments }
17
+ Attachment.stub(:latest_attach, attachment) do
18
+ assert_equal '<p>description with image <img src="mock_url" title="attach_image" alt="attach_image" loading="lazy" /></p>', @strainer.parse_inline_attachments(text, @issue_drop)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def mock_attachment
25
+ attachment = Minitest::Mock.new
26
+ attachment.expect(:url, 'mock_url')
27
+ attachment.expect(:description, "attach_image")
28
+ attachment
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,8 @@
1
1
  class Attachment < ActiveRecord::Base
2
2
  belongs_to :author, :class_name => "User"
3
+
4
+ def self.latest_attach(attachments, filename)
5
+ # stub method for test
6
+ # returned one attachment
7
+ end
3
8
  end
data/test/models/issue.rb CHANGED
@@ -8,6 +8,7 @@ class Issue < ActiveRecord::Base
8
8
 
9
9
  has_many :relations_from, class_name: 'IssueRelation', foreign_key: 'issue_from_id', dependent: :delete_all
10
10
  has_many :relations_to, class_name: 'IssueRelation', foreign_key: 'issue_to_id', dependent: :delete_all
11
+ has_many :journals, foreign_key: 'journalized_id'
11
12
 
12
13
  up_acts_as_draftable
13
14
  up_acts_as_taggable
@@ -18,4 +19,8 @@ class Issue < ActiveRecord::Base
18
19
  def visible?
19
20
  true
20
21
  end
22
+
23
+ def attachments
24
+ Attachment.all
25
+ end
21
26
  end
@@ -0,0 +1,6 @@
1
+ class Journal < ActiveRecord::Base
2
+ belongs_to :journalized, :polymorphic => true
3
+ belongs_to :issue, :foreign_key => :journalized_id
4
+
5
+ belongs_to :user
6
+ end