redmineup 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/Gemfile +4 -0
  4. data/README.md +204 -0
  5. data/Rakefile +11 -0
  6. data/app/controllers/redmineup_controller.rb +26 -0
  7. data/app/views/redmine_crm/redmineup_calendar/_calendar.html.erb +34 -0
  8. data/app/views/redmineup/_money.html.erb +44 -0
  9. data/app/views/redmineup/settings.html.erb +10 -0
  10. data/bitbucket-pipelines.yml +50 -0
  11. data/config/currency_iso.json +2544 -0
  12. data/config/locales/cs.yml +13 -0
  13. data/config/locales/de.yml +13 -0
  14. data/config/locales/en.yml +13 -0
  15. data/config/locales/es.yml +13 -0
  16. data/config/locales/ru.yml +13 -0
  17. data/config/routes.rb +5 -0
  18. data/doc/CHANGELOG +14 -0
  19. data/doc/LICENSE.txt +339 -0
  20. data/lib/redmineup/acts_as_draftable/draft.rb +40 -0
  21. data/lib/redmineup/acts_as_draftable/up_acts_as_draftable.rb +172 -0
  22. data/lib/redmineup/acts_as_list/list.rb +282 -0
  23. data/lib/redmineup/acts_as_priceable/up_acts_as_priceable.rb +33 -0
  24. data/lib/redmineup/acts_as_taggable/tag.rb +81 -0
  25. data/lib/redmineup/acts_as_taggable/tag_list.rb +111 -0
  26. data/lib/redmineup/acts_as_taggable/tagging.rb +16 -0
  27. data/lib/redmineup/acts_as_taggable/up_acts_as_taggable.rb +357 -0
  28. data/lib/redmineup/acts_as_viewed/up_acts_as_viewed.rb +274 -0
  29. data/lib/redmineup/acts_as_votable/up_acts_as_votable.rb +80 -0
  30. data/lib/redmineup/acts_as_votable/up_acts_as_voter.rb +20 -0
  31. data/lib/redmineup/acts_as_votable/votable.rb +323 -0
  32. data/lib/redmineup/acts_as_votable/vote.rb +28 -0
  33. data/lib/redmineup/acts_as_votable/voter.rb +131 -0
  34. data/lib/redmineup/assets_manager.rb +43 -0
  35. data/lib/redmineup/colors_helper.rb +192 -0
  36. data/lib/redmineup/compatibility/application_controller_patch.rb +33 -0
  37. data/lib/redmineup/compatibility/routing_mapper_patch.rb +25 -0
  38. data/lib/redmineup/currency/formatting.rb +224 -0
  39. data/lib/redmineup/currency/heuristics.rb +151 -0
  40. data/lib/redmineup/currency/loader.rb +23 -0
  41. data/lib/redmineup/currency.rb +450 -0
  42. data/lib/redmineup/engine.rb +4 -0
  43. data/lib/redmineup/helpers/external_assets_helper.rb +20 -0
  44. data/lib/redmineup/helpers/form_tag_helper.rb +88 -0
  45. data/lib/redmineup/helpers/rup_calendar_helper.rb +146 -0
  46. data/lib/redmineup/helpers/tags_helper.rb +13 -0
  47. data/lib/redmineup/helpers/vote_helper.rb +35 -0
  48. data/lib/redmineup/hooks/views_layouts_hook.rb +11 -0
  49. data/lib/redmineup/liquid/drops/attachment_drop.rb +47 -0
  50. data/lib/redmineup/liquid/drops/issue_relations_drop.rb +41 -0
  51. data/lib/redmineup/liquid/drops/issues_drop.rb +217 -0
  52. data/lib/redmineup/liquid/drops/news_drop.rb +54 -0
  53. data/lib/redmineup/liquid/drops/projects_drop.rb +86 -0
  54. data/lib/redmineup/liquid/drops/time_entries_drop.rb +65 -0
  55. data/lib/redmineup/liquid/drops/users_drop.rb +68 -0
  56. data/lib/redmineup/liquid/filters/arrays.rb +254 -0
  57. data/lib/redmineup/liquid/filters/base.rb +249 -0
  58. data/lib/redmineup/liquid/filters/colors.rb +31 -0
  59. data/lib/redmineup/money_helper.rb +66 -0
  60. data/lib/redmineup/patches/liquid_patch.rb +33 -0
  61. data/lib/redmineup/settings/money.rb +46 -0
  62. data/lib/redmineup/settings.rb +53 -0
  63. data/lib/redmineup/version.rb +3 -0
  64. data/lib/redmineup.rb +108 -0
  65. data/redmineup.gemspec +29 -0
  66. data/test/acts_as_draftable/draft_test.rb +29 -0
  67. data/test/acts_as_draftable/rup_acts_as_draftable_test.rb +178 -0
  68. data/test/acts_as_taggable/rup_acts_as_taggable_test.rb +350 -0
  69. data/test/acts_as_taggable/tag_list_test.rb +34 -0
  70. data/test/acts_as_taggable/tag_test.rb +72 -0
  71. data/test/acts_as_taggable/tagging_test.rb +15 -0
  72. data/test/acts_as_viewed/rup_acts_as_viewed_test.rb +47 -0
  73. data/test/acts_as_votable/rup_acts_as_votable_test.rb +19 -0
  74. data/test/acts_as_votable/rup_acts_as_voter_test.rb +14 -0
  75. data/test/acts_as_votable/votable_test.rb +507 -0
  76. data/test/acts_as_votable/voter_test.rb +296 -0
  77. data/test/currency_test.rb +292 -0
  78. data/test/database.yml +17 -0
  79. data/test/fixtures/attachments.yml +14 -0
  80. data/test/fixtures/issues.yml +24 -0
  81. data/test/fixtures/news.yml +8 -0
  82. data/test/fixtures/projects.yml +10 -0
  83. data/test/fixtures/taggings.yml +32 -0
  84. data/test/fixtures/tags.yml +11 -0
  85. data/test/fixtures/users.yml +9 -0
  86. data/test/fixtures/votable_caches.yml +2 -0
  87. data/test/fixtures/votables.yml +4 -0
  88. data/test/fixtures/voters.yml +6 -0
  89. data/test/liquid/drops/attachment_drop_test.rb +15 -0
  90. data/test/liquid/drops/issue_relations_drop_test.rb +24 -0
  91. data/test/liquid/drops/issues_drop_test.rb +38 -0
  92. data/test/liquid/drops/news_drop_test.rb +38 -0
  93. data/test/liquid/drops/projects_drop_test.rb +44 -0
  94. data/test/liquid/drops/uses_drop_test.rb +36 -0
  95. data/test/liquid/filters/arrays_filter_test.rb +31 -0
  96. data/test/liquid/filters/base_filter_test.rb +67 -0
  97. data/test/liquid/filters/colors_filter_test.rb +33 -0
  98. data/test/liquid/liquid_helper.rb +34 -0
  99. data/test/models/attachment.rb +3 -0
  100. data/test/models/issue.rb +21 -0
  101. data/test/models/issue_relation.rb +10 -0
  102. data/test/models/news.rb +3 -0
  103. data/test/models/project.rb +8 -0
  104. data/test/models/user.rb +11 -0
  105. data/test/models/vote_classes.rb +33 -0
  106. data/test/money_helper_test.rb +12 -0
  107. data/test/schema.rb +144 -0
  108. data/test/tags_helper_test.rb +29 -0
  109. data/test/test_helper.rb +66 -0
  110. data/test/vote_helper_test.rb +28 -0
  111. data/vendor/assets/images/money.png +0 -0
  112. data/vendor/assets/images/vcard.png +0 -0
  113. data/vendor/assets/javascripts/Chart.bundle.min.js +16 -0
  114. data/vendor/assets/javascripts/select2.js +2 -0
  115. data/vendor/assets/javascripts/select2_helpers.js +192 -0
  116. data/vendor/assets/stylesheets/money.css +96 -0
  117. data/vendor/assets/stylesheets/select2.css +424 -0
  118. metadata +295 -0
@@ -0,0 +1,65 @@
1
+ module Redmineup
2
+ module Liquid
3
+ class TimeEntriesDrop < ::Liquid::Drop
4
+ def initialize(time_entries)
5
+ @time_entries = time_entries
6
+ end
7
+
8
+ def all
9
+ @all ||= @time_entries.map do |time_entry|
10
+ TimeEntryDrop.new time_entry
11
+ end
12
+ end
13
+
14
+ def visible
15
+ @visible ||= @all.select(&:visible?)
16
+ end
17
+
18
+ def each(&block)
19
+ all.each(&block)
20
+ end
21
+
22
+ def size
23
+ @time_entries.size
24
+ end
25
+ end
26
+
27
+ class TimeEntryDrop < ::Liquid::Drop
28
+ include ActionView::Helpers::UrlHelper
29
+
30
+ delegate :id,
31
+ :hours,
32
+ :comments,
33
+ :spent_on,
34
+ :tyear,
35
+ :tmonth,
36
+ :tweek,
37
+ :visible?,
38
+ :updated_on,
39
+ :created_on,
40
+ :to => :@time_entry,
41
+ allow_nil: true
42
+
43
+ def initialize(time_entry)
44
+ @time_entry = time_entry
45
+ end
46
+
47
+ def user
48
+ @user ||= UserDrop.new(@time_entry.user)
49
+ end
50
+
51
+ def issue
52
+ @issue ||= IssueDrop.new(@time_entry.issue) unless @time_entry.issue.blank?
53
+ end
54
+
55
+ def activity
56
+ @activity ||= @time_entry.activity && @time_entry.activity.name
57
+ end
58
+
59
+ def custom_field_values
60
+ @time_entry.custom_field_values
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,68 @@
1
+ module Redmineup
2
+ module Liquid
3
+ class UsersDrop < ::Liquid::Drop
4
+
5
+ def self.default_drop
6
+ self.new User.sorted
7
+ end
8
+
9
+ def initialize(users)
10
+ @users = users
11
+ end
12
+
13
+ def before_method(login)
14
+ user = @users.where(:login => login).first || User.new
15
+ UserDrop.new user
16
+ end
17
+
18
+ def current
19
+ UserDrop.new User.current
20
+ end
21
+
22
+ def all
23
+ @all ||= @users.map do |user|
24
+ UserDrop.new user
25
+ end
26
+ end
27
+
28
+ def each(&block)
29
+ all.each(&block)
30
+ end
31
+
32
+ def size
33
+ @users.size
34
+ end
35
+ end
36
+
37
+ class UserDrop < ::Liquid::Drop
38
+ delegate :id, :name, :firstname, :lastname, :mail, :active?, :admin?, :logged?, :language, :to => :@user, allow_nil: true
39
+
40
+ def initialize(user)
41
+ @user = user
42
+ end
43
+
44
+ def avatar
45
+ ApplicationController.helpers.avatar(@user)
46
+ end
47
+
48
+ def permissions
49
+ roles = @user.memberships.collect { |m| m.roles }.flatten.uniq
50
+ roles << (@user.logged? ? Role.non_member : Role.anonymous)
51
+ roles.map(&:permissions).flatten.uniq.map(&:to_s)
52
+ end
53
+
54
+ def groups
55
+ @user.groups.map(&:name)
56
+ end
57
+
58
+ def projects
59
+ ProjectsDrop.new @user.memberships.map(&:project).flatten.select(&:visible?).uniq
60
+ end
61
+
62
+ def custom_field_values
63
+ @user.custom_field_values
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,254 @@
1
+ module Redmineup
2
+ module Liquid
3
+ module Filters
4
+ module Arrays
5
+
6
+ # Get the first element of the passed in array
7
+ #
8
+ # Example:
9
+ # {{ product.images | first | to_img }}
10
+ #
11
+ # {{ product.images | first: 3 }}
12
+ #
13
+ def first(array, count=1)
14
+ (count > 1 ? array.first(count) : array.first) if array.respond_to?(:first)
15
+ end
16
+
17
+ # Convert the input into json string
18
+ #
19
+ # input - The Array or Hash to be converted
20
+ #
21
+ # Returns the converted json string
22
+ def jsonify(input)
23
+ as_liquid(input).to_json
24
+ end
25
+
26
+ # Group an array of items by a property
27
+ #
28
+ # input - the inputted Enumerable
29
+ # property - the property
30
+ #
31
+ # Returns an array of Hashes, each looking something like this:
32
+ # {"name" => "larry"
33
+ # "items" => [...] } # all the items where `property` == "larry"
34
+ def group_by(input, property)
35
+ if groupable?(input)
36
+ input.group_by { |item| item_property(item, property).to_s }.each_with_object([]) do |item, array|
37
+ array << {
38
+ "name" => item.first,
39
+ "items" => item.last,
40
+ "size" => item.last.size
41
+ }
42
+ end
43
+ else
44
+ input
45
+ end
46
+ end
47
+
48
+ # Filter an array of objects
49
+ #
50
+ # input - the object array
51
+ # property - property within each object to filter by
52
+ # value - desired value
53
+ #
54
+ # Returns the filtered array of objects
55
+ def where(input, property, value, operator='==')
56
+ return input unless input.respond_to?(:select)
57
+ input = input.values if input.is_a?(Hash)
58
+ if operator == '=='
59
+ input.select do |object|
60
+ Array(item_property(object, property)).map(&:to_s).include?(value.to_s)
61
+ end || []
62
+ elsif operator == '<>'
63
+ input.select do |object|
64
+ !Array(item_property(object, property)).map(&:to_s).include?(value.to_s)
65
+ end || []
66
+ elsif operator == '>'
67
+ input.select do |object|
68
+ item_property_value = item_property(object, property)
69
+ item_property_value && item_property_value > value
70
+ end || []
71
+ elsif operator == '<'
72
+ input.select do |object|
73
+ item_property_value = item_property(object, property)
74
+ item_property_value && item_property_value < value
75
+ end || []
76
+ elsif operator == 'match'
77
+ input.select do |object|
78
+ Array(item_property(object, property)).map(&:to_s).any?{|i| i.match(value.to_s)}
79
+ end || []
80
+ elsif operator == 'any'
81
+ input.select do |object|
82
+ item_property(object, property).present?
83
+ end || []
84
+ elsif operator == 'none'
85
+ input.select do |object|
86
+ item_property(object, property).blank?
87
+ end || []
88
+ else
89
+ []
90
+ end
91
+ end
92
+
93
+ # Filters an array of objects against an expression
94
+ #
95
+ # input - the object array
96
+ # variable - the variable to assign each item to in the expression
97
+ # expression - a Liquid comparison expression passed in as a string
98
+ #
99
+ # Returns the filtered array of objects
100
+ def where_exp(input, variable, expression)
101
+ return input unless input.respond_to?(:select)
102
+
103
+ input = input.values if input.is_a?(Hash) # FIXME
104
+
105
+ condition = parse_condition(expression)
106
+ @context.stack do
107
+ input.select do |object|
108
+ @context[variable] = object
109
+ condition.evaluate(@context)
110
+ end
111
+ end || []
112
+ end
113
+
114
+ # Filter an array of objects by tags
115
+ #
116
+ # input - the object array
117
+ # tags - quoted tags list divided by comma
118
+ # match - (all- defaut, any, exclude)
119
+ #
120
+ # Returns the filtered array of objects
121
+ def tagged_with(input, tags, match='all')
122
+ return input unless input.respond_to?(:select)
123
+ input = input.values if input.is_a?(Hash)
124
+ tag_list = tags.is_a?(Array) ? tags.sort : tags.split(',').map(&:strip).sort
125
+ case match
126
+ when "all"
127
+ input.select do |object|
128
+ object.respond_to?(:tag_list) &&
129
+ (tag_list - Array(item_property(object, 'tag_list')).map(&:to_s).sort).empty?
130
+ end || []
131
+ when "any"
132
+ input.select do |object|
133
+ object.respond_to?(:tag_list) &&
134
+ (tag_list & Array(item_property(object, 'tag_list')).map(&:to_s).sort).any?
135
+ end || []
136
+ when "exclude"
137
+ input.select do |object|
138
+ object.respond_to?(:tag_list) &&
139
+ (tag_list & Array(item_property(object, 'tag_list')).map(&:to_s).sort).empty?
140
+ end || []
141
+ else
142
+ []
143
+ end
144
+ end
145
+
146
+ # Sort an array of objects
147
+ #
148
+ # input - the object array
149
+ # property - property within each object to filter by
150
+ # nils ('first' | 'last') - nils appear before or after non-nil values
151
+ #
152
+ # Returns the filtered array of objects
153
+ def sort(input, property = nil, nils = "first")
154
+ if input.nil?
155
+ raise ArgumentError, "Cannot sort a null object."
156
+ end
157
+ if property.nil?
158
+ input.sort
159
+ else
160
+ if nils == "first"
161
+ order = - 1
162
+ elsif nils == "last"
163
+ order = + 1
164
+ else
165
+ raise ArgumentError, "Invalid nils order: " \
166
+ "'#{nils}' is not a valid nils order. It must be 'first' or 'last'."
167
+ end
168
+
169
+ sort_input(input, property, order)
170
+ end
171
+ end
172
+
173
+ def pop(array, input = 1)
174
+ return array unless array.is_a?(Array)
175
+ new_ary = array.dup
176
+ new_ary.pop(input.to_i || 1)
177
+ new_ary
178
+ end
179
+
180
+ def push(array, input)
181
+ return array unless array.is_a?(Array)
182
+ new_ary = array.dup
183
+ new_ary.push(input)
184
+ new_ary
185
+ end
186
+
187
+ def shift(array, input = 1)
188
+ return array unless array.is_a?(Array)
189
+ new_ary = array.dup
190
+ new_ary.shift(input.to_i || 1)
191
+ new_ary
192
+ end
193
+
194
+ def unshift(array, input)
195
+ return array unless array.is_a?(Array)
196
+ new_ary = array.dup
197
+ new_ary.unshift(input)
198
+ new_ary
199
+ end
200
+
201
+ private
202
+
203
+ # Parse a string to a Liquid Condition
204
+ def parse_condition(exp)
205
+ parser = ::Liquid::Parser.new(exp)
206
+ condition = parse_binary_comparison(parser)
207
+
208
+ parser.consume(:end_of_string)
209
+ condition
210
+ end
211
+
212
+ # Generate a Liquid::Condition object from a Liquid::Parser object additionally processing
213
+ # the parsed expression based on whether the expression consists of binary operations with
214
+ # Liquid operators `and` or `or`
215
+ #
216
+ # - parser: an instance of Liquid::Parser
217
+ #
218
+ # Returns an instance of Liquid::Condition
219
+ def parse_binary_comparison(parser)
220
+ condition = parse_comparison(parser)
221
+ first_condition = condition
222
+ while (binary_operator = parser.id?("and") || parser.id?("or"))
223
+ child_condition = parse_comparison(parser)
224
+ condition.send(binary_operator, child_condition)
225
+ condition =where_exp child_condition
226
+ end
227
+ first_condition
228
+ end
229
+
230
+ # Generates a Liquid::Condition object from a Liquid::Parser object based on whether the parsed
231
+ # expression involves a "comparison" operator (e.g. <, ==, >, !=, etc)
232
+ #
233
+ # - parser: an instance of Liquid::Parser
234
+ #
235
+ # Returns an instance of Liquid::Condition
236
+ def parse_comparison(parser)
237
+ left_operand = ::Liquid::Expression.parse(parser.expression)
238
+ operator = parser.consume?(:comparison)
239
+
240
+ # No comparison-operator detected. Initialize a Liquid::Condition using only left operand
241
+ return ::Liquid::Condition.new(left_operand) unless operator
242
+
243
+ # Parse what remained after extracting the left operand and the `:comparison` operator
244
+ # and initialize a Liquid::Condition object using the operands and the comparison-operator
245
+ ::Liquid::Condition.new(left_operand, operator, ::Liquid::Expression.parse(parser.expression))
246
+ end
247
+
248
+
249
+ end # module ArrayFilters
250
+ end
251
+ end
252
+
253
+ ::Liquid::Template.register_filter(Redmineup::Liquid::Filters::Arrays)
254
+ end
@@ -0,0 +1,249 @@
1
+ require 'uri'
2
+ require 'rack'
3
+ require 'json'
4
+ require 'date'
5
+ require 'liquid'
6
+
7
+ module Redmineup
8
+ module Liquid
9
+ module Filters
10
+ module Base
11
+ include Redmineup::MoneyHelper
12
+
13
+ def textilize(input)
14
+ RedCloth3.new(input).to_html
15
+ end
16
+
17
+ def default(input, value)
18
+ input.blank? ? value : input
19
+ end
20
+
21
+ def underscore(input)
22
+ input.to_s.gsub(' ', '_').gsub('/', '_').underscore
23
+ end
24
+
25
+ def dasherize(input)
26
+ input.to_s.gsub(' ', '-').gsub('/', '-').dasherize
27
+ end
28
+
29
+ def shuffle(array)
30
+ array.to_a.shuffle
31
+ end
32
+
33
+ def random(input)
34
+ rand(input.to_i)
35
+ end
36
+
37
+ def md5(input)
38
+ Digest::MD5.hexdigest(input) unless input.blank?
39
+ end
40
+
41
+ # example:
42
+ # {{ "http:://www.example.com?key=hello world" | encode }}
43
+ #
44
+ # => http%3A%3A%2F%2Fwww.example.com%3Fkey%3Dhello+world
45
+ def encode(input)
46
+ ::Rack::Utils.escape(input)
47
+ end
48
+
49
+ # example:
50
+ # {{ today | plus_days: 2 }}
51
+ def plus_days(input, distanse)
52
+ return '' if input.nil?
53
+ days = distanse.to_i
54
+ input.to_date + days.days rescue 'Invalid date'
55
+ end
56
+
57
+ # example:
58
+ # {{ today | date_range: '2015-12-12' }}
59
+ def date_range(input, distanse)
60
+ return '' if input.nil?
61
+ (input.to_date - distanse.to_date).to_i rescue 'Invalid date'
62
+ end
63
+
64
+ # example:
65
+ # {{ now | utc }}
66
+ def utc(input)
67
+ return '' if input.nil?
68
+ input.to_time.utc rescue 'Invalid date'
69
+ end
70
+
71
+ def modulo(input, operand)
72
+ apply_operation(input, operand, :%)
73
+ end
74
+
75
+ def round(input, n = 0)
76
+ result = to_number(input).round(to_number(n))
77
+ result = result.to_f if result.is_a?(BigDecimal)
78
+ result = result.to_i if n == 0
79
+ result
80
+ end
81
+
82
+ def ceil(input)
83
+ to_number(input).ceil.to_i
84
+ end
85
+
86
+ def floor(input)
87
+ to_number(input).floor.to_i
88
+ end
89
+
90
+ def currency(input, currency_code = nil)
91
+ price_to_currency(input, currency_code || container_currency, :converted => false)
92
+ end
93
+
94
+ def call_method(input, method_name)
95
+ if input.respond_to?(method_name)
96
+ input.method(method_name).call
97
+ end
98
+ end
99
+
100
+ def custom_field(input, field_name)
101
+ if input.respond_to?(:custom_field_values)
102
+ 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
104
+ end
105
+ end
106
+
107
+ def custom_fields(input)
108
+ if input.respond_to?(:custom_field_values)
109
+ input.custom_field_values.map { |cfv| cfv.custom_field.name }
110
+ end
111
+ end
112
+
113
+ def attachment(input, file_name)
114
+ if input.respond_to?(:attachments)
115
+ if input.attachments.is_a?(Hash)
116
+ attachment = input.attachments[file_name]
117
+ else
118
+ attachment = input.attachments.detect{|a| a.file_name == file_name}
119
+ end
120
+ AttachmentDrop.new attachment if attachment
121
+ end
122
+ end
123
+
124
+ def multi_line(input)
125
+ input.to_s.gsub("\n", '<br/>').html_safe
126
+ end
127
+
128
+ def concat(input, *args)
129
+ result = input.to_s
130
+ args.flatten.each { |a| result << a.to_s }
131
+ result
132
+ end
133
+
134
+ # right justify and padd a string
135
+ def rjust(input, integer, padstr = '')
136
+ input.to_s.rjust(integer, padstr)
137
+ end
138
+
139
+ # left justify and padd a string
140
+ def ljust(input, integer, padstr = '')
141
+ input.to_s.ljust(integer, padstr)
142
+ end
143
+
144
+ def textile(input)
145
+ ::RedCloth3.new(input).to_html
146
+ end
147
+
148
+ protected
149
+
150
+ # Convert an array of properties ('key:value') into a hash
151
+ # Ex: ['width:50', 'height:100'] => { :width => '50', :height => '100' }
152
+ def args_to_options(*args)
153
+ options = {}
154
+ args.flatten.each do |a|
155
+ if (a =~ /^(.*):(.*)$/)
156
+ options[$1.to_sym] = $2
157
+ end
158
+ end
159
+ options
160
+ end
161
+
162
+ # Write options (Hash) into a string according to the following pattern:
163
+ # <key1>="<value1>", <key2>="<value2", ...etc
164
+ def inline_options(options = {})
165
+ return '' if options.empty?
166
+ (options.stringify_keys.sort.to_a.collect { |a, b| "#{a}=\"#{b}\"" }).join(' ') << ' '
167
+ end
168
+
169
+ def sort_input(input, property, order)
170
+ input.sort do |apple, orange|
171
+ apple_property = item_property(apple, property)
172
+ orange_property = item_property(orange, property)
173
+
174
+ if !apple_property.nil? && orange_property.nil?
175
+ - order
176
+ elsif apple_property.nil? && !orange_property.nil?
177
+ + order
178
+ else
179
+ apple_property <=> orange_property
180
+ end
181
+ end
182
+ end
183
+
184
+ def time(input)
185
+ case input
186
+ when Time
187
+ input.clone
188
+ when Date
189
+ input.to_time
190
+ when String
191
+ Time.parse(input) rescue Time.at(input.to_i)
192
+ when Numeric
193
+ Time.at(input)
194
+ else
195
+ raise Errors::InvalidDateError,
196
+ "Invalid Date: '#{input.inspect}' is not a valid datetime."
197
+ end.localtime
198
+ end
199
+
200
+ def groupable?(element)
201
+ element.respond_to?(:group_by)
202
+ end
203
+
204
+ def item_property(item, property)
205
+ if item.respond_to?(:to_liquid)
206
+ property.to_s.split(".").reduce(item.to_liquid) do |subvalue, attribute|
207
+ subvalue[attribute]
208
+ end
209
+ elsif item.respond_to?(:data)
210
+ item.data[property.to_s]
211
+ else
212
+ item[property.to_s]
213
+ end
214
+ end
215
+
216
+ def as_liquid(item)
217
+ case item
218
+ when Hash
219
+ pairs = item.map { |k, v| as_liquid([k, v]) }
220
+ Hash[pairs]
221
+ when Array
222
+ item.map { |i| as_liquid(i) }
223
+ else
224
+ if item.respond_to?(:to_liquid)
225
+ liquidated = item.to_liquid
226
+ # prevent infinite recursion for simple types (which return `self`)
227
+ if liquidated == item
228
+ item
229
+ else
230
+ as_liquid(liquidated)
231
+ end
232
+ else
233
+ item
234
+ end
235
+ end
236
+ end
237
+
238
+ def container
239
+ @container ||= @context.registers[:container]
240
+ end
241
+
242
+ def container_currency
243
+ container.currency if container.respond_to?(:currency)
244
+ end
245
+ end
246
+ ::Liquid::Template.register_filter(Redmineup::Liquid::Filters::Base)
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,31 @@
1
+ require 'liquid'
2
+
3
+ module Redmineup
4
+ module Liquid
5
+ module Filters
6
+ module Colors
7
+ def darken_color(input, value=0.4)
8
+ Redmineup::ColorsHelper.darken_color(input, value.to_f)
9
+ end
10
+
11
+ def lighten_color(input, value=0.6)
12
+ Redmineup::ColorsHelper.lighten_color(input, value.to_f)
13
+ end
14
+
15
+ def contrasting_text_color(input)
16
+ Redmineup::ColorsHelper.contrasting_text_color(input)
17
+ end
18
+
19
+ def hex_color(input)
20
+ Redmineup::ColorsHelper.hex_color(input)
21
+ end
22
+
23
+ def convert_to_brightness_value(input)
24
+ Redmineup::ColorsHelper.convert_to_brightness_value(input)
25
+ end
26
+
27
+ end
28
+ ::Liquid::Template.register_filter(Redmineup::Liquid::Filters::Colors)
29
+ end
30
+ end
31
+ end