redmineup 1.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/Gemfile +4 -0
- data/README.md +204 -0
- data/Rakefile +11 -0
- data/app/controllers/redmineup_controller.rb +26 -0
- data/app/views/redmine_crm/redmineup_calendar/_calendar.html.erb +34 -0
- data/app/views/redmineup/_money.html.erb +44 -0
- data/app/views/redmineup/settings.html.erb +10 -0
- data/bitbucket-pipelines.yml +50 -0
- data/config/currency_iso.json +2544 -0
- data/config/locales/cs.yml +13 -0
- data/config/locales/de.yml +13 -0
- data/config/locales/en.yml +13 -0
- data/config/locales/es.yml +13 -0
- data/config/locales/ru.yml +13 -0
- data/config/routes.rb +5 -0
- data/doc/CHANGELOG +14 -0
- data/doc/LICENSE.txt +339 -0
- data/lib/redmineup/acts_as_draftable/draft.rb +40 -0
- data/lib/redmineup/acts_as_draftable/up_acts_as_draftable.rb +172 -0
- data/lib/redmineup/acts_as_list/list.rb +282 -0
- data/lib/redmineup/acts_as_priceable/up_acts_as_priceable.rb +33 -0
- data/lib/redmineup/acts_as_taggable/tag.rb +81 -0
- data/lib/redmineup/acts_as_taggable/tag_list.rb +111 -0
- data/lib/redmineup/acts_as_taggable/tagging.rb +16 -0
- data/lib/redmineup/acts_as_taggable/up_acts_as_taggable.rb +357 -0
- data/lib/redmineup/acts_as_viewed/up_acts_as_viewed.rb +274 -0
- data/lib/redmineup/acts_as_votable/up_acts_as_votable.rb +80 -0
- data/lib/redmineup/acts_as_votable/up_acts_as_voter.rb +20 -0
- data/lib/redmineup/acts_as_votable/votable.rb +323 -0
- data/lib/redmineup/acts_as_votable/vote.rb +28 -0
- data/lib/redmineup/acts_as_votable/voter.rb +131 -0
- data/lib/redmineup/assets_manager.rb +43 -0
- data/lib/redmineup/colors_helper.rb +192 -0
- data/lib/redmineup/compatibility/application_controller_patch.rb +33 -0
- data/lib/redmineup/compatibility/routing_mapper_patch.rb +25 -0
- data/lib/redmineup/currency/formatting.rb +224 -0
- data/lib/redmineup/currency/heuristics.rb +151 -0
- data/lib/redmineup/currency/loader.rb +23 -0
- data/lib/redmineup/currency.rb +450 -0
- data/lib/redmineup/engine.rb +4 -0
- data/lib/redmineup/helpers/external_assets_helper.rb +20 -0
- data/lib/redmineup/helpers/form_tag_helper.rb +88 -0
- data/lib/redmineup/helpers/rup_calendar_helper.rb +146 -0
- data/lib/redmineup/helpers/tags_helper.rb +13 -0
- data/lib/redmineup/helpers/vote_helper.rb +35 -0
- data/lib/redmineup/hooks/views_layouts_hook.rb +11 -0
- data/lib/redmineup/liquid/drops/attachment_drop.rb +47 -0
- data/lib/redmineup/liquid/drops/issue_relations_drop.rb +41 -0
- data/lib/redmineup/liquid/drops/issues_drop.rb +217 -0
- data/lib/redmineup/liquid/drops/news_drop.rb +54 -0
- data/lib/redmineup/liquid/drops/projects_drop.rb +86 -0
- data/lib/redmineup/liquid/drops/time_entries_drop.rb +65 -0
- data/lib/redmineup/liquid/drops/users_drop.rb +68 -0
- data/lib/redmineup/liquid/filters/arrays.rb +254 -0
- data/lib/redmineup/liquid/filters/base.rb +249 -0
- data/lib/redmineup/liquid/filters/colors.rb +31 -0
- data/lib/redmineup/money_helper.rb +66 -0
- data/lib/redmineup/patches/liquid_patch.rb +33 -0
- data/lib/redmineup/settings/money.rb +46 -0
- data/lib/redmineup/settings.rb +53 -0
- data/lib/redmineup/version.rb +3 -0
- data/lib/redmineup.rb +108 -0
- data/redmineup.gemspec +29 -0
- data/test/acts_as_draftable/draft_test.rb +29 -0
- data/test/acts_as_draftable/rup_acts_as_draftable_test.rb +178 -0
- data/test/acts_as_taggable/rup_acts_as_taggable_test.rb +350 -0
- data/test/acts_as_taggable/tag_list_test.rb +34 -0
- data/test/acts_as_taggable/tag_test.rb +72 -0
- data/test/acts_as_taggable/tagging_test.rb +15 -0
- data/test/acts_as_viewed/rup_acts_as_viewed_test.rb +47 -0
- data/test/acts_as_votable/rup_acts_as_votable_test.rb +19 -0
- data/test/acts_as_votable/rup_acts_as_voter_test.rb +14 -0
- data/test/acts_as_votable/votable_test.rb +507 -0
- data/test/acts_as_votable/voter_test.rb +296 -0
- data/test/currency_test.rb +292 -0
- data/test/database.yml +17 -0
- data/test/fixtures/attachments.yml +14 -0
- data/test/fixtures/issues.yml +24 -0
- data/test/fixtures/news.yml +8 -0
- data/test/fixtures/projects.yml +10 -0
- data/test/fixtures/taggings.yml +32 -0
- data/test/fixtures/tags.yml +11 -0
- data/test/fixtures/users.yml +9 -0
- data/test/fixtures/votable_caches.yml +2 -0
- data/test/fixtures/votables.yml +4 -0
- data/test/fixtures/voters.yml +6 -0
- data/test/liquid/drops/attachment_drop_test.rb +15 -0
- data/test/liquid/drops/issue_relations_drop_test.rb +24 -0
- data/test/liquid/drops/issues_drop_test.rb +38 -0
- data/test/liquid/drops/news_drop_test.rb +38 -0
- data/test/liquid/drops/projects_drop_test.rb +44 -0
- data/test/liquid/drops/uses_drop_test.rb +36 -0
- data/test/liquid/filters/arrays_filter_test.rb +31 -0
- data/test/liquid/filters/base_filter_test.rb +67 -0
- data/test/liquid/filters/colors_filter_test.rb +33 -0
- data/test/liquid/liquid_helper.rb +34 -0
- data/test/models/attachment.rb +3 -0
- data/test/models/issue.rb +21 -0
- data/test/models/issue_relation.rb +10 -0
- data/test/models/news.rb +3 -0
- data/test/models/project.rb +8 -0
- data/test/models/user.rb +11 -0
- data/test/models/vote_classes.rb +33 -0
- data/test/money_helper_test.rb +12 -0
- data/test/schema.rb +144 -0
- data/test/tags_helper_test.rb +29 -0
- data/test/test_helper.rb +66 -0
- data/test/vote_helper_test.rb +28 -0
- data/vendor/assets/images/money.png +0 -0
- data/vendor/assets/images/vcard.png +0 -0
- data/vendor/assets/javascripts/Chart.bundle.min.js +16 -0
- data/vendor/assets/javascripts/select2.js +2 -0
- data/vendor/assets/javascripts/select2_helpers.js +192 -0
- data/vendor/assets/stylesheets/money.css +96 -0
- data/vendor/assets/stylesheets/select2.css +424 -0
- 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
|