/
baskets_controller.rb
610 lines (517 loc) · 21.6 KB
/
baskets_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
class BasketsController < ApplicationController
permit "site_admin or admin of :current_basket", :only => [:edit, :update, :homepage_options, :destroy,
:add_index_topic, :appearance, :update_appearance,
:set_settings]
before_filter :redirect_if_current_user_cant_add_or_request_basket, :only => [:new, :create]
after_filter :remove_robots_txt_cache, :only => [:create, :update, :destroy]
# Get the Privacy Controls helper for the add item forms
helper :privacy_controls
include EmailController
include WorkerControllerHelpers
# include TaggingController
include AnonymousFinishedAfterFilter
def index
redirect_to :action => 'list'
end
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
# EOIN: FIXME: verify is not in rails3 but we do need to limit the HTTP verbs in routing. This will need to be addressed before we go live
# verify :method => :post, :only => [ :destroy, :create, :update ],
# :redirect_to => { :action => :list }
def list
list_baskets
@rss_tag_auto = rss_tag(:replace_page_with_rss => true)
@rss_tag_link = rss_tag(:replace_page_with_rss => true, :auto_detect => false)
@requested_count = Basket.count(:conditions => "status = 'requested'")
@rejected_count = Basket.count(:conditions => "status = 'rejected'")
end
def rss
@number_per_page = 100
@baskets = Basket.all(:limit => @number_per_page, :order => 'id DESC')
respond_to do |format|
format.xml
end
end
def show
redirect_to_default_all
end
def new
@profiles = Profile.all
params[:basket_profile] = @profiles.first.id if @profiles.size == 1
@basket = Basket.new
prepare_and_validate_profile_for(:edit)
end
def render_basket_form
new
respond_to do |format|
format.js do
render :update do |page|
page.replace_html 'basket_form', :partial => 'new_form'
end
end
end
end
def create
convert_text_fields_to_boolean
# if an site admin makes a basket, make sure the basket is instantly approved
if basket_policy_request_with_permissions?
params[:basket][:status] = 'requested'
else
params[:basket][:status] = 'approved'
end
params[:basket][:creator_id] = current_user.id
@basket = Basket.new(params[:basket])
@profiles = Profile.all
prepare_and_validate_profile_for(:edit)
if @basket.save
# Reload to ensure basket.creator is updated.
@basket.reload
set_settings
# Set this baskets profile mapping
if params[:basket_profile]
profile = Profile.find_by_id(params[:basket_profile])
@basket.profiles << profile if profile
end
# if basket creator is admin or creation not moderated, make creator basket admin
@basket.accepts_role('admin', current_user) if SystemSetting.basket_creation_policy == 'open' || @site_admin
# if an site admin makes a basket, make sure emailing notifications are skipped
if basket_policy_request_with_permissions?
@site_basket.administrators.each do |administrator|
UserNotifier.basket_notification_to(administrator, current_user, @basket, 'request').deliver
end
flash[:notice] = t('baskets_controller.create.to_be_reviewed')
redirect_to "/#{@site_basket.urlified_name}"
else
if !@site_admin
@site_basket.administrators.each do |administrator|
UserNotifier.basket_notification_to(administrator, current_user, @basket, 'created').deliver
end
end
flash[:notice] = t('baskets_controller.create.created')
redirect_to :urlified_name => @basket.urlified_name, :controller => 'baskets', :action => 'edit', :id => @basket
end
else
render :action => 'new'
end
end
def edit
appropriate_basket
@topics = @basket.topics
@index_topic = @basket.index_topic
prepare_and_validate_profile_for(:edit)
end
def homepage_options
appropriate_basket
@topics = @basket.topics
@index_topic = @basket.index_topic
prepare_and_validate_profile_for(:homepage_options)
end
def update
params[:source_form] ||= 'edit'
params[:basket] ||= Hash.new
@basket = Basket.find(params[:id])
@topics = @basket.topics
original_name = @basket.name
unless params[:accept_basket].blank?
params[:basket][:status] = 'approved'
@basket.accepts_role('admin', @basket.creator)
end
unless params[:reject_basket].blank?
params[:basket][:status] = 'rejected'
end
# have to update zoom records for things in the basket
# in two steps
# delete old record before basket.urlified_name has changed
# as well as caches
# because item.zoom_destroy needs original record to match
# then after update, create new zoom records with new urlified_name
if !params[:basket][:name].blank? and original_name != params[:basket][:name]
ZOOM_CLASSES.each do |zoom_class|
basket_items = @basket.send(zoom_class.tableize)
basket_items.each do |item|
zoom_destroy_for(item)
end
end
end
# Because we dont edit the basket content on edit form, skip sanitizing the content
# to prevent changes in edit from being locked out
params[:basket][:do_not_sanitize] = true if params[:source_form] == 'edit'
convert_text_fields_to_boolean if params[:source_form] == 'edit'
prepare_and_validate_profile_for(params[:source_form].to_sym)
# clear out existing feeds before looping over the new set
# it is important this only run if the source form was the homepage options
@basket.feeds.destroy_all if params[:source_form].to_sym == :homepage_options
if @basket.update_attributes(params[:basket])
# Reload to ensure basket.name is updated and not the previous
# basket name.
@basket.reload
set_settings
# clear slideshow in session
# in case the user user changes how images should be ordered
session[:slideshow] = nil
session[:image_slideshow] = nil
# @basket.name has changed
if original_name != @basket.name
# update zoom records for basket items
# to match new basket.urlified_name
ZOOM_CLASSES.each do |zoom_class|
basket_items = @basket.send(zoom_class.tableize)
basket_items.each do |item|
# item.prepare_and_save_to_zoom
# switched to async backgroundrb worker for search record set up
update_search_record_for(item)
end
end
end
# We send the emails right before a redirect so
# it doesn't break anything if the emailing fails
unless params[:accept_basket].blank?
UserNotifier.basket_notification_to(@basket.creator, current_user, @basket, 'approved').deliver
end
unless params[:reject_basket].blank?
UserNotifier.basket_notification_to(@basket.creator, current_user, @basket, 'rejected').deliver
end
# Add this last because it takes the longest time to process
@basket.feeds.each do |feed|
feed.update_feed
MiddleMan.new_worker( :worker => :feeds_worker, :worker_key => feed.to_worker_key, :data => feed.id )
end
flash[:notice] = t('baskets_controller.update.updated')
redirect_to "/#{@basket.urlified_name}/"
else
render :action => params[:source_form]
end
end
def destroy
@basket = Basket.find(params[:id])
# dependent destroy isn't sufficient
# to delete zoom items from the zoom_db
# has to be done in the controller
# because of the reliance on preparing the zoom record
ZOOM_CLASSES.each do |zoom_class|
# skip comments, they should be destroyed by their parent items
if zoom_class != 'Comment'
zoom_items = @basket.send(zoom_class.tableize)
if zoom_items.size > 0
zoom_items.each do |item|
@successful = zoom_item_destroy(item)
if !@successful
break
end
end
else
@successful = true
end
end
if !@successful
break
end
end
if @successful
@successful = @basket.destroy
end
if @successful
flash[:notice] = t('baskets_controller.destroy.destroyed')
redirect_to '/'
end
end
def add_index_topic
@topic = Topic.find(params[:topic])
@basket = Basket.find(params[:index_for_basket])
@successful = @basket.update_index_topic(@topic)
if @successful
# this action saves a new version of the topic
# add this as a contribution
@topic.add_as_contributor(current_user)
flash[:notice] = t('baskets_controller.add_index_topic.created')
if params[:return_to_homepage]
redirect_to "/#{@basket.urlified_name}"
else
redirect_to :action => 'homepage_options', :controller => 'baskets', :id => params[:index_for_basket]
end
end
end
def appearance
appropriate_basket
prepare_and_validate_profile_for(:appearance)
end
def update_appearance
@basket = Basket.find(params[:id])
do_not_sanitize = (params[:settings][:do_not_sanitize_footer_content] == 'true')
original_html = params[:settings][:additional_footer_content]
sanitized_html = original_html
unless do_not_sanitize && @site_admin || original_html.blank?
sanitized_html = original_html.sanitize
params[:settings][:additional_footer_content] = sanitized_html
end
prepare_and_validate_profile_for(:appearance)
set_settings
flash[:notice] = t('baskets_controller.update_appearance.updated')
logger.debug("sanitized yes") if original_html != sanitized_html
flash[:notice] += t('baskets_controller.update_appearance.sanitized') if original_html != sanitized_html
redirect_to :action => :appearance
end
def choose_type
# give the user the option to add the item to any place the have access to
@basket_list = Array.new
if @site_admin
@basket_list = Basket.list_as_names_and_urlified_names
else
all_baskets_hash = Hash.new
# get the add item setting for each of the baskets the user has access to
Basket.find_all_by_urlified_name(@basket_access_hash.stringify_keys.keys).each do |b|
all_baskets_hash[b.urlified_name.to_sym] = { :basket => b, :privacy => b.setting(:show_add_links) }
end
# collect baskets that they can see add item controls for
@basket_list = @basket_access_hash.collect do |basket_urlified_name, basket_hash|
current_user_is?(all_baskets_hash[basket_urlified_name.to_sym][:privacy], all_baskets_hash[basket_urlified_name.to_sym][:basket]) \
? [basket_hash[:basket_name], basket_urlified_name.to_s] \
: nil
end.compact
end
@item_types = Array.new
ZOOM_CLASSES.each { |zoom_class| @item_types << [zoom_class_humanize(zoom_class),
zoom_class_controller(zoom_class)] if zoom_class != 'Comment' }
return unless request.post?
redirect_to :urlified_name => params[:new_item_basket],
:controller => params[:new_item_controller],
:action => 'new',
:relate_to_item => params[:relate_to_item],
:relate_to_type => params[:relate_to_type],
:related_item_private => params[:related_item_private]
end
def render_item_form
@new_item_basket = params[:new_item_basket]
@new_item_controller = params[:new_item_controller]
@relate_to_item = params[:relate_to_item]
@relate_to_type = params[:relate_to_type]
@related_item_private = params[:related_item_private]
params[:topic] = Hash.new
params[:topic][:topic_type_id] = params[:new_item_topic_type]
@item_class = zoom_class_from_controller(@new_item_controller)
@item = @item_class.constantize.new
@content_type = ContentType.find_by_class_name(@item_class)
respond_to do |format|
format.html { render :partial => 'topics/form', :layout => 'application' }
format.js
end
end
# the start of a page
# where the user is told they don't have access to requested action
# and they are presented with options to continue
# in the future this will present the join policy of the basket, etc
# now it only says "login as different user or contact an administrator"
def permission_denied
end
def set_settings
return unless params[:settings]
# create a hash with setting keys and values for usage later
basket_settings = Hash.new
@basket.settings.each_with_key { |key, value| basket_settings[key.to_sym] = value }
params[:settings].each do |name, value|
# make sure we do not cause an SQL query if the value is the same
next if basket_settings[name.to_sym] == value
# convert the string to a boolean/nil value if it can be
value = value.param_to_obj_equiv if value.is_a?(String)
# save this new value to the baskets settings
@basket.settings[name] = value
end
end
def appropriate_basket
@basket = current_basket_is_selected? ? @current_basket : Basket.find(params[:id])
end
def current_basket_is_selected?
params[:id].blank? || @current_basket.id == params[:id]
end
# make these methods available in the views
helper_method :profile_rules, :allowed_field?, :current_value_of
private
#
# Basket Profile Helpers
# (we put them in the controller because some are used here)
#
# when we are making/editing a basket, set the default values so they
# reflect on the form the person making the basket sees. Don't do this
# however if they have submitted a form and a value exists in params[:basket]
# (because it overwrites it)
# we can't use hidden fields because its not secure
# so after a post has been made, we have to check values
# are allowed/set and replace/add them if they aren't.
# make sure we edit the params hash for both basket and settings
# as well as the @basket object for basket settings
# don't do this if the user is a site admin though
def prepare_and_validate_profile_for(form_type)
# this var is used in form helpers
@form_type = form_type
# we don't run this method is we don't have profile rules
return if profile_rules.blank?
# we need to check all form types for the values
form_types = [:edit, :appearance, :homepage_options]
# make the params values hash if they aren't already
params[:basket] ||= Hash.new
params[:settings] ||= Hash.new
Rails.logger.debug "Before params validation and reset, basket was " + params[:basket].inspect
Rails.logger.debug "Before params validation and reset, settings was " + params[:settings].inspect
# for each basket attribute, reset to the default value if not an allowed field
Basket::EDITABLE_ATTRIBUTES.each do |setting|
if (@site_admin || allowed_field?(setting)) && params[:basket].has_key?(setting.to_sym)
# if we run this, it means that the current user is allowed
# to set this field and the field has a value already
@basket.send("#{setting}=", params[:basket][setting.to_sym])
else
# if we run this, it means that the current user is not allowed
# to set this field, or they are but the field has no value
value = current_value_of(setting, true, form_types)
next if setting.to_sym == :feeds_attributes && value.nil?
params[:basket][setting.to_sym] = value
@basket.send("#{setting}=", value)
end
end
# for each basket setting, reset to the default value if not an allowed field
Basket::EDITABLE_SETTINGS.each do |setting|
if !(@site_admin || allowed_field?(setting)) && !params[:basket].has_key?(setting.to_sym)
# if we run this, it means that the current user is not allowed
# to set this field, or they are but the field has no value
params[:settings][setting.to_sym] = current_value_of(setting, true, form_types)
end
end
Rails.logger.debug "After params validation and reset, basket is " + params[:basket].inspect
Rails.logger.debug "After params validation and reset, settings is " + params[:settings].inspect
end
# gets the profile rules for this basket. Memoize the result to prevent needless queries
# in the case of a new record, pull the basket profile from the db based on
# basket_profile param. In the case of editing a record, pull the first basket profile
# from the associations. In either case, if a profile doesn't exist or can't be found,
# return nil.
def profile_rules
@profile_rules ||= begin
if !@basket || @basket.new_record?
profile = Profile.find_by_id(params[:basket_profile])
profile ? profile.rules(true) : nil
else
!@basket.profiles.blank? ? @basket.profiles.first.rules(true) : nil
end
end
end
# Check whether a field is allowed to be shown to a user
# return true if no profiles are mapped to this basket
# return true if the profiles rule type is all
# Some fields exists as child options of a parent option, and as such,
# don't have their checkboxes, and thus arn't in the allowed list, however
# if this method is being called for them, then the parent has been allowed
# so return true if the field is in any of the child/nested fields. We have
# to do this before anything that returns false else they get turned to nil
# return false if the profiles rule type is none
# return true if the field is in the profiles allowed field list
# finally, return false
def allowed_field?(name)
return true if profile_rules.blank? # no profile mapping
return true if params[:show_all_fields] && @site_admin
return true if profile_rules[@form_type.to_s]['rule_type'] == 'all'
return true if Basket::NESTED_FIELDS.include?(name)
return false if profile_rules[@form_type.to_s]['rule_type'] == 'none'
return true if profile_rules[@form_type.to_s]['allowed'] &&
profile_rules[@form_type.to_s]['allowed'].include?(name.to_s)
false
end
# get the current value of a field, from either basket/setting submitted values,
# profile if new record, or exsiting value of existing record
# skip_posted_values will skip getting the value from params
def current_value_of(name, skip_posted_values=false, form_type=nil)
form_type ||= @form_type
value = nil
unless skip_posted_values
# if the value exists in a submitted form, use it
value = params[:basket][name] if params[:basket] && params[:basket].key?(name)
value = params[:settings][name] if params[:settings] && params[:settings].key?(name)
end
if value.nil? && @basket && !@basket.new_record?
value = if @basket.respond_to?(name)
# if the basket responds to a value method
@basket.send(name)
elsif @basket.respond_to?("#{name}?")
# if the basket respond to a boolean method
@basket.send("#{name}?")
else
# else, see if it has a setting (which will returns nil if not)
@basket.settings[name.to_sym]
end
end
# if by this point we still have nothing/nil
if value.nil? || value.class == NilClass
if profile_rules.blank?
# no profile mapping
value = nil
else
# return the profile rule default value
# if we have an array, loop through them all, checking all types for an ok field
if form_type.is_a?(Array)
form_type.each do |type|
next unless profile_rules[type.to_s] && profile_rules[type.to_s]['values']
value ||= profile_rules[type.to_s]['values'][name.to_s]
end
else
value = profile_rules[form_type.to_s]['values'][name.to_s]
end
end
end
# turn strings into booleans when possible for comparing (v == false) etc
case value
when 'true'
true
when 'false'
false
when 'nil', 'inherit'
nil
else
value
end
end
#
# End of Basket Profile Helpers
#
def list_baskets(per_page=10)
if !params[:type].blank? && @site_admin
@listing_type = params[:type]
else
@listing_type = 'approved'
end
@default_sorting = {:order => 'created_at', :direction => 'desc'}
paginate_order = current_sorting_options(@default_sorting[:order], @default_sorting[:direction], ['name', 'created_at'])
options = { :page => params[:page],
:per_page => per_page,
:order => paginate_order }
options.merge!({ :conditions => ['status = ?', @listing_type] })
@baskets = Basket.paginate(options)
end
# Kieran Pilkington, 2008/08/26
# In order to set settings back to inherit, we have to take strings
# and convert back to booleans or nil later. We have to take boolean
# as well though, as they are used in functional tests
def convert_text_fields_to_boolean
boolean_fields = [:show_privacy_controls, :private_default, :file_private_default, :allow_non_member_comments]
boolean_fields.each do |field|
params[:basket][field] = case params[:basket][field]
when 'true', true
true
when 'false', false
false
else
nil
end
end
end
# Kieran Pilkington, 2008/10/01
# When a basket is created, edited, or deleted, we have to clear
# the robots txt file caches to the new settings take effect
def remove_robots_txt_cache
expire_page "/robots.txt"
end
# Kieran Pilkington - 2008/09/22
# redirect to permission denied if current user cant add/request baskets
def redirect_if_current_user_cant_add_or_request_basket
unless current_user_can_add_or_request_basket?
flash[:error] = t('baskets_controller.redirect_if_current_user_cant_add_or_request_basket.not_authorized')
redirect_to DEFAULT_REDIRECTION_HASH
end
end
end