Description:
merge with master
Commit status:
[Not Reviewed]
References:
merge java
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r665:ecbd1a5dbc27 - - 7 files changed: 108 inserted, 8 deleted

@@ -0,0 +1,34
1 + .container-fluid
2 + %h1 Editing announcement
3 + = error_messages_for :announcement
4 + .row
5 + .col-md-6
6 + = form_for(@announcement) do |f|
7 + .form-group
8 + %label Title
9 + = f.text_field :title, class: 'form-control'
10 + .form-group
11 + %label Notes
12 + (shown internally, used to organize announcements)
13 + = f.text_field :notes, class: 'form-control'
14 + .form-group
15 + %label Body
16 + = f.text_area :body, class: 'form-control', style: 'height: 200px;'
17 + .form-group
18 + %label Author
19 + = f.text_field :author, class: 'form-control'
20 + .checkbox
21 + %label
22 + = f.check_box :published
23 + Published
24 + .checkbox
25 + %label
26 + = f.check_box :frontpage
27 + Show on front page?
28 + .checkbox
29 + %label
30 + = f.check_box :contest_only
31 + Show only in contest?
32 + = f.submit "Update", class: 'btn btn-primary'
33 + = link_to 'Show', @announcement, class: 'btn btn-default'
34 + = link_to 'Back', announcements_path, class: 'btn btn-default'
@@ -0,0 +1,52
1 + = error_messages_for 'problem'
2 + / [form:problem]
3 + .form-group
4 + %label{:for => "problem_name"} Name
5 + = text_field 'problem', 'name', class: 'form-control'
6 + %small
7 + Do not directly edit the problem name, unless you know what you are doing. If you want to change the name, use the name change button in the problem management menu instead.
8 + .form-group
9 + %label{:for => "problem_full_name"} Full name
10 + = text_field 'problem', 'full_name', class: 'form-control'
11 + .form-group
12 + %label{:for => "problem_full_score"} Full score
13 + = text_field 'problem', 'full_score', class: 'form-control'
14 + .form-group
15 + %label{:for => "problem_date_added"} Date added
16 + = date_select 'problem', 'date_added', class: 'form-control'
17 + - # TODO: these should be put in model Problem, but I can't think of
18 + - # nice default values for them. These values look fine only
19 + - # in this case (of lazily adding new problems).
20 + - @problem.available = true if @problem!=nil and @problem.available==nil
21 + - @problem.test_allowed = true if @problem!=nil and @problem.test_allowed==nil
22 + - @problem.output_only = false if @problem!=nil and @problem.output_only==nil
23 + .checkbox
24 + %label{:for => "problem_available"}
25 + = check_box :problem, :available
26 + Available?
27 + .checkbox
28 + %label{:for => "problem_test_allowed"}
29 + = check_box :problem, :test_allowed
30 + Test allowed?
31 + .checkbox
32 + %label{:for => "problem_output_only"}
33 + = check_box :problem, :output_only
34 + Output only?
35 + = error_messages_for 'description'
36 + .form-group
37 + %label{:for => "description_body"} Description
38 + %br/
39 + = text_area :description, :body, :rows => 10, :cols => 80,class: 'form-control'
40 + .form-group
41 + %label{:for => "description_markdowned"} Markdowned?
42 + = select "description", |
43 + "markdowned", |
44 + [['True',true],['False',false]], |
45 + {:selected => (@description) ? @description.markdowned : false } |
46 + .form-group
47 + %label{:for => "problem_url"} URL
48 + %br/
49 + = text_field 'problem', 'url',class: 'form-control'
50 + %p
51 + Task PDF #{file_field_tag 'file'}
52 + / [eoform:problem]
@@ -0,0 +1,14
1 + .container-fluid
2 + = form_for @problem,url:{action: 'update'},html: {multipart: true} do
3 + .row
4 + .col-md-6
5 + %h1 Editing problem
6 + = render :partial => 'form'
7 + .row
8 + .col-md-4
9 + = submit_tag 'Edit', class: 'btn btn-primary btn-block'
10 + .col-md-4
11 + = link_to 'Show', {:action => 'show', :id => @problem}, class: 'btn btn-default btn-block'
12 + .col-md-4
13 + = link_to 'Back', problems_path, class: 'btn btn-default btn-block'
14 + .div{style: 'height: 5em'}
@@ -0,0 +1,5
1 + require 'spec_helper'
2 +
3 + describe Testcases do
4 + pending "add some examples to (or delete) #{__FILE__}"
5 + end
@@ -180,97 +180,97
180 begin
180 begin
181 @problem = Problem.available.find(params[:id])
181 @problem = Problem.available.find(params[:id])
182 rescue
182 rescue
183 redirect_to action: :problem_hof
183 redirect_to action: :problem_hof
184 flash[:notice] = 'Error: submissions for that problem are not viewable.'
184 flash[:notice] = 'Error: submissions for that problem are not viewable.'
185 return
185 return
186 end
186 end
187 end
187 end
188
188
189 return unless @problem
189 return unless @problem
190
190
191 @by_lang = {} #aggregrate by language
191 @by_lang = {} #aggregrate by language
192
192
193 range =65
193 range =65
194 @histogram = { data: Array.new(range,0), summary: {} }
194 @histogram = { data: Array.new(range,0), summary: {} }
195 @summary = {count: 0, solve: 0, attempt: 0}
195 @summary = {count: 0, solve: 0, attempt: 0}
196 user = Hash.new(0)
196 user = Hash.new(0)
197 Submission.where(problem_id: @problem.id).find_each do |sub|
197 Submission.where(problem_id: @problem.id).find_each do |sub|
198 #histogram
198 #histogram
199 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
199 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
200 @histogram[:data][d.to_i] += 1 if d < range
200 @histogram[:data][d.to_i] += 1 if d < range
201
201
202 next unless sub.points
202 next unless sub.points
203 @summary[:count] += 1
203 @summary[:count] += 1
204 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
204 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
205
205
206 lang = Language.find_by_id(sub.language_id)
206 lang = Language.find_by_id(sub.language_id)
207 next unless lang
207 next unless lang
208 next unless sub.points >= @problem.full_score
208 next unless sub.points >= @problem.full_score
209
209
210 #initialize
210 #initialize
211 unless @by_lang.has_key?(lang.pretty_name)
211 unless @by_lang.has_key?(lang.pretty_name)
212 @by_lang[lang.pretty_name] = {
212 @by_lang[lang.pretty_name] = {
213 runtime: { avail: false, value: 2**30-1 },
213 runtime: { avail: false, value: 2**30-1 },
214 memory: { avail: false, value: 2**30-1 },
214 memory: { avail: false, value: 2**30-1 },
215 length: { avail: false, value: 2**30-1 },
215 length: { avail: false, value: 2**30-1 },
216 first: { avail: false, value: DateTime.new(3000,1,1) }
216 first: { avail: false, value: DateTime.new(3000,1,1) }
217 }
217 }
218 end
218 end
219
219
220 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
220 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
221 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
221 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
222 end
222 end
223
223
224 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
224 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
225 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
225 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
226 end
226 end
227
227
228 - if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and
228 + if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and
229 !sub.user.admin?
229 !sub.user.admin?
230 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
230 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
231 end
231 end
232
232
233 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
233 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
234 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
234 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
235 end
235 end
236 end
236 end
237
237
238 #process user_id
238 #process user_id
239 @by_lang.each do |lang,prop|
239 @by_lang.each do |lang,prop|
240 prop.each do |k,v|
240 prop.each do |k,v|
241 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
241 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
242 end
242 end
243 end
243 end
244
244
245 #sum into best
245 #sum into best
246 if @by_lang and @by_lang.first
246 if @by_lang and @by_lang.first
247 @best = @by_lang.first[1].clone
247 @best = @by_lang.first[1].clone
248 @by_lang.each do |lang,prop|
248 @by_lang.each do |lang,prop|
249 if @best[:runtime][:value] >= prop[:runtime][:value]
249 if @best[:runtime][:value] >= prop[:runtime][:value]
250 @best[:runtime] = prop[:runtime]
250 @best[:runtime] = prop[:runtime]
251 @best[:runtime][:lang] = lang
251 @best[:runtime][:lang] = lang
252 end
252 end
253 if @best[:memory][:value] >= prop[:memory][:value]
253 if @best[:memory][:value] >= prop[:memory][:value]
254 @best[:memory] = prop[:memory]
254 @best[:memory] = prop[:memory]
255 @best[:memory][:lang] = lang
255 @best[:memory][:lang] = lang
256 end
256 end
257 if @best[:length][:value] >= prop[:length][:value]
257 if @best[:length][:value] >= prop[:length][:value]
258 @best[:length] = prop[:length]
258 @best[:length] = prop[:length]
259 @best[:length][:lang] = lang
259 @best[:length][:lang] = lang
260 end
260 end
261 if @best[:first][:value] >= prop[:first][:value]
261 if @best[:first][:value] >= prop[:first][:value]
262 @best[:first] = prop[:first]
262 @best[:first] = prop[:first]
263 @best[:first][:lang] = lang
263 @best[:first][:lang] = lang
264 end
264 end
265 end
265 end
266 end
266 end
267
267
268 @histogram[:summary][:max] = [@histogram[:data].max,1].max
268 @histogram[:summary][:max] = [@histogram[:data].max,1].max
269 @summary[:attempt] = user.count
269 @summary[:attempt] = user.count
270 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
270 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
271 end
271 end
272
272
273 def stuck #report struggling user,problem
273 def stuck #report struggling user,problem
274 # init
274 # init
275 user,problem = nil
275 user,problem = nil
276 solve = true
276 solve = true
@@ -66,97 +66,98
66 @user.errors.add(:base,"Email cannot be blank") if @user.email==''
66 @user.errors.add(:base,"Email cannot be blank") if @user.email==''
67 render :action => 'new', :layout => 'empty'
67 render :action => 'new', :layout => 'empty'
68 end
68 end
69 end
69 end
70
70
71 def confirm
71 def confirm
72 login = params[:login]
72 login = params[:login]
73 key = params[:activation]
73 key = params[:activation]
74 @user = User.find_by_login(login)
74 @user = User.find_by_login(login)
75 if (@user) and (@user.verify_activation_key(key))
75 if (@user) and (@user.verify_activation_key(key))
76 if @user.valid? # check uniquenss of email
76 if @user.valid? # check uniquenss of email
77 @user.activated = true
77 @user.activated = true
78 @user.save
78 @user.save
79 @result = :successful
79 @result = :successful
80 else
80 else
81 @result = :email_used
81 @result = :email_used
82 end
82 end
83 else
83 else
84 @result = :failed
84 @result = :failed
85 end
85 end
86 render :action => 'confirm', :layout => 'empty'
86 render :action => 'confirm', :layout => 'empty'
87 end
87 end
88
88
89 def forget
89 def forget
90 render :action => 'forget', :layout => 'empty'
90 render :action => 'forget', :layout => 'empty'
91 end
91 end
92
92
93 def retrieve_password
93 def retrieve_password
94 email = params[:email]
94 email = params[:email]
95 user = User.find_by_email(email)
95 user = User.find_by_email(email)
96 if user
96 if user
97 last_updated_time = user.updated_at || user.created_at || (Time.now.gmtime - 1.hour)
97 last_updated_time = user.updated_at || user.created_at || (Time.now.gmtime - 1.hour)
98 if last_updated_time > Time.now.gmtime - 5.minutes
98 if last_updated_time > Time.now.gmtime - 5.minutes
99 flash[:notice] = 'The account has recently created or new password has recently been requested. Please wait for 5 minutes'
99 flash[:notice] = 'The account has recently created or new password has recently been requested. Please wait for 5 minutes'
100 else
100 else
101 user.password = user.password_confirmation = User.random_password
101 user.password = user.password_confirmation = User.random_password
102 user.save
102 user.save
103 send_new_password_email(user)
103 send_new_password_email(user)
104 flash[:notice] = 'New password has been mailed to you.'
104 flash[:notice] = 'New password has been mailed to you.'
105 end
105 end
106 else
106 else
107 flash[:notice] = I18n.t 'registration.password_retrieval.no_email'
107 flash[:notice] = I18n.t 'registration.password_retrieval.no_email'
108 end
108 end
109 redirect_to :action => 'forget'
109 redirect_to :action => 'forget'
110 end
110 end
111
111
112 def stat
112 def stat
113 @user = User.find(params[:id])
113 @user = User.find(params[:id])
114 - @submission = Submission.includes(:problem).where(user_id: params[:id])
114 + @submission = Submission.joins(:problem).where(user_id: params[:id])
115 + @submission = @submission.where('problems.available = true') unless current_user.admin?
115
116
116 range = 120
117 range = 120
117 @histogram = { data: Array.new(range,0), summary: {} }
118 @histogram = { data: Array.new(range,0), summary: {} }
118 @summary = {count: 0, solve: 0, attempt: 0}
119 @summary = {count: 0, solve: 0, attempt: 0}
119 problem = Hash.new(0)
120 problem = Hash.new(0)
120
121
121 @submission.find_each do |sub|
122 @submission.find_each do |sub|
122 #histogram
123 #histogram
123 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
124 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
124 @histogram[:data][d.to_i] += 1 if d < range
125 @histogram[:data][d.to_i] += 1 if d < range
125
126
126 @summary[:count] += 1
127 @summary[:count] += 1
127 next unless sub.problem
128 next unless sub.problem
128 problem[sub.problem] = [problem[sub.problem], ( (sub.try(:points) || 0) >= sub.problem.full_score) ? 1 : 0].max
129 problem[sub.problem] = [problem[sub.problem], ( (sub.try(:points) || 0) >= sub.problem.full_score) ? 1 : 0].max
129 end
130 end
130
131
131 @histogram[:summary][:max] = [@histogram[:data].max,1].max
132 @histogram[:summary][:max] = [@histogram[:data].max,1].max
132 @summary[:attempt] = problem.count
133 @summary[:attempt] = problem.count
133 problem.each_value { |v| @summary[:solve] += 1 if v == 1 }
134 problem.each_value { |v| @summary[:solve] += 1 if v == 1 }
134 end
135 end
135
136
136 def toggle_activate
137 def toggle_activate
137 @user = User.find(params[:id])
138 @user = User.find(params[:id])
138 @user.update_attributes( activated: !@user.activated? )
139 @user.update_attributes( activated: !@user.activated? )
139 respond_to do |format|
140 respond_to do |format|
140 format.js { render partial: 'toggle_button',
141 format.js { render partial: 'toggle_button',
141 locals: {button_id: "#toggle_activate_user_#{@user.id}",button_on: @user.activated? } }
142 locals: {button_id: "#toggle_activate_user_#{@user.id}",button_on: @user.activated? } }
142 end
143 end
143 end
144 end
144
145
145 def toggle_enable
146 def toggle_enable
146 @user = User.find(params[:id])
147 @user = User.find(params[:id])
147 @user.update_attributes( enabled: !@user.enabled? )
148 @user.update_attributes( enabled: !@user.enabled? )
148 respond_to do |format|
149 respond_to do |format|
149 format.js { render partial: 'toggle_button',
150 format.js { render partial: 'toggle_button',
150 locals: {button_id: "#toggle_enable_user_#{@user.id}",button_on: @user.enabled? } }
151 locals: {button_id: "#toggle_enable_user_#{@user.id}",button_on: @user.enabled? } }
151 end
152 end
152 end
153 end
153
154
154 protected
155 protected
155
156
156 def verify_online_registration
157 def verify_online_registration
157 if !GraderConfiguration['system.online_registration']
158 if !GraderConfiguration['system.online_registration']
158 redirect_to :controller => 'main', :action => 'login'
159 redirect_to :controller => 'main', :action => 'login'
159 end
160 end
160 end
161 end
161
162
162 def send_confirmation_email(user)
163 def send_confirmation_email(user)
@@ -2,77 +2,71
2
2
3 require 'rails/all'
3 require 'rails/all'
4
4
5 if defined?(Bundler)
5 if defined?(Bundler)
6 # If you precompile assets before deploying to production, use this line
6 # If you precompile assets before deploying to production, use this line
7 Bundler.require(*Rails.groups(:assets => %w(development test)))
7 Bundler.require(*Rails.groups(:assets => %w(development test)))
8 # If you want your assets lazily compiled in production, use this line
8 # If you want your assets lazily compiled in production, use this line
9 # Bundler.require(:default, :assets, Rails.env)
9 # Bundler.require(:default, :assets, Rails.env)
10 end
10 end
11
11
12 module CafeGrader
12 module CafeGrader
13 class Application < Rails::Application
13 class Application < Rails::Application
14 # Settings in config/environments/* take precedence over those specified here.
14 # Settings in config/environments/* take precedence over those specified here.
15 # Application configuration should go into files in config/initializers
15 # Application configuration should go into files in config/initializers
16 # -- all .rb files in that directory are automatically loaded.
16 # -- all .rb files in that directory are automatically loaded.
17
17
18 # Custom directories with classes and modules you want to be autoloadable.
18 # Custom directories with classes and modules you want to be autoloadable.
19 config.autoload_paths += %W(#{config.root}/lib)
19 config.autoload_paths += %W(#{config.root}/lib)
20
20
21 # Only load the plugins named here, in the order given (default is alphabetical).
21 # Only load the plugins named here, in the order given (default is alphabetical).
22 # :all can be used as a placeholder for all plugins not explicitly named.
22 # :all can be used as a placeholder for all plugins not explicitly named.
23 # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
23 # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
24
24
25 # Activate observers that should always be running.
25 # Activate observers that should always be running.
26 # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
26 # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
27
27
28 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
28 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
29 # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
29 # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
30 config.time_zone = 'UTC'
30 config.time_zone = 'UTC'
31
31
32 # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
32 # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
33 # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
33 # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
34 config.i18n.default_locale = :en
34 config.i18n.default_locale = :en
35
35
36 # Configure the default encoding used in templates for Ruby 1.9.
36 # Configure the default encoding used in templates for Ruby 1.9.
37 config.encoding = "utf-8"
37 config.encoding = "utf-8"
38
38
39 # Configure sensitive parameters which will be filtered from the log file.
39 # Configure sensitive parameters which will be filtered from the log file.
40 config.filter_parameters += [:password]
40 config.filter_parameters += [:password]
41
41
42 # Enable escaping HTML in JSON.
42 # Enable escaping HTML in JSON.
43 config.active_support.escape_html_entities_in_json = true
43 config.active_support.escape_html_entities_in_json = true
44
44
45 # Use SQL instead of Active Record's schema dumper when creating the database.
45 # Use SQL instead of Active Record's schema dumper when creating the database.
46 # This is necessary if your schema can't be completely dumped by the schema dumper,
46 # This is necessary if your schema can't be completely dumped by the schema dumper,
47 # like if you have constraints or database-specific column types
47 # like if you have constraints or database-specific column types
48 # config.active_record.schema_format = :sql
48 # config.active_record.schema_format = :sql
49
49
50 - # Enforce whitelist mode for mass assignment.
51 - # This will create an empty whitelist of attributes available for mass-assignment for all models
52 - # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
53 - # parameters by using an attr_accessible or attr_protected declaration.
54 - config.active_record.whitelist_attributes = false
55 -
56 # Enable the asset pipeline
50 # Enable the asset pipeline
57 config.assets.enabled = true
51 config.assets.enabled = true
58
52
59 # Version of your assets, change this if you want to expire all your assets
53 # Version of your assets, change this if you want to expire all your assets
60 config.assets.version = '1.0'
54 config.assets.version = '1.0'
61
55
62 # ---------------- IMPORTANT ----------------------
56 # ---------------- IMPORTANT ----------------------
63 # If we deploy the app into a subdir name "grader", be sure to do "rake assets:precompile RAILS_RELATIVE_URL_ROOT=/grader"
57 # If we deploy the app into a subdir name "grader", be sure to do "rake assets:precompile RAILS_RELATIVE_URL_ROOT=/grader"
64 # moreover, using the following line instead also known to works
58 # moreover, using the following line instead also known to works
65 #config.action_controller.relative_url_root = '/grader'
59 #config.action_controller.relative_url_root = '/grader'
66
60
67 #font path
61 #font path
68 config.assets.paths << "#{Rails}/vendor/assets/fonts"
62 config.assets.paths << "#{Rails}/vendor/assets/fonts"
69
63
70 config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
64 config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
71 config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
65 config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
72 %w( announcements submissions configurations contests contest_management graders heartbeat
66 %w( announcements submissions configurations contests contest_management graders heartbeat
73 login main messages problems report site sites sources tasks
67 login main messages problems report site sites sources tasks
74 test user_admin users ).each do |controller|
68 test user_admin users ).each do |controller|
75 config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
69 config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
76 end
70 end
77 end
71 end
78 end
72 end
You need to be logged in to leave comments. Login now