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

r689:124074894d4b - - 16 files changed: 106 inserted, 21 deleted

new file 100644
new file 100644
new file 100644
new file 100644
@@ -0,0 +1,7
1 + class GroupProblem < ActiveRecord::Base
2 + self.table_name = 'groups_problems'
3 +
4 + belongs_to :problem
5 + belongs_to :group
6 + validates_uniqueness_of :problem_id, scope: :group_id, message: ->(object, data) { "'#{Problem.find(data[:value]).full_name}' is already in the group" }
7 + end
@@ -0,0 +1,7
1 + class GroupUser < ActiveRecord::Base
2 + self.table_name = 'groups_users'
3 +
4 + belongs_to :user
5 + belongs_to :group
6 + validates_uniqueness_of :user_id, scope: :group_id, message: ->(object, data) { "'#{User.find(data[:value]).full_name}' is already in the group" }
7 + end
@@ -0,0 +1,23
1 + # Be sure to restart your server when you modify this file.
2 +
3 + # Version of your assets, change this if you want to expire all your assets.
4 + Rails.application.config.assets.version = '1.0'
5 +
6 + # Add additional assets to the asset load path.
7 + # Rails.application.config.assets.paths << Emoji.images_path
8 + # Add Yarn node_modules folder to the asset load path.
9 + Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 + Rails.application.config.assets.paths << Rails.root.join('vendor/assets/fonts')
11 +
12 + # Precompile additional assets.
13 + # application.js, application.css, and all non-JS/CSS in the app/assets
14 + # folder are already added.
15 + # Rails.application.config.assets.precompile += %w( admin.js admin.css )
16 +
17 + Rails.application.config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
18 + Rails.application.config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
19 + %w( announcements submissions configurations contests contest_management graders heartbeat
20 + login main messages problems report site sites sources tasks groups
21 + test user_admin users ).each do |controller|
22 + Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
23 + end
@@ -1,86 +1,94
1 1 class GroupsController < ApplicationController
2 2 before_action :set_group, only: [:show, :edit, :update, :destroy,
3 3 :add_user, :remove_user,
4 4 :add_problem, :remove_problem,
5 5 ]
6 6 before_action :authenticate, :admin_authorization
7 7
8 8 # GET /groups
9 9 def index
10 10 @groups = Group.all
11 11 end
12 12
13 13 # GET /groups/1
14 14 def show
15 15 end
16 16
17 17 # GET /groups/new
18 18 def new
19 19 @group = Group.new
20 20 end
21 21
22 22 # GET /groups/1/edit
23 23 def edit
24 24 end
25 25
26 26 # POST /groups
27 27 def create
28 28 @group = Group.new(group_params)
29 29
30 30 if @group.save
31 31 redirect_to @group, notice: 'Group was successfully created.'
32 32 else
33 33 render :new
34 34 end
35 35 end
36 36
37 37 # PATCH/PUT /groups/1
38 38 def update
39 39 if @group.update(group_params)
40 40 redirect_to @group, notice: 'Group was successfully updated.'
41 41 else
42 42 render :edit
43 43 end
44 44 end
45 45
46 46 # DELETE /groups/1
47 47 def destroy
48 48 @group.destroy
49 49 redirect_to groups_url, notice: 'Group was successfully destroyed.'
50 50 end
51 51
52 52 def remove_user
53 53 user = User.find(params[:user_id])
54 54 @group.users.delete(user)
55 - redirect_to group_path(@group), notice: "User #{user.login} was removed from the group #{@group.name}"
55 + redirect_to group_path(@group), flash: {success: "User #{user.login} was removed from the group #{@group.name}"}
56 56 end
57 57
58 58 def add_user
59 59 user = User.find(params[:user_id])
60 + begin
60 61 @group.users << user
61 - redirect_to group_path(@group), notice: "User #{user.login} was add to the group #{@group.name}"
62 + redirect_to group_path(@group), flash: { success: "User #{user.login} was add to the group #{@group.name}"}
63 + rescue => e
64 + redirect_to group_path(@group), alert: e.message
65 + end
62 66 end
63 67
64 68 def remove_problem
65 69 problem = Problem.find(params[:problem_id])
66 70 @group.problems.delete(problem)
67 - redirect_to group_path(@group), notice: "Problem #{problem.name} was removed from the group #{@group.name}"
71 + redirect_to group_path(@group), flash: {success: "Problem #{problem.name} was removed from the group #{@group.name}" }
68 72 end
69 73
70 74 def add_problem
71 75 problem = Problem.find(params[:problem_id])
76 + begin
72 77 @group.problems << problem
73 - redirect_to group_path(@group), notice: "Problem #{problem.name} was add to the group #{@group.name}"
78 + redirect_to group_path(@group), flash: {success: "Problem #{problem.name} was add to the group #{@group.name}" }
79 + rescue => e
80 + redirect_to group_path(@group), alert: e.message
81 + end
74 82 end
75 83
76 84 private
77 85 # Use callbacks to share common setup or constraints between actions.
78 86 def set_group
79 87 @group = Group.find(params[:id])
80 88 end
81 89
82 90 # Only allow a trusted parameter "white list" through.
83 91 def group_params
84 92 params.require(:group).permit(:name, :description)
85 93 end
86 94 end
@@ -1,299 +1,309
1 1 class ProblemsController < ApplicationController
2 2
3 3 before_action :authenticate, :authorization
4 4 before_action :testcase_authorization, only: [:show_testcase]
5 5
6 6 in_place_edit_for :problem, :name
7 7 in_place_edit_for :problem, :full_name
8 8 in_place_edit_for :problem, :full_score
9 9
10 10 def index
11 11 @problems = Problem.order(date_added: :desc)
12 12 end
13 13
14 14 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
15 15 verify :method => :post, :only => [ :create, :quick_create,
16 16 :do_manage,
17 17 :do_import,
18 18 ],
19 19 :redirect_to => { :action => :index }
20 20
21 21 def show
22 22 @problem = Problem.find(params[:id])
23 23 end
24 24
25 25 def new
26 26 @problem = Problem.new
27 27 @description = nil
28 28 end
29 29
30 30 def create
31 31 @problem = Problem.new(problem_params)
32 32 @description = Description.new(params[:description])
33 33 if @description.body!=''
34 34 if !@description.save
35 35 render :action => new and return
36 36 end
37 37 else
38 38 @description = nil
39 39 end
40 40 @problem.description = @description
41 41 if @problem.save
42 42 flash[:notice] = 'Problem was successfully created.'
43 43 redirect_to action: :index
44 44 else
45 45 render :action => 'new'
46 46 end
47 47 end
48 48
49 49 def quick_create
50 50 @problem = Problem.new(problem_params)
51 51 @problem.full_name = @problem.name if @problem.full_name == ''
52 52 @problem.full_score = 100
53 53 @problem.available = false
54 54 @problem.test_allowed = true
55 55 @problem.output_only = false
56 56 @problem.date_added = Time.new
57 57 if @problem.save
58 58 flash[:notice] = 'Problem was successfully created.'
59 59 redirect_to action: :index
60 60 else
61 61 flash[:notice] = 'Error saving problem'
62 62 redirect_to action: :index
63 63 end
64 64 end
65 65
66 66 def edit
67 67 @problem = Problem.find(params[:id])
68 68 @description = @problem.description
69 69 end
70 70
71 71 def update
72 72 @problem = Problem.find(params[:id])
73 73 @description = @problem.description
74 74 if @description.nil? and params[:description][:body]!=''
75 75 @description = Description.new(params[:description])
76 76 if !@description.save
77 77 flash[:notice] = 'Error saving description'
78 78 render :action => 'edit' and return
79 79 end
80 80 @problem.description = @description
81 81 elsif @description
82 82 if !@description.update_attributes(params[:description])
83 83 flash[:notice] = 'Error saving description'
84 84 render :action => 'edit' and return
85 85 end
86 86 end
87 87 if params[:file] and params[:file].content_type != 'application/pdf'
88 88 flash[:notice] = 'Error: Uploaded file is not PDF'
89 89 render :action => 'edit' and return
90 90 end
91 91 if @problem.update_attributes(problem_params)
92 92 flash[:notice] = 'Problem was successfully updated.'
93 93 unless params[:file] == nil or params[:file] == ''
94 94 flash[:notice] = 'Problem was successfully updated and a new PDF file is uploaded.'
95 95 out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}"
96 96 if not FileTest.exists? out_dirname
97 97 Dir.mkdir out_dirname
98 98 end
99 99
100 100 out_filename = "#{out_dirname}/#{@problem.name}.pdf"
101 101 if FileTest.exists? out_filename
102 102 File.delete out_filename
103 103 end
104 104
105 105 File.open(out_filename,"wb") do |file|
106 106 file.write(params[:file].read)
107 107 end
108 108 @problem.description_filename = "#{@problem.name}.pdf"
109 109 @problem.save
110 110 end
111 111 redirect_to :action => 'show', :id => @problem
112 112 else
113 113 render :action => 'edit'
114 114 end
115 115 end
116 116
117 117 def destroy
118 118 p = Problem.find(params[:id]).destroy
119 119 redirect_to action: :index
120 120 end
121 121
122 122 def toggle
123 123 @problem = Problem.find(params[:id])
124 124 @problem.update_attributes(available: !(@problem.available) )
125 125 respond_to do |format|
126 126 format.js { }
127 127 end
128 128 end
129 129
130 130 def toggle_test
131 131 @problem = Problem.find(params[:id])
132 132 @problem.update_attributes(test_allowed: !(@problem.test_allowed?) )
133 133 respond_to do |format|
134 134 format.js { }
135 135 end
136 136 end
137 137
138 138 def toggle_view_testcase
139 139 @problem = Problem.find(params[:id])
140 140 @problem.update_attributes(view_testcase: !(@problem.view_testcase?) )
141 141 respond_to do |format|
142 142 format.js { }
143 143 end
144 144 end
145 145
146 146 def turn_all_off
147 147 Problem.available.all.each do |problem|
148 148 problem.available = false
149 149 problem.save
150 150 end
151 151 redirect_to action: :index
152 152 end
153 153
154 154 def turn_all_on
155 155 Problem.where.not(available: true).each do |problem|
156 156 problem.available = true
157 157 problem.save
158 158 end
159 159 redirect_to action: :index
160 160 end
161 161
162 162 def stat
163 163 @problem = Problem.find(params[:id])
164 164 unless @problem.available or session[:admin]
165 165 redirect_to :controller => 'main', :action => 'list'
166 166 return
167 167 end
168 168 @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id)
169 169
170 170 #stat summary
171 171 range =65
172 172 @histogram = { data: Array.new(range,0), summary: {} }
173 173 user = Hash.new(0)
174 174 @submissions.find_each do |sub|
175 175 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
176 176 @histogram[:data][d.to_i] += 1 if d < range
177 177 user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max
178 178 end
179 179 @histogram[:summary][:max] = [@histogram[:data].max,1].max
180 180
181 181 @summary = { attempt: user.count, solve: 0 }
182 182 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
183 183 end
184 184
185 185 def manage
186 186 @problems = Problem.order(date_added: :desc)
187 187 end
188 188
189 189 def do_manage
190 190 if params.has_key? 'change_date_added'
191 191 change_date_added
192 192 elsif params.has_key? 'add_to_contest'
193 193 add_to_contest
194 194 elsif params.has_key? 'enable_problem'
195 195 set_available(true)
196 196 elsif params.has_key? 'disable_problem'
197 197 set_available(false)
198 198 elsif params.has_key? 'add_group'
199 199 group = Group.find(params[:group_id])
200 + ok = []
201 + failed = []
200 202 get_problems_from_params.each do |p|
203 + begin
201 204 group.problems << p
205 + ok << p.full_name
206 + rescue => e
207 + failed << p.full_name
202 208 end
203 209 end
210 + flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0
211 + flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0
212 + end
213 +
204 214 redirect_to :action => 'manage'
205 215 end
206 216
207 217 def import
208 218 @allow_test_pair_import = allow_test_pair_import?
209 219 end
210 220
211 221 def do_import
212 222 old_problem = Problem.find_by_name(params[:name])
213 223 if !allow_test_pair_import? and params.has_key? :import_to_db
214 224 params.delete :import_to_db
215 225 end
216 226 @problem, import_log = Problem.create_from_import_form_params(params,
217 227 old_problem)
218 228
219 229 if !@problem.errors.empty?
220 230 render :action => 'import' and return
221 231 end
222 232
223 233 if old_problem!=nil
224 234 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
225 235 end
226 236 @log = import_log
227 237 end
228 238
229 239 def remove_contest
230 240 problem = Problem.find(params[:id])
231 241 contest = Contest.find(params[:contest_id])
232 242 if problem!=nil and contest!=nil
233 243 problem.contests.delete(contest)
234 244 end
235 245 redirect_to :action => 'manage'
236 246 end
237 247
238 248 ##################################
239 249 protected
240 250
241 251 def allow_test_pair_import?
242 252 if defined? ALLOW_TEST_PAIR_IMPORT
243 253 return ALLOW_TEST_PAIR_IMPORT
244 254 else
245 255 return false
246 256 end
247 257 end
248 258
249 259 def change_date_added
250 260 problems = get_problems_from_params
251 261 year = params[:date_added][:year].to_i
252 262 month = params[:date_added][:month].to_i
253 263 day = params[:date_added][:day].to_i
254 264 date = Date.new(year,month,day)
255 265 problems.each do |p|
256 266 p.date_added = date
257 267 p.save
258 268 end
259 269 end
260 270
261 271 def add_to_contest
262 272 problems = get_problems_from_params
263 273 contest = Contest.find(params[:contest][:id])
264 274 if contest!=nil and contest.enabled
265 275 problems.each do |p|
266 276 p.contests << contest
267 277 end
268 278 end
269 279 end
270 280
271 281 def set_available(avail)
272 282 problems = get_problems_from_params
273 283 problems.each do |p|
274 284 p.available = avail
275 285 p.save
276 286 end
277 287 end
278 288
279 289 def get_problems_from_params
280 290 problems = []
281 291 params.keys.each do |k|
282 292 if k.index('prob-')==0
283 293 name, id, order = k.split('-')
284 294 problems << Problem.find(id)
285 295 end
286 296 end
287 297 problems
288 298 end
289 299
290 300 def get_problems_stat
291 301 end
292 302
293 303 private
294 304
295 305 def problem_params
296 306 params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description)
297 307 end
298 308
299 309 end
@@ -61,535 +61,546
61 61 @user = User.find(params[:id])
62 62 @user.last_ip = nil
63 63 @user.save
64 64 redirect_to action: 'index', page: params[:page]
65 65 end
66 66
67 67 def create_from_list
68 68 lines = params[:user_list]
69 69
70 70 note = []
71 71
72 72 lines.split("\n").each do |line|
73 73 items = line.chomp.split(',')
74 74 if items.length>=2
75 75 login = items[0]
76 76 full_name = items[1]
77 77 remark =''
78 78 user_alias = ''
79 79
80 80 added_random_password = false
81 81 if items.length >= 3 and items[2].chomp(" ").length > 0;
82 82 password = items[2].chomp(" ")
83 83 else
84 84 password = random_password
85 85 add_random_password=true;
86 86 end
87 87
88 88 if items.length>= 4 and items[3].chomp(" ").length > 0;
89 89 user_alias = items[3].chomp(" ")
90 90 else
91 91 user_alias = login
92 92 end
93 93
94 94 if items.length>=5
95 95 remark = items[4].strip;
96 96 end
97 97
98 98 user = User.find_by_login(login)
99 99 if (user)
100 100 user.full_name = full_name
101 101 user.password = password
102 102 user.remark = remark
103 103 else
104 104 user = User.new({:login => login,
105 105 :full_name => full_name,
106 106 :password => password,
107 107 :password_confirmation => password,
108 108 :alias => user_alias,
109 109 :remark => remark})
110 110 end
111 111 user.activated = true
112 112 user.save
113 113
114 114 if added_random_password
115 115 note << "'#{login}' (+)"
116 116 else
117 117 note << login
118 118 end
119 119 end
120 120 end
121 121 flash[:success] = 'User(s) ' + note.join(', ') +
122 122 ' were successfully created. ' +
123 123 '( (+) - created with random passwords.)'
124 124 redirect_to :action => 'index'
125 125 end
126 126
127 127 def edit
128 128 @user = User.find(params[:id])
129 129 end
130 130
131 131 def update
132 132 @user = User.find(params[:id])
133 133 if @user.update_attributes(user_params)
134 134 flash[:notice] = 'User was successfully updated.'
135 135 redirect_to :action => 'show', :id => @user
136 136 else
137 137 render :action => 'edit'
138 138 end
139 139 end
140 140
141 141 def destroy
142 142 User.find(params[:id]).destroy
143 143 redirect_to :action => 'index'
144 144 end
145 145
146 146 def user_stat
147 147 if params[:commit] == 'download csv'
148 148 @problems = Problem.all
149 149 else
150 150 @problems = Problem.available_problems
151 151 end
152 152 @users = User.includes(:contests, :contest_stat).where(enabled: true)
153 153 @scorearray = Array.new
154 154 @users.each do |u|
155 155 ustat = Array.new
156 156 ustat[0] = u
157 157 @problems.each do |p|
158 158 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
159 159 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
160 160 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
161 161 else
162 162 ustat << [0,false]
163 163 end
164 164 end
165 165 @scorearray << ustat
166 166 end
167 167 if params[:commit] == 'download csv' then
168 168 csv = gen_csv_from_scorearray(@scorearray,@problems)
169 169 send_data csv, filename: 'last_score.csv'
170 170 else
171 171 render template: 'user_admin/user_stat'
172 172 end
173 173 end
174 174
175 175 def user_stat_max
176 176 if params[:commit] == 'download csv'
177 177 @problems = Problem.all
178 178 else
179 179 @problems = Problem.available_problems
180 180 end
181 181 @users = User.includes(:contests).includes(:contest_stat).all
182 182 @scorearray = Array.new
183 183 #set up range from param
184 184 since_id = params.fetch(:since_id, 0).to_i
185 185 until_id = params.fetch(:until_id, 0).to_i
186 186 @users.each do |u|
187 187 ustat = Array.new
188 188 ustat[0] = u
189 189 @problems.each do |p|
190 190 max_points = 0
191 191 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
192 192 max_points = sub.points if sub and sub.points and (sub.points > max_points)
193 193 end
194 194 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
195 195 end
196 196 @scorearray << ustat
197 197 end
198 198
199 199 if params[:commit] == 'download csv' then
200 200 csv = gen_csv_from_scorearray(@scorearray,@problems)
201 201 send_data csv, filename: 'max_score.csv'
202 202 else
203 203 render template: 'user_admin/user_stat'
204 204 end
205 205 end
206 206
207 207 def import
208 208 if params[:file]==''
209 209 flash[:notice] = 'Error importing no file'
210 210 redirect_to :action => 'index' and return
211 211 end
212 212 import_from_file(params[:file])
213 213 end
214 214
215 215 def random_all_passwords
216 216 users = User.all
217 217 @prefix = params[:prefix] || ''
218 218 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
219 219 @changed = false
220 220 if request.request_method == 'POST'
221 221 @non_admin_users.each do |user|
222 222 password = random_password
223 223 user.password = password
224 224 user.password_confirmation = password
225 225 user.save
226 226 end
227 227 @changed = true
228 228 end
229 229 end
230 230
231 231
232 232 # contest management
233 233
234 234 def contests
235 235 @contest, @users = find_contest_and_user_from_contest_id(params[:id])
236 236 @contests = Contest.enabled
237 237 end
238 238
239 239 def assign_from_list
240 240 contest_id = params[:users_contest_id]
241 241 org_contest, users = find_contest_and_user_from_contest_id(contest_id)
242 242 contest = Contest.find(params[:new_contest][:id])
243 243 if !contest
244 244 flash[:notice] = 'Error: no contest'
245 245 redirect_to :action => 'contests', :id =>contest_id
246 246 end
247 247
248 248 note = []
249 249 users.each do |u|
250 250 u.contests = [contest]
251 251 note << u.login
252 252 end
253 253 flash[:notice] = 'User(s) ' + note.join(', ') +
254 254 " were successfully reassigned to #{contest.title}."
255 255 redirect_to :action => 'contests', :id =>contest.id
256 256 end
257 257
258 258 def add_to_contest
259 259 user = User.find(params[:id])
260 260 contest = Contest.find(params[:contest_id])
261 261 if user and contest
262 262 user.contests << contest
263 263 end
264 264 redirect_to :action => 'index'
265 265 end
266 266
267 267 def remove_from_contest
268 268 user = User.find(params[:id])
269 269 contest = Contest.find(params[:contest_id])
270 270 if user and contest
271 271 user.contests.delete(contest)
272 272 end
273 273 redirect_to :action => 'index'
274 274 end
275 275
276 276 def contest_management
277 277 end
278 278
279 279 def manage_contest
280 280 contest = Contest.find(params[:contest][:id])
281 281 if !contest
282 282 flash[:notice] = 'You did not choose the contest.'
283 283 redirect_to :action => 'contest_management' and return
284 284 end
285 285
286 286 operation = params[:operation]
287 287
288 288 if not ['add','remove','assign'].include? operation
289 289 flash[:notice] = 'You did not choose the operation to perform.'
290 290 redirect_to :action => 'contest_management' and return
291 291 end
292 292
293 293 lines = params[:login_list]
294 294 if !lines or lines.blank?
295 295 flash[:notice] = 'You entered an empty list.'
296 296 redirect_to :action => 'contest_management' and return
297 297 end
298 298
299 299 note = []
300 300 users = []
301 301 lines.split("\n").each do |line|
302 302 user = User.find_by_login(line.chomp)
303 303 if user
304 304 if operation=='add'
305 305 if ! user.contests.include? contest
306 306 user.contests << contest
307 307 end
308 308 elsif operation=='remove'
309 309 user.contests.delete(contest)
310 310 else
311 311 user.contests = [contest]
312 312 end
313 313
314 314 if params[:reset_timer]
315 315 user.contest_stat.forced_logout = true
316 316 user.contest_stat.reset_timer_and_save
317 317 end
318 318
319 319 if params[:notification_emails]
320 320 send_contest_update_notification_email(user, contest)
321 321 end
322 322
323 323 note << user.login
324 324 users << user
325 325 end
326 326 end
327 327
328 328 if params[:reset_timer]
329 329 logout_users(users)
330 330 end
331 331
332 332 flash[:notice] = 'User(s) ' + note.join(', ') +
333 333 ' were successfully modified. '
334 334 redirect_to :action => 'contest_management'
335 335 end
336 336
337 337 # admin management
338 338
339 339 def admin
340 340 @admins = User.all.find_all {|user| user.admin? }
341 341 end
342 342
343 343 def grant_admin
344 344 login = params[:login]
345 345 user = User.find_by_login(login)
346 346 if user!=nil
347 347 admin_role = Role.find_by_name('admin')
348 348 user.roles << admin_role
349 349 else
350 350 flash[:notice] = 'Unknown user'
351 351 end
352 352 flash[:notice] = 'User added as admins'
353 353 redirect_to :action => 'admin'
354 354 end
355 355
356 356 def revoke_admin
357 357 user = User.find(params[:id])
358 358 if user==nil
359 359 flash[:notice] = 'Unknown user'
360 360 redirect_to :action => 'admin' and return
361 361 elsif user.login == 'root'
362 362 flash[:notice] = 'You cannot revoke admisnistrator permission from root.'
363 363 redirect_to :action => 'admin' and return
364 364 end
365 365
366 366 admin_role = Role.find_by_name('admin')
367 367 user.roles.delete(admin_role)
368 368 flash[:notice] = 'User permission revoked'
369 369 redirect_to :action => 'admin'
370 370 end
371 371
372 372 # mass mailing
373 373
374 374 def mass_mailing
375 375 end
376 376
377 377 def bulk_mail
378 378 lines = params[:login_list]
379 379 if !lines or lines.blank?
380 380 flash[:notice] = 'You entered an empty list.'
381 381 redirect_to :action => 'mass_mailing' and return
382 382 end
383 383
384 384 mail_subject = params[:subject]
385 385 if !mail_subject or mail_subject.blank?
386 386 flash[:notice] = 'You entered an empty mail subject.'
387 387 redirect_to :action => 'mass_mailing' and return
388 388 end
389 389
390 390 mail_body = params[:email_body]
391 391 if !mail_body or mail_body.blank?
392 392 flash[:notice] = 'You entered an empty mail body.'
393 393 redirect_to :action => 'mass_mailing' and return
394 394 end
395 395
396 396 note = []
397 397 users = []
398 398 lines.split("\n").each do |line|
399 399 user = User.find_by_login(line.chomp)
400 400 if user
401 401 send_mail(user.email, mail_subject, mail_body)
402 402 note << user.login
403 403 end
404 404 end
405 405
406 406 flash[:notice] = 'User(s) ' + note.join(', ') +
407 407 ' were successfully modified. '
408 408 redirect_to :action => 'mass_mailing'
409 409 end
410 410
411 411 #bulk manage
412 412 def bulk_manage
413 413
414 414 begin
415 415 @users = User.where('(login REGEXP ?) OR (remark REGEXP ?)',params[:regex],params[:regex]) if params[:regex]
416 416 @users.count if @users #i don't know why I have to call count, but if I won't exception is not raised
417 417 rescue Exception
418 418 flash[:error] = 'Regular Expression is malformed'
419 419 @users = nil
420 420 end
421 421
422 422 if params[:commit]
423 423 @action = {}
424 424 @action[:set_enable] = params[:enabled]
425 425 @action[:enabled] = params[:enable] == "1"
426 426 @action[:gen_password] = params[:gen_password]
427 427 @action[:add_group] = params[:add_group]
428 428 @action[:group_name] = params[:group_name]
429 429 end
430 430
431 431 if params[:commit] == "Perform"
432 432 if @action[:set_enable]
433 433 @users.update_all(enabled: @action[:enabled])
434 434 end
435 435 if @action[:gen_password]
436 436 @users.each do |u|
437 437 password = random_password
438 438 u.password = password
439 439 u.password_confirmation = password
440 440 u.save
441 441 end
442 442 end
443 443 if @action[:add_group] and @action[:group_name]
444 444 @group = Group.find(@action[:group_name])
445 - @users.each { |user| @group.users << user }
445 + ok = []
446 + failed = []
447 + @users.each do |user|
448 + begin
449 + @group.users << user
450 + ok << user.login
451 + rescue => e
452 + failed << user.login
453 + end
454 + end
455 + flash[:success] = "The following users are added to the 'group #{@group.name}': " + ok.join(', ') if ok.count > 0
456 + flash[:alert] = "The following users are already in the 'group #{@group.name}': " + failed.join(', ') if failed.count > 0
446 457 end
447 458 end
448 459 end
449 460
450 461 protected
451 462
452 463 def random_password(length=5)
453 464 chars = 'abcdefghijkmnopqrstuvwxyz23456789'
454 465 newpass = ""
455 466 length.times { newpass << chars[rand(chars.size-1)] }
456 467 return newpass
457 468 end
458 469
459 470 def import_from_file(f)
460 471 data_hash = YAML.load(f)
461 472 @import_log = ""
462 473
463 474 country_data = data_hash[:countries]
464 475 site_data = data_hash[:sites]
465 476 user_data = data_hash[:users]
466 477
467 478 # import country
468 479 countries = {}
469 480 country_data.each_pair do |id,country|
470 481 c = Country.find_by_name(country[:name])
471 482 if c!=nil
472 483 countries[id] = c
473 484 @import_log << "Found #{country[:name]}\n"
474 485 else
475 486 countries[id] = Country.new(:name => country[:name])
476 487 countries[id].save
477 488 @import_log << "Created #{country[:name]}\n"
478 489 end
479 490 end
480 491
481 492 # import sites
482 493 sites = {}
483 494 site_data.each_pair do |id,site|
484 495 s = Site.find_by_name(site[:name])
485 496 if s!=nil
486 497 @import_log << "Found #{site[:name]}\n"
487 498 else
488 499 s = Site.new(:name => site[:name])
489 500 @import_log << "Created #{site[:name]}\n"
490 501 end
491 502 s.password = site[:password]
492 503 s.country = countries[site[:country_id]]
493 504 s.save
494 505 sites[id] = s
495 506 end
496 507
497 508 # import users
498 509 user_data.each_pair do |id,user|
499 510 u = User.find_by_login(user[:login])
500 511 if u!=nil
501 512 @import_log << "Found #{user[:login]}\n"
502 513 else
503 514 u = User.new(:login => user[:login])
504 515 @import_log << "Created #{user[:login]}\n"
505 516 end
506 517 u.full_name = user[:name]
507 518 u.password = user[:password]
508 519 u.country = countries[user[:country_id]]
509 520 u.site = sites[user[:site_id]]
510 521 u.activated = true
511 522 u.email = "empty-#{u.login}@none.com"
512 523 if not u.save
513 524 @import_log << "Errors\n"
514 525 u.errors.each { |attr,msg| @import_log << "#{attr} - #{msg}\n" }
515 526 end
516 527 end
517 528
518 529 end
519 530
520 531 def logout_users(users)
521 532 users.each do |user|
522 533 contest_stat = user.contest_stat(true)
523 534 if contest_stat and !contest_stat.forced_logout
524 535 contest_stat.forced_logout = true
525 536 contest_stat.save
526 537 end
527 538 end
528 539 end
529 540
530 541 def send_contest_update_notification_email(user, contest)
531 542 contest_title_name = GraderConfiguration['contest.name']
532 543 contest_name = contest.name
533 544 mail_subject = t('contest.notification.email_subject', {
534 545 :contest_title_name => contest_title_name,
535 546 :contest_name => contest_name })
536 547 mail_body = t('contest.notification.email_body', {
537 548 :full_name => user.full_name,
538 549 :contest_title_name => contest_title_name,
539 550 :contest_name => contest.name,
540 551 })
541 552
542 553 logger.info mail_body
543 554 send_mail(user.email, mail_subject, mail_body)
544 555 end
545 556
546 557 def find_contest_and_user_from_contest_id(id)
547 558 if id!='none'
548 559 @contest = Contest.find(id)
549 560 else
550 561 @contest = nil
551 562 end
552 563 if @contest
553 564 @users = @contest.users
554 565 else
555 566 @users = User.find_users_with_no_contest
556 567 end
557 568 return [@contest, @users]
558 569 end
559 570
560 571 def gen_csv_from_scorearray(scorearray,problem)
561 572 CSV.generate do |csv|
562 573 #add header
563 574 header = ['User','Name', 'Activated?', 'Logged in', 'Contest']
564 575 problem.each { |p| header << p.name }
565 576 header += ['Total','Passed']
566 577 csv << header
567 578 #add data
568 579 scorearray.each do |sc|
569 580 total = num_passed = 0
570 581 row = Array.new
571 582 sc.each_index do |i|
572 583 if i == 0
573 584 row << sc[i].login
574 585 row << sc[i].full_name
575 586 row << sc[i].activated
576 587 row << (sc[i].try(:contest_stat).try(:started_at).nil? ? 'no' : 'yes')
577 588 row << sc[i].contests.collect {|c| c.name}.join(', ')
578 589 else
579 590 row << sc[i][0]
580 591 total += sc[i][0]
581 592 num_passed += 1 if sc[i][1]
582 593 end
583 594 end
584 595 row << total
585 596 row << num_passed
586 597 csv << row
587 598 end
588 599 end
589 600 end
590 601
591 602 private
592 603 def user_params
593 604 params.require(:user).permit(:login,:password,:password_confirmation,:email, :alias, :full_name,:remark)
594 605 end
595 606 end
@@ -1,221 +1,221
1 1 # Methods added to this helper will be available to all templates in the application.
2 2 module ApplicationHelper
3 3
4 4 #new bootstrap header
5 5 def navbar_user_header
6 6 left_menu = ''
7 7 right_menu = ''
8 8 user = User.find(session[:user_id])
9 9
10 10 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
11 11 left_menu << add_menu("#{I18n.t 'menu.tasks'}", 'tasks', 'list')
12 12 left_menu << add_menu("#{I18n.t 'menu.submissions'}", 'main', 'submission')
13 13 left_menu << add_menu("#{I18n.t 'menu.test'}", 'test', 'index')
14 14 end
15 15
16 16 if GraderConfiguration['right.user_hall_of_fame']
17 17 left_menu << add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof')
18 18 end
19 19
20 20 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
21 21 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
22 22 if GraderConfiguration['system.user_setting_enabled']
23 23 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog')}".html_safe, 'users', 'index', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}})
24 24 end
25 25 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}})
26 26
27 27
28 28 result = content_tag(:ul,left_menu.html_safe,class: 'nav navbar-nav') + content_tag(:ul,right_menu.html_safe,class: 'nav navbar-nav navbar-right')
29 29 end
30 30
31 31 def add_menu(title, controller, action, html_option = {})
32 32 link_option = {controller: controller, action: action}
33 33 html_option[:class] = (html_option[:class] || '') + " active" if current_page?(link_option)
34 34 content_tag(:li, link_to(title,link_option),html_option)
35 35 end
36 36
37 37 def user_header
38 38 menu_items = ''
39 39 user = User.find(session[:user_id])
40 40
41 41 if (user!=nil) and (session[:admin])
42 42 # admin menu
43 43 menu_items << "<b>Administrative task:</b> "
44 44 append_to menu_items, '[Announcements]', 'announcements', 'index'
45 45 append_to menu_items, '[Msg console]', 'messages', 'console'
46 46 append_to menu_items, '[Problems]', 'problems', 'index'
47 47 append_to menu_items, '[Users]', 'user_admin', 'index'
48 48 append_to menu_items, '[Results]', 'user_admin', 'user_stat'
49 49 append_to menu_items, '[Report]', 'report', 'multiple_login'
50 50 append_to menu_items, '[Graders]', 'graders', 'list'
51 51 append_to menu_items, '[Contests]', 'contest_management', 'index'
52 52 append_to menu_items, '[Sites]', 'sites', 'index'
53 53 append_to menu_items, '[System config]', 'configurations', 'index'
54 54 menu_items << "<br/>"
55 55 end
56 56
57 57 # main page
58 58 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
59 59 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
60 60
61 61 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
62 62 append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list'
63 63 append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission'
64 64 append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
65 65 end
66 66
67 67 if GraderConfiguration['right.user_hall_of_fame']
68 68 append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof'
69 69 end
70 70 append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
71 71
72 72 if GraderConfiguration['system.user_setting_enabled']
73 73 append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index'
74 74 end
75 75 append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login'
76 76
77 77 menu_items.html_safe
78 78 end
79 79
80 80 def append_to(option,label, controller, action)
81 81 option << ' ' if option!=''
82 82 option << link_to_unless_current(label,
83 83 :controller => controller,
84 84 :action => action)
85 85 end
86 86
87 87 def format_short_time(time)
88 88 now = Time.now.gmtime
89 89 st = ''
90 90 if (time.yday != now.yday) or
91 91 (time.year != now.year)
92 92 st = time.strftime("%x ")
93 93 end
94 94 st + time.strftime("%X")
95 95 end
96 96
97 97 def format_short_duration(duration)
98 98 return '' if duration==nil
99 99 d = duration.to_f
100 100 return Time.at(d).gmtime.strftime("%X")
101 101 end
102 102
103 103 def read_textfile(fname,max_size=2048)
104 104 begin
105 105 File.open(fname).read(max_size)
106 106 rescue
107 107 nil
108 108 end
109 109 end
110 110
111 111 def toggle_button(on,toggle_url,id, option={})
112 112 btn_size = option[:size] || 'btn-xs'
113 113 link_to (on ? "Yes" : "No"), toggle_url,
114 114 {class: "btn btn-block #{btn_size} btn-#{on ? 'success' : 'default'} ajax-toggle",
115 115 id: id,
116 116 data: {remote: true, method: 'get'}}
117 117 end
118 118
119 119 def get_ace_mode(language)
120 120 # return ace mode string from Language
121 121
122 122 case language.pretty_name
123 123 when 'Pascal'
124 124 'ace/mode/pascal'
125 125 when 'C++','C'
126 126 'ace/mode/c_cpp'
127 127 when 'Ruby'
128 128 'ace/mode/ruby'
129 129 when 'Python'
130 130 'ace/mode/python'
131 131 when 'Java'
132 132 'ace/mode/java'
133 133 else
134 134 'ace/mode/c_cpp'
135 135 end
136 136 end
137 137
138 138
139 139 def user_title_bar(user)
140 140 header = ''
141 141 time_left = ''
142 142
143 143 #
144 144 # if the contest is over
145 145 if GraderConfiguration.time_limit_mode?
146 146 if user.contest_finished?
147 147 header = <<CONTEST_OVER
148 148 <tr><td colspan="2" align="center">
149 149 <span class="contest-over-msg">THE CONTEST IS OVER</span>
150 150 </td></tr>
151 151 CONTEST_OVER
152 152 end
153 153 if !user.contest_started?
154 154 time_left = "&nbsp;&nbsp;" + (t 'title_bar.contest_not_started')
155 155 else
156 156 time_left = "&nbsp;&nbsp;" + (t 'title_bar.remaining_time') +
157 157 " #{format_short_duration(user.contest_time_left)}"
158 158 end
159 159 end
160 160
161 161 #
162 162 # if the contest is in the anaysis mode
163 163 if GraderConfiguration.analysis_mode?
164 164 header = <<ANALYSISMODE
165 165 <tr><td colspan="2" align="center">
166 166 <span class="contest-over-msg">ANALYSIS MODE</span>
167 167 </td></tr>
168 168 ANALYSISMODE
169 169 end
170 170
171 171 contest_name = GraderConfiguration['contest.name']
172 172
173 173 #
174 174 # build real title bar
175 175 result = <<TITLEBAR
176 176 <div class="title">
177 177 <table>
178 178 #{header}
179 179 <tr>
180 180 <td class="left-col">
181 181 #{user.full_name}<br/>
182 182 #{t 'title_bar.current_time'} #{format_short_time(Time.zone.now)}
183 183 #{time_left}
184 184 <br/>
185 185 </td>
186 186 <td class="right-col">#{contest_name}</td>
187 187 </tr>
188 188 </table>
189 189 </div>
190 190 TITLEBAR
191 191 result.html_safe
192 192 end
193 193
194 194 def markdown(text)
195 195 markdown = RDiscount.new(text)
196 196 markdown.to_html.html_safe
197 197 end
198 198
199 199
200 200 BOOTSTRAP_FLASH_MSG = {
201 201 success: 'alert-success',
202 202 error: 'alert-danger',
203 - alert: 'alert-block',
203 + alert: 'alert-danger',
204 204 notice: 'alert-info'
205 205 }
206 206
207 207 def bootstrap_class_for(flash_type)
208 208 BOOTSTRAP_FLASH_MSG.fetch(flash_type.to_sym, flash_type.to_s)
209 209 end
210 210
211 211 def flash_messages
212 212 flash.each do |msg_type, message|
213 213 concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do
214 214 concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' })
215 215 concat message
216 216 end)
217 217 end
218 218 nil
219 219 end
220 220
221 221 end
@@ -1,5 +1,13
1 1 class Group < ActiveRecord::Base
2 - has_and_belongs_to_many :problems
3 - has_and_belongs_to_many :users
2 + has_many :groups_problems, class_name: GroupProblem
3 + has_many :problems, :through => :groups_problems
4 +
5 + has_many :groups_users, class_name: GroupUser
6 + has_many :users, :through => :groups_users
7 +
8 + #has_and_belongs_to_many :problems
9 + #has_and_belongs_to_many :users
10 +
11 +
4 12 end
5 13
@@ -1,137 +1,141
1 1 class Problem < ActiveRecord::Base
2 2
3 3 belongs_to :description
4 4 has_and_belongs_to_many :contests, :uniq => true
5 - has_and_belongs_to_many :groups
5 +
6 + #has_and_belongs_to_many :groups
7 + has_many :groups_problems, class_name: GroupProblem
8 + has_many :groups, :through => :groups_problems
9 +
6 10 has_many :test_pairs, :dependent => :delete_all
7 11 has_many :testcases, :dependent => :destroy
8 12
9 13 validates_presence_of :name
10 14 validates_format_of :name, :with => /\A\w+\z/
11 15 validates_presence_of :full_name
12 16
13 17 scope :available, -> { where(available: true) }
14 18
15 19 DEFAULT_TIME_LIMIT = 1
16 20 DEFAULT_MEMORY_LIMIT = 32
17 21
18 22 def self.available_problems
19 23 available.order(date_added: :desc).order(:name)
20 24 #Problem.available.all(:order => "date_added DESC, name ASC")
21 25 end
22 26
23 27 def self.create_from_import_form_params(params, old_problem=nil)
24 28 org_problem = old_problem || Problem.new
25 29 import_params, problem = Problem.extract_params_and_check(params,
26 30 org_problem)
27 31
28 32 if !problem.errors.empty?
29 33 return problem, 'Error importing'
30 34 end
31 35
32 36 problem.full_score = 100
33 37 problem.date_added = Time.new
34 38 problem.test_allowed = true
35 39 problem.output_only = false
36 40 problem.available = false
37 41
38 42 if not problem.save
39 43 return problem, 'Error importing'
40 44 end
41 45
42 46 import_to_db = params.has_key? :import_to_db
43 47
44 48 importer = TestdataImporter.new(problem)
45 49
46 50 if not importer.import_from_file(import_params[:file],
47 51 import_params[:time_limit],
48 52 import_params[:memory_limit],
49 53 import_params[:checker_name],
50 54 import_to_db)
51 55 problem.errors.add(:base,'Import error.')
52 56 end
53 57
54 58 return problem, importer.log_msg
55 59 end
56 60
57 61 def self.download_file_basedir
58 62 return "#{Rails.root}/data/tasks"
59 63 end
60 64
61 65 def get_submission_stat
62 66 result = Hash.new
63 67 #total number of submission
64 68 result[:total_sub] = Submission.where(problem_id: self.id).count
65 69 result[:attempted_user] = Submission.where(problem_id: self.id).group(:user_id)
66 70 result[:pass] = Submission.where(problem_id: self.id).where("points >= ?",self.full_score).count
67 71 return result
68 72 end
69 73
70 74 def long_name
71 75 "[#{name}] #{full_name}"
72 76 end
73 77
74 78 protected
75 79
76 80 def self.to_i_or_default(st, default)
77 81 if st!=''
78 82 result = st.to_i
79 83 end
80 84 result ||= default
81 85 end
82 86
83 87 def self.to_f_or_default(st, default)
84 88 if st!=''
85 89 result = st.to_f
86 90 end
87 91 result ||= default
88 92 end
89 93
90 94 def self.extract_params_and_check(params, problem)
91 95 time_limit = Problem.to_f_or_default(params[:time_limit],
92 96 DEFAULT_TIME_LIMIT)
93 97 memory_limit = Problem.to_i_or_default(params[:memory_limit],
94 98 DEFAULT_MEMORY_LIMIT)
95 99
96 100 if time_limit<=0 or time_limit >60
97 101 problem.errors.add(:base,'Time limit out of range.')
98 102 end
99 103
100 104 if memory_limit==0 and params[:memory_limit]!='0'
101 105 problem.errors.add(:base,'Memory limit format errors.')
102 106 elsif memory_limit<=0 or memory_limit >512
103 107 problem.errors.add(:base,'Memory limit out of range.')
104 108 end
105 109
106 110 if params[:file]==nil or params[:file]==''
107 111 problem.errors.add(:base,'No testdata file.')
108 112 end
109 113
110 114 checker_name = 'text'
111 115 if ['text','float'].include? params[:checker]
112 116 checker_name = params[:checker]
113 117 end
114 118
115 119 file = params[:file]
116 120
117 121 if !problem.errors.empty?
118 122 return nil, problem
119 123 end
120 124
121 125 problem.name = params[:name]
122 126 if params[:full_name]!=''
123 127 problem.full_name = params[:full_name]
124 128 else
125 129 problem.full_name = params[:name]
126 130 end
127 131
128 132 return [{
129 133 :time_limit => time_limit,
130 134 :memory_limit => memory_limit,
131 135 :file => file,
132 136 :checker_name => checker_name
133 137 },
134 138 problem]
135 139 end
136 140
137 141 end
@@ -1,394 +1,397
1 1 require 'digest/sha1'
2 2 require 'net/pop'
3 3 require 'net/https'
4 4 require 'net/http'
5 5 require 'json'
6 6
7 7 class User < ActiveRecord::Base
8 8
9 9 has_and_belongs_to_many :roles
10 - has_and_belongs_to_many :groups
10 +
11 + #has_and_belongs_to_many :groups
12 + has_many :groups_users, class_name: GroupUser
13 + has_many :groups, :through => :groups_users
11 14
12 15 has_many :test_requests, -> {order(submitted_at: DESC)}
13 16
14 17 has_many :messages, -> { order(created_at: DESC) },
15 18 :class_name => "Message",
16 19 :foreign_key => "sender_id"
17 20
18 21 has_many :replied_messages, -> { order(created_at: DESC) },
19 22 :class_name => "Message",
20 23 :foreign_key => "receiver_id"
21 24
22 25 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
23 26
24 27 belongs_to :site
25 28 belongs_to :country
26 29
27 30 has_and_belongs_to_many :contests, -> { order(:name); uniq}
28 31
29 32 scope :activated_users, -> {where activated: true}
30 33
31 34 validates_presence_of :login
32 35 validates_uniqueness_of :login
33 36 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
34 37 validates_length_of :login, :within => 3..30
35 38
36 39 validates_presence_of :full_name
37 40 validates_length_of :full_name, :minimum => 1
38 41
39 42 validates_presence_of :password, :if => :password_required?
40 43 validates_length_of :password, :within => 4..20, :if => :password_required?
41 44 validates_confirmation_of :password, :if => :password_required?
42 45
43 46 validates_format_of :email,
44 47 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
45 48 :if => :email_validation?
46 49 validate :uniqueness_of_email_from_activated_users,
47 50 :if => :email_validation?
48 51 validate :enough_time_interval_between_same_email_registrations,
49 52 :if => :email_validation?
50 53
51 54 # these are for ytopc
52 55 # disable for now
53 56 #validates_presence_of :province
54 57
55 58 attr_accessor :password
56 59
57 60 before_save :encrypt_new_password
58 61 before_save :assign_default_site
59 62 before_save :assign_default_contest
60 63
61 64 # this is for will_paginate
62 65 cattr_reader :per_page
63 66 @@per_page = 50
64 67
65 68 def self.authenticate(login, password)
66 69 user = find_by_login(login)
67 70 if user
68 71 return user if user.authenticated?(password)
69 72 if user.authenticated_by_cucas?(password) or user.authenticated_by_pop3?(password)
70 73 user.password = password
71 74 user.save
72 75 return user
73 76 end
74 77 end
75 78 end
76 79
77 80 def authenticated?(password)
78 81 if self.activated
79 82 hashed_password == User.encrypt(password,self.salt)
80 83 else
81 84 false
82 85 end
83 86 end
84 87
85 88 def authenticated_by_pop3?(password)
86 89 Net::POP3.enable_ssl
87 90 pop = Net::POP3.new('pops.it.chula.ac.th')
88 91 authen = true
89 92 begin
90 93 pop.start(login, password)
91 94 pop.finish
92 95 return true
93 96 rescue
94 97 return false
95 98 end
96 99 end
97 100
98 101 def authenticated_by_cucas?(password)
99 102 url = URI.parse('https://www.cas.chula.ac.th/cas/api/?q=studentAuthenticate')
100 103 appid = '41508763e340d5858c00f8c1a0f5a2bb'
101 104 appsecret ='d9cbb5863091dbe186fded85722a1e31'
102 105 post_args = {
103 106 'appid' => appid,
104 107 'appsecret' => appsecret,
105 108 'username' => login,
106 109 'password' => password
107 110 }
108 111
109 112 #simple call
110 113 begin
111 114 http = Net::HTTP.new('www.cas.chula.ac.th', 443)
112 115 http.use_ssl = true
113 116 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
114 117 result = [ ]
115 118 http.start do |http|
116 119 req = Net::HTTP::Post.new('/cas/api/?q=studentAuthenticate')
117 120 param = "appid=#{appid}&appsecret=#{appsecret}&username=#{login}&password=#{password}"
118 121 resp = http.request(req,param)
119 122 result = JSON.parse resp.body
120 123 end
121 124 return true if result["type"] == "beanStudent"
122 125 rescue => e
123 126 return false
124 127 end
125 128 return false
126 129 end
127 130
128 131 def admin?
129 132 self.roles.detect {|r| r.name == 'admin' }
130 133 end
131 134
132 135 def email_for_editing
133 136 if self.email==nil
134 137 "(unknown)"
135 138 elsif self.email==''
136 139 "(blank)"
137 140 else
138 141 self.email
139 142 end
140 143 end
141 144
142 145 def email_for_editing=(e)
143 146 self.email=e
144 147 end
145 148
146 149 def alias_for_editing
147 150 if self.alias==nil
148 151 "(unknown)"
149 152 elsif self.alias==''
150 153 "(blank)"
151 154 else
152 155 self.alias
153 156 end
154 157 end
155 158
156 159 def alias_for_editing=(e)
157 160 self.alias=e
158 161 end
159 162
160 163 def activation_key
161 164 if self.hashed_password==nil
162 165 encrypt_new_password
163 166 end
164 167 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
165 168 end
166 169
167 170 def verify_activation_key(key)
168 171 key == activation_key
169 172 end
170 173
171 174 def self.random_password(length=5)
172 175 chars = 'abcdefghjkmnopqrstuvwxyz'
173 176 password = ''
174 177 length.times { password << chars[rand(chars.length - 1)] }
175 178 password
176 179 end
177 180
178 181 def self.find_non_admin_with_prefix(prefix='')
179 182 users = User.all
180 183 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
181 184 end
182 185
183 186 # Contest information
184 187
185 188 def self.find_users_with_no_contest()
186 189 users = User.all
187 190 return users.find_all { |u| u.contests.length == 0 }
188 191 end
189 192
190 193
191 194 def contest_time_left
192 195 if GraderConfiguration.contest_mode?
193 196 return nil if site==nil
194 197 return site.time_left
195 198 elsif GraderConfiguration.indv_contest_mode?
196 199 time_limit = GraderConfiguration.contest_time_limit
197 200 if time_limit == nil
198 201 return nil
199 202 end
200 203 if contest_stat==nil or contest_stat.started_at==nil
201 204 return (Time.now.gmtime + time_limit) - Time.now.gmtime
202 205 else
203 206 finish_time = contest_stat.started_at + time_limit
204 207 current_time = Time.now.gmtime
205 208 if current_time > finish_time
206 209 return 0
207 210 else
208 211 return finish_time - current_time
209 212 end
210 213 end
211 214 else
212 215 return nil
213 216 end
214 217 end
215 218
216 219 def contest_finished?
217 220 if GraderConfiguration.contest_mode?
218 221 return false if site==nil
219 222 return site.finished?
220 223 elsif GraderConfiguration.indv_contest_mode?
221 224 return false if self.contest_stat(true)==nil
222 225 return contest_time_left == 0
223 226 else
224 227 return false
225 228 end
226 229 end
227 230
228 231 def contest_started?
229 232 if GraderConfiguration.indv_contest_mode?
230 233 stat = self.contest_stat
231 234 return ((stat != nil) and (stat.started_at != nil))
232 235 elsif GraderConfiguration.contest_mode?
233 236 return true if site==nil
234 237 return site.started
235 238 else
236 239 return true
237 240 end
238 241 end
239 242
240 243 def update_start_time
241 244 stat = self.contest_stat
242 245 if stat.nil? or stat.started_at.nil?
243 246 stat ||= UserContestStat.new(:user => self)
244 247 stat.started_at = Time.now.gmtime
245 248 stat.save
246 249 end
247 250 end
248 251
249 252 def problem_in_user_contests?(problem)
250 253 problem_contests = problem.contests.all
251 254
252 255 if problem_contests.length == 0 # this is public contest
253 256 return true
254 257 end
255 258
256 259 contests.each do |contest|
257 260 if problem_contests.find {|c| c.id == contest.id }
258 261 return true
259 262 end
260 263 end
261 264 return false
262 265 end
263 266
264 267 def available_problems_group_by_contests
265 268 contest_problems = []
266 269 pin = {}
267 270 contests.enabled.each do |contest|
268 271 available_problems = contest.problems.available
269 272 contest_problems << {
270 273 :contest => contest,
271 274 :problems => available_problems
272 275 }
273 276 available_problems.each {|p| pin[p.id] = true}
274 277 end
275 278 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
276 279 contest_problems << {
277 280 :contest => nil,
278 281 :problems => other_avaiable_problems
279 282 }
280 283 return contest_problems
281 284 end
282 285
283 286 def solve_all_available_problems?
284 287 available_problems.each do |p|
285 288 u = self
286 289 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
287 290 return false if !p or !sub or sub.points < p.full_score
288 291 end
289 292 return true
290 293 end
291 294
292 295 def available_problems
293 296 if not GraderConfiguration.multicontests?
294 297 if GraderConfiguration.use_problem_group?
295 298 return available_problems_in_group
296 299 else
297 300 return Problem.available_problems
298 301 end
299 302 else
300 303 contest_problems = []
301 304 pin = {}
302 305 contests.enabled.each do |contest|
303 306 contest.problems.available.each do |problem|
304 307 if not pin.has_key? problem.id
305 308 contest_problems << problem
306 309 end
307 310 pin[problem.id] = true
308 311 end
309 312 end
310 313 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
311 314 return contest_problems + other_avaiable_problems
312 315 end
313 316 end
314 317
315 318 def available_problems_in_group
316 319 problem = []
317 320 self.groups.each do |group|
318 321 group.problems.where(available: true).each { |p| problem << p }
319 322 end
320 323 problem.uniq!
321 324 if problem
322 325 problem.sort! do |a,b|
323 326 case
324 327 when a.date_added < b.date_added
325 328 1
326 329 when a.date_added > b.date_added
327 330 -1
328 331 else
329 332 a.name <=> b.name
330 333 end
331 334 end
332 335 return problem
333 336 else
334 337 return []
335 338 end
336 339 end
337 340
338 341 def can_view_problem?(problem)
339 342 if not GraderConfiguration.multicontests?
340 343 return problem.available
341 344 else
342 345 return problem_in_user_contests? problem
343 346 end
344 347 end
345 348
346 349 def self.clear_last_login
347 350 User.update_all(:last_ip => nil)
348 351 end
349 352
350 353 protected
351 354 def encrypt_new_password
352 355 return if password.blank?
353 356 self.salt = (10+rand(90)).to_s
354 357 self.hashed_password = User.encrypt(self.password,self.salt)
355 358 end
356 359
357 360 def assign_default_site
358 361 # have to catch error when migrating (because self.site is not available).
359 362 begin
360 363 if self.site==nil
361 364 self.site = Site.find_by_name('default')
362 365 if self.site==nil
363 366 self.site = Site.find(1) # when 'default has be renamed'
364 367 end
365 368 end
366 369 rescue
367 370 end
368 371 end
369 372
370 373 def assign_default_contest
371 374 # have to catch error when migrating (because self.site is not available).
372 375 begin
373 376 if self.contests.length == 0
374 377 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
375 378 if default_contest
376 379 self.contests = [default_contest]
377 380 end
378 381 end
379 382 rescue
380 383 end
381 384 end
382 385
383 386 def password_required?
384 387 self.hashed_password.blank? || !self.password.blank?
385 388 end
386 389
387 390 def self.encrypt(string,salt)
388 391 Digest::SHA1.hexdigest(salt + string)
389 392 end
390 393
391 394 def uniqueness_of_email_from_activated_users
392 395 user = User.activated_users.find_by_email(self.email)
393 396 if user and (user.login != self.login)
394 397 self.errors.add(:base,"Email has already been taken")
@@ -1,54 +1,54
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 - %h1 Listing problems
3 + %h1 Problems
4 4 %p
5 - = link_to 'New problem', new_problem_path, class: 'btn btn-default btn-sm'
6 - = link_to 'Manage problems', { action: 'manage'}, class: 'btn btn-default btn-sm'
7 - = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-default btn-sm'
5 + = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-success btn-sm'
6 + = link_to 'New problem', new_problem_path, class: 'btn btn-success btn-sm'
7 + = link_to 'Bulk Manage', { action: 'manage'}, class: 'btn btn-info btn-sm'
8 8 = link_to 'Turn off all problems', {:action => 'turn_all_off'}, class: 'btn btn-default btn-sm'
9 9 = link_to 'Turn on all problems', {:action => 'turn_all_on'}, class: 'btn btn-default btn-sm'
10 10 .submitbox
11 11 = form_tag :action => 'quick_create' do
12 12 %b Quick New:
13 13 %label{:for => "problem_name"} Name
14 14 = text_field 'problem', 'name'
15 15 |
16 16 %label{:for => "problem_full_name"} Full name
17 17 = text_field 'problem', 'full_name'
18 18 = submit_tag "Create"
19 19 %table.table.table-condense.table-hover
20 20 %thead
21 21 %th Name
22 22 %th Full name
23 23 %th.text-right Full score
24 24 %th Date added
25 25 %th.text-center
26 26 Avail?
27 27 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user submits to this problem?' } [?]
28 28 %th.text-center
29 29 View Data?
30 30 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user view the testcase of this problem?' } [?]
31 31 %th.text-center
32 32 Test?
33 33 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user uses test interface on this problem?' } [?]
34 34 - if GraderConfiguration.multicontests?
35 35 %th Contests
36 36 - for problem in @problems
37 37 %tr{:class => "#{(problem.available) ? "success" : "danger"}", :id => "prob-#{problem.id}", :name => "prob-#{problem.id}"}
38 38 - @problem=problem
39 39 %td= problem.name #in_place_editor_field :problem, :name, {}, :rows=>1
40 40 %td= problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
41 41 %td.text-right= problem.full_score #in_place_editor_field :problem, :full_score, {}, :rows=>1
42 42 %td= problem.date_added
43 43 %td= toggle_button(@problem.available?, toggle_problem_path(@problem), "problem-avail-#{@problem.id}")
44 44 %td= toggle_button(@problem.view_testcase?, toggle_view_testcase_problem_path(@problem), "problem-view-testcase-#{@problem.id}")
45 45 %td= toggle_button(@problem.test_allowed?, toggle_test_problem_path(@problem), "problem-test-#{@problem.id}")
46 46 - if GraderConfiguration.multicontests?
47 47 %td
48 48 = problem.contests.collect { |c| c.name }.join(', ')
49 49 %td= link_to 'Stat', {:action => 'stat', :id => problem.id}, class: 'btn btn-info btn-xs btn-block'
50 50 %td= link_to 'Show', {:action => 'show', :id => problem}, class: 'btn btn-info btn-xs btn-block'
51 51 %td= link_to 'Edit', {:action => 'edit', :id => problem}, class: 'btn btn-info btn-xs btn-block'
52 52 %td= link_to 'Destroy', { :action => 'destroy', :id => problem }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block'
53 53 %br/
54 54 = link_to '[New problem]', :action => 'new'
@@ -1,90 +1,94
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 3 = javascript_include_tag 'local_jquery'
4 4
5 5 :javascript
6 6 $(document).ready( function() {
7 7 function shiftclick(start,stop,value) {
8 8 $('tr input').each( function(id,input) {
9 9 var $input=$(input);
10 10 var iid=parseInt($input.attr('id').split('-')[2]);
11 11 if(iid>=start&&iid<=stop){
12 12 $input.prop('checked',value)
13 13 }
14 14 });
15 15 }
16 16
17 17 $('tr input').click( function(e) {
18 18 if (e.shiftKey) {
19 19 stop = parseInt($(this).attr('id').split('-')[2]);
20 20 var orig_stop = stop
21 21 if (typeof start !== 'undefined') {
22 22 if (start > stop) {
23 23 var tmp = start;
24 24 start = stop;
25 25 stop = tmp;
26 26 }
27 27 shiftclick(start,stop,$(this).is(':checked') )
28 28 }
29 29 start = orig_stop
30 30 } else {
31 31 start = parseInt($(this).attr('id').split('-')[2]);
32 32 }
33 33 });
34 34 });
35 35
36 36
37 37 %h1 Manage problems
38 38
39 - %p= link_to '[Back to problem list]', :action => 'list'
39 + %p= link_to '[Back to problem list]', problems_path
40 40
41 41 = form_tag :action=>'do_manage' do
42 - .submitbox.panel
42 + .panel.panel-primary
43 + .panel-heading
44 + Action
45 + .panel-body
46 + .submit-box
43 47 What do you want to do to the selected problem?
44 48 %br/
45 49 (You can shift-click to select a range of problems)
46 50 %ul
47 51 %li
48 52 Change date added to
49 53 = select_date Date.current, :prefix => 'date_added'
50 54 &nbsp;&nbsp;&nbsp;
51 - = submit_tag 'Change', :name => 'change_date_added'
55 + = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-default'
52 56 %li
53 57 Set available to
54 - = submit_tag 'True', :name => 'enable_problem'
55 - = submit_tag 'False', :name => 'disable_problem'
58 + = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-default'
59 + = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-default'
56 60
57 61 - if GraderConfiguration.multicontests?
58 62 %li
59 63 Add to
60 64 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
61 - = submit_tag 'Add', :name => 'add_to_contest'
65 + = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-default'
62 66 %li
63 67 Add problems to group
64 68 = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2'
65 - = submit_tag 'Add', name: 'add_group'
69 + = submit_tag 'Add', name: 'add_group', class: 'btn btn-default'
66 70
67 71
68 72 %table.table.table-hover
69 73 %tr{style: "text-align: left;"}
70 74 %th= check_box_tag 'select_all'
71 75 %th Name
72 76 %th Full name
73 77 %th Available
74 78 %th Date added
75 79 - if GraderConfiguration.multicontests?
76 80 %th Contests
77 81
78 82 - num = 0
79 83 - for problem in @problems
80 84 - num += 1
81 85 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
82 86 %td= check_box_tag "prob-#{problem.id}-#{num}"
83 87 %td= problem.name
84 88 %td= problem.full_name
85 89 %td= problem.available
86 90 %td= problem.date_added
87 91 - if GraderConfiguration.multicontests?
88 92 %td
89 93 - problem.contests.each do |contest|
90 94 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
You need to be logged in to leave comments. Login now