##// END OF EJS Templates
Rescue Query::StatementInvalid in TimelogController....
Jean-Philippe Lang -
r10746:aba07a860f95
parent child
Show More
@@ -1,350 +1,352
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20
20
21 before_filter :find_project_for_new_time_entry, :only => [:create]
21 before_filter :find_project_for_new_time_entry, :only => [:create]
22 before_filter :find_time_entry, :only => [:show, :edit, :update]
22 before_filter :find_time_entry, :only => [:show, :edit, :update]
23 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
24 before_filter :authorize, :except => [:new, :index, :report]
24 before_filter :authorize, :except => [:new, :index, :report]
25
25
26 before_filter :find_optional_project, :only => [:index, :report]
26 before_filter :find_optional_project, :only => [:index, :report]
27 before_filter :find_optional_project_for_new_time_entry, :only => [:new]
27 before_filter :find_optional_project_for_new_time_entry, :only => [:new]
28 before_filter :authorize_global, :only => [:new, :index, :report]
28 before_filter :authorize_global, :only => [:new, :index, :report]
29
29
30 accept_rss_auth :index
30 accept_rss_auth :index
31 accept_api_auth :index, :show, :create, :update, :destroy
31 accept_api_auth :index, :show, :create, :update, :destroy
32
32
33 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
34
33 helper :sort
35 helper :sort
34 include SortHelper
36 include SortHelper
35 helper :issues
37 helper :issues
36 include TimelogHelper
38 include TimelogHelper
37 helper :custom_fields
39 helper :custom_fields
38 include CustomFieldsHelper
40 include CustomFieldsHelper
39 helper :queries
41 helper :queries
40
42
41 def index
43 def index
42 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
44 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
43 scope = time_entry_scope
45 scope = time_entry_scope
44
46
45 sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
47 sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
46 sort_update(@query.sortable_columns)
48 sort_update(@query.sortable_columns)
47
49
48 respond_to do |format|
50 respond_to do |format|
49 format.html {
51 format.html {
50 # Paginate results
52 # Paginate results
51 @entry_count = scope.count
53 @entry_count = scope.count
52 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
54 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
53 @entries = scope.all(
55 @entries = scope.all(
54 :include => [:project, :activity, :user, {:issue => :tracker}],
56 :include => [:project, :activity, :user, {:issue => :tracker}],
55 :order => sort_clause,
57 :order => sort_clause,
56 :limit => @entry_pages.items_per_page,
58 :limit => @entry_pages.items_per_page,
57 :offset => @entry_pages.current.offset
59 :offset => @entry_pages.current.offset
58 )
60 )
59 @total_hours = scope.sum(:hours).to_f
61 @total_hours = scope.sum(:hours).to_f
60
62
61 render :layout => !request.xhr?
63 render :layout => !request.xhr?
62 }
64 }
63 format.api {
65 format.api {
64 @entry_count = scope.count
66 @entry_count = scope.count
65 @offset, @limit = api_offset_and_limit
67 @offset, @limit = api_offset_and_limit
66 @entries = scope.all(
68 @entries = scope.all(
67 :include => [:project, :activity, :user, {:issue => :tracker}],
69 :include => [:project, :activity, :user, {:issue => :tracker}],
68 :order => sort_clause,
70 :order => sort_clause,
69 :limit => @limit,
71 :limit => @limit,
70 :offset => @offset
72 :offset => @offset
71 )
73 )
72 }
74 }
73 format.atom {
75 format.atom {
74 entries = scope.all(
76 entries = scope.all(
75 :include => [:project, :activity, :user, {:issue => :tracker}],
77 :include => [:project, :activity, :user, {:issue => :tracker}],
76 :order => "#{TimeEntry.table_name}.created_on DESC",
78 :order => "#{TimeEntry.table_name}.created_on DESC",
77 :limit => Setting.feeds_limit.to_i
79 :limit => Setting.feeds_limit.to_i
78 )
80 )
79 render_feed(entries, :title => l(:label_spent_time))
81 render_feed(entries, :title => l(:label_spent_time))
80 }
82 }
81 format.csv {
83 format.csv {
82 # Export all entries
84 # Export all entries
83 @entries = scope.all(
85 @entries = scope.all(
84 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
86 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
85 :order => sort_clause
87 :order => sort_clause
86 )
88 )
87 send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
89 send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
88 }
90 }
89 end
91 end
90 end
92 end
91
93
92 def report
94 def report
93 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
95 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
94 scope = time_entry_scope
96 scope = time_entry_scope
95
97
96 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
98 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
97
99
98 respond_to do |format|
100 respond_to do |format|
99 format.html { render :layout => !request.xhr? }
101 format.html { render :layout => !request.xhr? }
100 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
102 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
101 end
103 end
102 end
104 end
103
105
104 def show
106 def show
105 respond_to do |format|
107 respond_to do |format|
106 # TODO: Implement html response
108 # TODO: Implement html response
107 format.html { render :nothing => true, :status => 406 }
109 format.html { render :nothing => true, :status => 406 }
108 format.api
110 format.api
109 end
111 end
110 end
112 end
111
113
112 def new
114 def new
113 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
115 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
114 @time_entry.safe_attributes = params[:time_entry]
116 @time_entry.safe_attributes = params[:time_entry]
115 end
117 end
116
118
117 def create
119 def create
118 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
120 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
119 @time_entry.safe_attributes = params[:time_entry]
121 @time_entry.safe_attributes = params[:time_entry]
120
122
121 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
123 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
122
124
123 if @time_entry.save
125 if @time_entry.save
124 respond_to do |format|
126 respond_to do |format|
125 format.html {
127 format.html {
126 flash[:notice] = l(:notice_successful_create)
128 flash[:notice] = l(:notice_successful_create)
127 if params[:continue]
129 if params[:continue]
128 if params[:project_id]
130 if params[:project_id]
129 redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue,
131 redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue,
130 :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
132 :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
131 :back_url => params[:back_url]
133 :back_url => params[:back_url]
132 else
134 else
133 redirect_to :action => 'new',
135 redirect_to :action => 'new',
134 :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
136 :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
135 :back_url => params[:back_url]
137 :back_url => params[:back_url]
136 end
138 end
137 else
139 else
138 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
140 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
139 end
141 end
140 }
142 }
141 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
143 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
142 end
144 end
143 else
145 else
144 respond_to do |format|
146 respond_to do |format|
145 format.html { render :action => 'new' }
147 format.html { render :action => 'new' }
146 format.api { render_validation_errors(@time_entry) }
148 format.api { render_validation_errors(@time_entry) }
147 end
149 end
148 end
150 end
149 end
151 end
150
152
151 def edit
153 def edit
152 @time_entry.safe_attributes = params[:time_entry]
154 @time_entry.safe_attributes = params[:time_entry]
153 end
155 end
154
156
155 def update
157 def update
156 @time_entry.safe_attributes = params[:time_entry]
158 @time_entry.safe_attributes = params[:time_entry]
157
159
158 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
160 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
159
161
160 if @time_entry.save
162 if @time_entry.save
161 respond_to do |format|
163 respond_to do |format|
162 format.html {
164 format.html {
163 flash[:notice] = l(:notice_successful_update)
165 flash[:notice] = l(:notice_successful_update)
164 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
166 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
165 }
167 }
166 format.api { render_api_ok }
168 format.api { render_api_ok }
167 end
169 end
168 else
170 else
169 respond_to do |format|
171 respond_to do |format|
170 format.html { render :action => 'edit' }
172 format.html { render :action => 'edit' }
171 format.api { render_validation_errors(@time_entry) }
173 format.api { render_validation_errors(@time_entry) }
172 end
174 end
173 end
175 end
174 end
176 end
175
177
176 def bulk_edit
178 def bulk_edit
177 @available_activities = TimeEntryActivity.shared.active
179 @available_activities = TimeEntryActivity.shared.active
178 @custom_fields = TimeEntry.first.available_custom_fields
180 @custom_fields = TimeEntry.first.available_custom_fields
179 end
181 end
180
182
181 def bulk_update
183 def bulk_update
182 attributes = parse_params_for_bulk_time_entry_attributes(params)
184 attributes = parse_params_for_bulk_time_entry_attributes(params)
183
185
184 unsaved_time_entry_ids = []
186 unsaved_time_entry_ids = []
185 @time_entries.each do |time_entry|
187 @time_entries.each do |time_entry|
186 time_entry.reload
188 time_entry.reload
187 time_entry.safe_attributes = attributes
189 time_entry.safe_attributes = attributes
188 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
190 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
189 unless time_entry.save
191 unless time_entry.save
190 # Keep unsaved time_entry ids to display them in flash error
192 # Keep unsaved time_entry ids to display them in flash error
191 unsaved_time_entry_ids << time_entry.id
193 unsaved_time_entry_ids << time_entry.id
192 end
194 end
193 end
195 end
194 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
196 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
195 redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
197 redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
196 end
198 end
197
199
198 def destroy
200 def destroy
199 destroyed = TimeEntry.transaction do
201 destroyed = TimeEntry.transaction do
200 @time_entries.each do |t|
202 @time_entries.each do |t|
201 unless t.destroy && t.destroyed?
203 unless t.destroy && t.destroyed?
202 raise ActiveRecord::Rollback
204 raise ActiveRecord::Rollback
203 end
205 end
204 end
206 end
205 end
207 end
206
208
207 respond_to do |format|
209 respond_to do |format|
208 format.html {
210 format.html {
209 if destroyed
211 if destroyed
210 flash[:notice] = l(:notice_successful_delete)
212 flash[:notice] = l(:notice_successful_delete)
211 else
213 else
212 flash[:error] = l(:notice_unable_delete_time_entry)
214 flash[:error] = l(:notice_unable_delete_time_entry)
213 end
215 end
214 redirect_back_or_default(:action => 'index', :project_id => @projects.first)
216 redirect_back_or_default(:action => 'index', :project_id => @projects.first)
215 }
217 }
216 format.api {
218 format.api {
217 if destroyed
219 if destroyed
218 render_api_ok
220 render_api_ok
219 else
221 else
220 render_validation_errors(@time_entries)
222 render_validation_errors(@time_entries)
221 end
223 end
222 }
224 }
223 end
225 end
224 end
226 end
225
227
226 private
228 private
227 def find_time_entry
229 def find_time_entry
228 @time_entry = TimeEntry.find(params[:id])
230 @time_entry = TimeEntry.find(params[:id])
229 unless @time_entry.editable_by?(User.current)
231 unless @time_entry.editable_by?(User.current)
230 render_403
232 render_403
231 return false
233 return false
232 end
234 end
233 @project = @time_entry.project
235 @project = @time_entry.project
234 rescue ActiveRecord::RecordNotFound
236 rescue ActiveRecord::RecordNotFound
235 render_404
237 render_404
236 end
238 end
237
239
238 def find_time_entries
240 def find_time_entries
239 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
241 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
240 raise ActiveRecord::RecordNotFound if @time_entries.empty?
242 raise ActiveRecord::RecordNotFound if @time_entries.empty?
241 @projects = @time_entries.collect(&:project).compact.uniq
243 @projects = @time_entries.collect(&:project).compact.uniq
242 @project = @projects.first if @projects.size == 1
244 @project = @projects.first if @projects.size == 1
243 rescue ActiveRecord::RecordNotFound
245 rescue ActiveRecord::RecordNotFound
244 render_404
246 render_404
245 end
247 end
246
248
247 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
249 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
248 if unsaved_time_entry_ids.empty?
250 if unsaved_time_entry_ids.empty?
249 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
251 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
250 else
252 else
251 flash[:error] = l(:notice_failed_to_save_time_entries,
253 flash[:error] = l(:notice_failed_to_save_time_entries,
252 :count => unsaved_time_entry_ids.size,
254 :count => unsaved_time_entry_ids.size,
253 :total => time_entries.size,
255 :total => time_entries.size,
254 :ids => '#' + unsaved_time_entry_ids.join(', #'))
256 :ids => '#' + unsaved_time_entry_ids.join(', #'))
255 end
257 end
256 end
258 end
257
259
258 def find_optional_project_for_new_time_entry
260 def find_optional_project_for_new_time_entry
259 if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
261 if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
260 @project = Project.find(project_id)
262 @project = Project.find(project_id)
261 end
263 end
262 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
264 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
263 @issue = Issue.find(issue_id)
265 @issue = Issue.find(issue_id)
264 @project ||= @issue.project
266 @project ||= @issue.project
265 end
267 end
266 rescue ActiveRecord::RecordNotFound
268 rescue ActiveRecord::RecordNotFound
267 render_404
269 render_404
268 end
270 end
269
271
270 def find_project_for_new_time_entry
272 def find_project_for_new_time_entry
271 find_optional_project_for_new_time_entry
273 find_optional_project_for_new_time_entry
272 if @project.nil?
274 if @project.nil?
273 render_404
275 render_404
274 end
276 end
275 end
277 end
276
278
277 def find_optional_project
279 def find_optional_project
278 if !params[:issue_id].blank?
280 if !params[:issue_id].blank?
279 @issue = Issue.find(params[:issue_id])
281 @issue = Issue.find(params[:issue_id])
280 @project = @issue.project
282 @project = @issue.project
281 elsif !params[:project_id].blank?
283 elsif !params[:project_id].blank?
282 @project = Project.find(params[:project_id])
284 @project = Project.find(params[:project_id])
283 end
285 end
284 end
286 end
285
287
286 # Returns the TimeEntry scope for index and report actions
288 # Returns the TimeEntry scope for index and report actions
287 def time_entry_scope
289 def time_entry_scope
288 scope = TimeEntry.visible.where(@query.statement)
290 scope = TimeEntry.visible.where(@query.statement)
289 if @issue
291 if @issue
290 scope = scope.on_issue(@issue)
292 scope = scope.on_issue(@issue)
291 elsif @project
293 elsif @project
292 scope = scope.on_project(@project, Setting.display_subprojects_issues?)
294 scope = scope.on_project(@project, Setting.display_subprojects_issues?)
293 end
295 end
294 scope
296 scope
295 end
297 end
296
298
297 # Retrieves the date range based on predefined ranges or specific from/to param dates
299 # Retrieves the date range based on predefined ranges or specific from/to param dates
298 def retrieve_date_range
300 def retrieve_date_range
299 @free_period = false
301 @free_period = false
300 @from, @to = nil, nil
302 @from, @to = nil, nil
301
303
302 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
304 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
303 case params[:period].to_s
305 case params[:period].to_s
304 when 'today'
306 when 'today'
305 @from = @to = Date.today
307 @from = @to = Date.today
306 when 'yesterday'
308 when 'yesterday'
307 @from = @to = Date.today - 1
309 @from = @to = Date.today - 1
308 when 'current_week'
310 when 'current_week'
309 @from = Date.today - (Date.today.cwday - 1)%7
311 @from = Date.today - (Date.today.cwday - 1)%7
310 @to = @from + 6
312 @to = @from + 6
311 when 'last_week'
313 when 'last_week'
312 @from = Date.today - 7 - (Date.today.cwday - 1)%7
314 @from = Date.today - 7 - (Date.today.cwday - 1)%7
313 @to = @from + 6
315 @to = @from + 6
314 when 'last_2_weeks'
316 when 'last_2_weeks'
315 @from = Date.today - 14 - (Date.today.cwday - 1)%7
317 @from = Date.today - 14 - (Date.today.cwday - 1)%7
316 @to = @from + 13
318 @to = @from + 13
317 when '7_days'
319 when '7_days'
318 @from = Date.today - 7
320 @from = Date.today - 7
319 @to = Date.today
321 @to = Date.today
320 when 'current_month'
322 when 'current_month'
321 @from = Date.civil(Date.today.year, Date.today.month, 1)
323 @from = Date.civil(Date.today.year, Date.today.month, 1)
322 @to = (@from >> 1) - 1
324 @to = (@from >> 1) - 1
323 when 'last_month'
325 when 'last_month'
324 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
326 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
325 @to = (@from >> 1) - 1
327 @to = (@from >> 1) - 1
326 when '30_days'
328 when '30_days'
327 @from = Date.today - 30
329 @from = Date.today - 30
328 @to = Date.today
330 @to = Date.today
329 when 'current_year'
331 when 'current_year'
330 @from = Date.civil(Date.today.year, 1, 1)
332 @from = Date.civil(Date.today.year, 1, 1)
331 @to = Date.civil(Date.today.year, 12, 31)
333 @to = Date.civil(Date.today.year, 12, 31)
332 end
334 end
333 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
335 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
334 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
336 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
335 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
337 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
336 @free_period = true
338 @free_period = true
337 else
339 else
338 # default
340 # default
339 end
341 end
340
342
341 @from, @to = @to, @from if @from && @to && @from > @to
343 @from, @to = @to, @from if @from && @to && @from > @to
342 end
344 end
343
345
344 def parse_params_for_bulk_time_entry_attributes(params)
346 def parse_params_for_bulk_time_entry_attributes(params)
345 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
347 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
346 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
348 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
347 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
349 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
348 attributes
350 attributes
349 end
351 end
350 end
352 end
General Comments 0
You need to be logged in to leave comments. Login now