Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 390
- Log:
Initial import of Canvass, a donations-based poll application.
- Author:
- rool
- Date:
- Mon Mar 21 14:58:04 +0000 2011
- Size:
- 25677 Bytes
1 | ######################################################################## |
2 | # File:: application_controller.rb |
3 | # (C):: Hipposoft 2011 |
4 | # |
5 | # Purpose:: Common Rails controller actions. |
6 | # ---------------------------------------------------------------------- |
7 | # 30-Jan-2011 (ADH): Created. |
8 | ######################################################################## |
9 | |
10 | # Filters added to this controller apply to all controllers in the application. |
11 | # Likewise, all the methods added will be available for all controllers. |
12 | |
13 | class ApplicationController < ActionController::Base |
14 | |
15 | # Include all helpers, all the time; make some methods available to views. |
16 | helper :all |
17 | helper_method :logged_in? |
18 | helper_method :current_user |
19 | |
20 | # See ActionController::RequestForgeryProtection for details. |
21 | protect_from_forgery |
22 | |
23 | filter_parameter_logging :card_type, |
24 | :card_number, |
25 | :card_cvv, |
26 | :card_to, |
27 | :card_from, |
28 | :card_issue |
29 | |
30 | # Hub single sign-on support. |
31 | |
32 | require 'hub_sso_lib' |
33 | include HubSsoLib::Core |
34 | |
35 | before_filter :hubssolib_beforehand |
36 | after_filter :hubssolib_afterwards |
37 | |
38 | # https://github.com/collectiveidea/acts_as_audited/issues/26 - "Audt record |
39 | # has no user on first request posted to a server". Recommended workaround is |
40 | # included below. We take advantage of this in passing by defining another |
41 | # sweeper (see "config/initializers/10_define_auditer_sweeper.rb") which adds |
42 | # a copy of a User's name into an Audit record so that the record still has |
43 | # useful data present even if the associated User is later deleted. |
44 | |
45 | require 'acts_as_audited/audit' |
46 | require 'acts_as_audited/audit_sweeper' |
47 | |
48 | cache_sweeper :audit_sweeper, :only => [ :create, :update, :destroy ] |
49 | cache_sweeper :auditer_sweeper, :only => [ :create, :update, :destroy ] |
50 | |
51 | # Other filters |
52 | |
53 | before_filter :assign_real_user |
54 | before_filter :set_best_locale |
55 | before_filter :set_language_dependent_sorting |
56 | before_filter :set_email_host_and_protocol |
57 | before_filter :run_garbage_collectors |
58 | |
59 | # List of models which have translatable columns. |
60 | |
61 | APPCTRL_TRANSLATABLE_MODELS = [ Poll ] |
62 | |
63 | # Should the main heading output be suppressed in the master layout? Return |
64 | # 'true' if so, else 'false'. Defined as 'false' here - subclasses should |
65 | # override this if necessary. |
66 | # |
67 | def skip_main_heading? |
68 | false |
69 | end |
70 | |
71 | # Is there a currently logged in user? Returns 'true' if so, else 'false'. |
72 | # |
73 | def logged_in? |
74 | hubssolib_logged_in? |
75 | end |
76 | |
77 | # Return the current *Canvass* user, which is mapped to a Hub user. Will be |
78 | # nil if not logged in, though you should check "logged_in?" first rather |
79 | # than rely on this. |
80 | # |
81 | # See also before_filter-called "assign_real_user()". |
82 | # |
83 | def current_user |
84 | @current_user |
85 | end |
86 | |
87 | protected |
88 | |
89 | # Set the Flash with an internationalised message describing the current |
90 | # controller's current action's result. The result code is compiled into a |
91 | # token string including the current controller and action names. This is |
92 | # then looked up in the current translation data. |
93 | # |
94 | def appctrl_set_flash( result ) |
95 | |
96 | # E.g. for controller name "users", action name "edit", result "notice": |
97 | # uk.org.pond.canvass.controllers.users.action_notice_edit |
98 | |
99 | flash[ result ] = t( "uk.org.pond.canvass.controllers.#{ controller_name }.action_#{ result }_#{ action_name }" ) |
100 | end |
101 | |
102 | # Report a serious unexpected error (pass the error details, e.g. "foo" from |
103 | # Ruby code "rescue => foo", or some specific error string). |
104 | # |
105 | def appctrl_report_error( error ) |
106 | flash[ :error ] = t( |
107 | :'uk.org.pond.canvass.generic_messages.error_prefix', |
108 | :error => ERB::Util.html_escape( error ) |
109 | ) |
110 | end |
111 | |
112 | # Apply sorting preferences for the good_sort plugin by reading the current |
113 | # user's preference data or applying a default sort hash passed into the |
114 | # mandatory first parameter, provided that sort data is not already |
115 | # specified in the params hash. If the params hash *does* contain such data |
116 | # it is saved in the current user preferences (if a current user exists). |
117 | # |
118 | # Defaults hash format: { "name" => <column name>, "down" => <'' or 'true'> } |
119 | # |
120 | # The optional second parameter is the name to use when looking up the user's |
121 | # search preferences. If not specified, the current request's controller's |
122 | # name will be used. You may prefer to specify e.g. the table name of the |
123 | # model which is being searched, so that preferences are associated with the |
124 | # model rather than with the controller - a controller is only loosely |
125 | # associated with a model, after all. |
126 | # |
127 | # See also "appctrl_search_sort_and_paginate". |
128 | # |
129 | def appctrl_apply_sorting_preferences( defaults, name = controller_name ) |
130 | |
131 | # Retrieve a sort order specified in the params hash, if any. |
132 | |
133 | sort = params[ :sort ] |
134 | |
135 | if ( sort.nil? ) |
136 | |
137 | # If that failed, use current user preferences if there is both a user |
138 | # and a set preference, else defaults; write back into the params hash |
139 | # for the caller's benefit. |
140 | |
141 | sort = current_user.get_preference( "sorting.#{ name }" ) if ( logged_in? ) |
142 | params[ :sort ] = sort || defaults |
143 | |
144 | else |
145 | |
146 | # Sorting data came from params - may not match existing user preferences |
147 | # so save it. The return value of the save attempt is ignored because if |
148 | # it fails there isn't much we can do to recover and a failure to save |
149 | # column sort order is considered fairly unimportant (although it may |
150 | # well point to much more serious underlying database problems). |
151 | |
152 | current_user.set_preference( "sorting.#{ name }", sort ) if ( logged_in? ) |
153 | |
154 | end |
155 | end |
156 | |
157 | # When a form created by view partial "shared/_simple_search.html.erb" is |
158 | # submitted, call here to generate a search conditions array based on the |
159 | # parts of the "params" hash related to the search form. |
160 | # |
161 | # Pass a model defining constant SEARCH_COLUMNS as the array of names, as |
162 | # strings, use for the ":fields" array specified in the location variables |
163 | # used to render the search form view partial. This is used as a security |
164 | # measure - if someone tries to alter the search query data to examine other |
165 | # columns, it won't work because only those columns specified by both the |
166 | # constructed view and the target model will be involved. If the model |
167 | # supports translatable columns, use the untranslated column names here. The |
168 | # special use of "#" in names is supported (see the method description of |
169 | # "appctrl_search_sort_and_paginate" for more details). |
170 | # |
171 | # Returns an array containing a conditions SQL fragment with substitution |
172 | # placeholders and the set of values which should be substituted in - passing |
173 | # this as a value for the ":conditions" parameter in a call to "find()" or |
174 | # similar will result in the results set being restricted accordance with the |
175 | # user's settings in the search form. |
176 | # |
177 | # If you pass a block, it will be called for each of the search parameters |
178 | # (in an undetermined order) and passed a string giving the column name of |
179 | # the search parameter in consideration right now. This lets you map virtual |
180 | # columns for search - for example if you split a postal address into lots |
181 | # of distinct fields in a model, you might allow searches on a fake column |
182 | # name of "address". When this name is passed to your block, generate an SQL |
183 | # fragment parameter substitution markers of "?" where required (for multiple |
184 | # real columns masquerading as one real virtual column, you will typically |
185 | # return something like "column_a LIKE ? OR column_b LIKE ? OR ...etc.". This |
186 | # function will take your SQL data, count the "?"s in it, make sure that the |
187 | # value supplied by the user for the virtual column is substituted safely |
188 | # into each of the places where "?" appears and enclose your SQL fragment in |
189 | # parentheses automatically so that precedence and logic of any other |
190 | # surrounding SQL will be maintained. |
191 | # |
192 | # If your block is happy with a default "column LIKE ?"-style SQL fragment |
193 | # then it should evaluate to "nil". If all your columns are simple cases, |
194 | # don't bother passing a block at all. |
195 | # |
196 | # Special consideration is given to float and integer column types and |
197 | # correct SQL is generated in such cases ("foo IS <unquoted-value>"). |
198 | # |
199 | # Note that columns with a blank associated search value are skipped entirely |
200 | # - if you do supply a block, it won't be called for that column (there's no |
201 | # point). |
202 | # |
203 | # IMPORTANT: Since databases disagree on whether or not the "LIKE" operator |
204 | # should be case sensitive, comparisons are forced to case *insensitive* via |
205 | # "LOWER" on both column name and value to ensure consistent application |
206 | # behaviour across databases. You should consider doing this in SQL returned |
207 | # from a block if you pass one - e.g. use "LOWER(column_name) LIKE LOWER(?)" |
208 | # instead of just "column_name LIKE ?". |
209 | # |
210 | # See also "appctrl_search_sort_and_paginate". |
211 | # |
212 | def appctrl_build_search_conditions( model ) |
213 | |
214 | # All search-related keys start with "s_". The next character is a number |
215 | # or letter which causes "sort" to organise keys in the order specified in |
216 | # the view. An associated key "sr_" carries "and" or "or" for the radio |
217 | # button selection for that entry, or neither of these if there is no radio |
218 | # button (the first search term). |
219 | |
220 | conditions = [] |
221 | query = '' |
222 | first = true |
223 | columns = model::SEARCH_COLUMNS |
224 | translate = model.respond_to?( :columns_for_translation ) |
225 | |
226 | columns.each do | column | |
227 | |
228 | # Fields with a "#" inside have a method name to call to generate the |
229 | # form data. |
230 | |
231 | array = column.split( '#' ) |
232 | |
233 | if ( array.size > 1 ) |
234 | column = array.first |
235 | generator_method = array.last |
236 | else |
237 | generator_method = nil |
238 | end |
239 | |
240 | column = model.translated_column( column ) if ( translate ) |
241 | value = params[ "s_#{ column }".to_sym ] |
242 | next if ( value.blank? ) |
243 | |
244 | radio = params[ "sr_#{ column }".to_sym ] |
245 | |
246 | # If this is the first search term with a non-empty value then the radio |
247 | # button and/or state is irrelevant and must be suppressed to avoid |
248 | # invalid SQL generation later. |
249 | |
250 | if ( first ) |
251 | radio = '' |
252 | first = false |
253 | end |
254 | |
255 | # Make sure the value is an appropriate type. |
256 | |
257 | klass = model.columns_hash[ column ].klass # Ruby type representing column |
258 | |
259 | if ( klass == Fixnum ) |
260 | value = value.to_i |
261 | elsif ( klass == Float ) |
262 | value = value.to_f |
263 | else |
264 | value = "%#{ value }%" |
265 | end |
266 | |
267 | # If a block is given then call it, passing the column name. It should |
268 | # evaluate to "nil" or a string. |
269 | |
270 | caller_sql = yield( column ) if ( block_given? ) |
271 | |
272 | if ( caller_sql.nil? ) |
273 | klass = model.columns_hash[ column ].klass # Ruby type representing column |
274 | |
275 | if ( klass == Fixnum || klass == Float ) |
276 | sql_fragment = "#{ column } = ?" |
277 | else |
278 | sql_fragment = "LOWER(#{ column }) LIKE LOWER(?)" |
279 | end |
280 | |
281 | conditions.push( value ) |
282 | else |
283 | sql_fragment = caller_sql |
284 | conditions = conditions + ( [ value ] * caller_sql.count( '?' ) ) |
285 | end |
286 | |
287 | # Map radios manually, rather than with "radio.uppercase" or whatever - |
288 | # the values come from the form submission so cannot be trusted. Someone |
289 | # might try inserting bits of SQL in there to hack the site. |
290 | |
291 | case radio |
292 | when "and": sql_fragment = " AND (#{ sql_fragment })" |
293 | when "or": sql_fragment = " OR (#{ sql_fragment })" |
294 | else sql_fragment = "(#{ sql_fragment })" |
295 | end |
296 | |
297 | query << sql_fragment |
298 | end |
299 | |
300 | # Push the conditions string onto the front of the array as this is what |
301 | # the ":conditions" parameter in "find()" et al will expect. |
302 | |
303 | return conditions.unshift( query ) |
304 | end |
305 | |
306 | # Do the common work for an AJAX-sortable, search-filtered and paginated |
307 | # list of options rendering via a "_list.html.erb" partial for XHR requests. |
308 | # |
309 | # Pass a model. This must use good_sort to provide a "sort_by" method for any |
310 | # column used for sorting. It must define constant SEARCH_COLUMNS to an array |
311 | # of names of any columns to be used for searching (see e.g. the Language, |
312 | # Category or User models for examples of varying complexity). Special case: |
313 | # A string in SEARCH_COLUMNS containing a hash defines the name of the search |
314 | # column before the hash then a method to use to generate the HTML for the |
315 | # search field afterwards. See "shared/_simple_search.html.erb" for the |
316 | # practical use of this. |
317 | # |
318 | # The method then assigns "@items" from sending a method given in the options |
319 | # hash to a base object also given in the options parameter. For example pass |
320 | # "Location" and ":roots" to get a list of the root Location objects, or pass |
321 | # "@location" and ":children" to get a list of the immediate children of |
322 | # whatever nested set object instance is in "@location". Options keys: |
323 | # |
324 | # :base_obj => Object on which to call the method... |
325 | # :method => ...specified here. |
326 | # :method_arg => Optional single argument value; will be passed to :method |
327 | # if given (may be nil, in which case nil will be passed as |
328 | # a single argument; or omitted, in which case :method is |
329 | # called with no arguments). |
330 | # :include => Optional value for :includes in the "find" call, to use |
331 | # eager loading of associations. For example, a User list |
332 | # may want to eager-load associated role and profile data, |
333 | # so would specify ":include => [ :role, :profile ]". |
334 | # |
335 | # If the base object parameter is omitted then it is assumed to carry the |
336 | # same value as the model given in the first parameter. |
337 | # |
338 | # If the method is omitted then "paginate" (from the will_paginate plugin) is |
339 | # called on the model directly. This is more efficient than passing a method |
340 | # name of ":all" (which is trapped as a special case for that reason). |
341 | # |
342 | # User sorting preferences for the calling controller are recorded and/or |
343 | # applied in passing, with a default setting of "ascending, by 'name'". If |
344 | # your model has other requirements then pass a default sort options hash in |
345 | # the options: |
346 | # |
347 | # :default_sorting => Optional default sort order hash, as described below. |
348 | # :always_sort_by => Optional string to append to an ORDER BY clause, e.g. |
349 | # for a User model, passing '"users".name' leads to |
350 | # users being sorted by whatever good_sort column is in |
351 | # use, then by ascending user name. NOTE: This may lead |
352 | # to the same order value being specified twice. To try |
353 | # and avoid that, if the order-by string already |
354 | # contains a case-*sensitive* match of the always-sort |
355 | # string, the latter is NOT appended. Thus it is best |
356 | # to specify just the always-sort column name and *not* |
357 | # include 'ASC' or 'DESC' in there (so you're limited |
358 | # to implicit ascending sort order) to avoid ending up |
359 | # with orders like "name ASC, name DESC". In this |
360 | # example, giving just "name" would match orders of |
361 | # "name" or "name DESC" and not be appended, thus doing |
362 | # the Right Thing. |
363 | # |
364 | # The hash has string keys and values; key 'down' should have a value of an |
365 | # empty string or 'true' (as a string) for ascending or descending order |
366 | # respectively; key 'field' should have a value of the name of the column (as |
367 | # a string, not symbol) for sorting. |
368 | # |
369 | # Callers may want to use a custom block for a call made internally to |
370 | # "appctrl_build_search_conditions" - see that method for details - if so, |
371 | # add that block in your call here, to "appctrl_search_sort_and_paginate", |
372 | # and it will be passed straight on to (and ultimately called from) |
373 | # "appctrl_search_sort_and_paginate". Search conditions may be further |
374 | # augmented by static search query data specified in the options as follows: |
375 | # |
376 | # :extra_conditions => An array of SQL query string and zero or more |
377 | # arguments to substitute in. The query string will |
378 | # be set before any search query, with the search |
379 | # query added with "AND (...query...)". |
380 | # |
381 | # In the event that searching is in progress (a search query restriction is |
382 | # in effect), "@search_results" will be set to 'true'. Views may find this |
383 | # useful, particularly if no items are returned from a search attempt. |
384 | # |
385 | def appctrl_search_sort_and_paginate( model, options = {} ) |
386 | base_obj = options.delete( :base_obj ) || model |
387 | method = options.delete( :method ) || :all |
388 | includes = options.delete( :include ) || {} |
389 | default_sorting = options.delete( :default_sorting ) || { 'down' => '', 'field' => 'name' } |
390 | always_sort_by = options.delete( :always_sort_by ) |
391 | extra_conditions = options.delete( :extra_conditions ) |
392 | is_translatable = model.respond_to?( :columns_for_translation ) |
393 | |
394 | # We use a 'tableized' model name for the preferences key so that subclass |
395 | # models work OK - e.g. "Auditer", a subclass of "Audit". |
396 | |
397 | appctrl_apply_sorting_preferences( default_sorting, model.model_name.tableize ) |
398 | |
399 | # Build search conditions and merge with caller-supplied extra conditions. |
400 | |
401 | search_query = appctrl_build_search_conditions( model ) do | column | |
402 | if block_given? |
403 | yield( column ) |
404 | else |
405 | nil |
406 | end |
407 | end |
408 | |
409 | @search_results = ! search_query.flatten.join.empty? |
410 | |
411 | unless ( extra_conditions.nil? ) |
412 | str = search_query[ 0 ] |
413 | |
414 | if ( str.empty? ) |
415 | str = extra_conditions[ 0 ] |
416 | else |
417 | str = "( #{ extra_conditions[ 0 ] } ) AND ( #{ str } )" |
418 | end |
419 | |
420 | search_values = search_query[ 1..-1 ] || [] |
421 | extra_values = extra_conditions[ 1..-1 ] || [] |
422 | |
423 | search_query = [ str ] + extra_values + search_values |
424 | end |
425 | |
426 | # Compile the pagination options based on the above data and fetch the |
427 | # relevant item list. Note that the column name for sorting may need to |
428 | # be patched up if the model supports translatable columns, so do this |
429 | # in passing. |
430 | |
431 | sort_params = params[ :sort ] |
432 | |
433 | if ( is_translatable && ! sort_params.nil? ) |
434 | column = sort_params[ 'field' ] |
435 | |
436 | # The user may have sorted on this column with language settings for a |
437 | # particular locale, causing the locale-specific name to be saved in the |
438 | # user's preferences. If the locale configuration later changes - e.g. |
439 | # user changes languages or a new translation is added which better |
440 | # matches the user's specified language - the code would then fail as it |
441 | # would try to access a sorting column which didn't match the name based |
442 | # on the new current locale. Thus, convert the column name to a locale |
443 | # netural form, then get the definitely-for-this-locale version. |
444 | |
445 | unless ( column.nil? ) |
446 | column = model.untranslated_column( column ) # Make locale neutral... |
447 | column = model.translated_column( column ) # ...then match current locale |
448 | |
449 | sort_params[ 'field' ] = column |
450 | end |
451 | end |
452 | |
453 | pagination_options = model.sort_by( sort_params ) |
454 | |
455 | unless ( always_sort_by.nil? ) |
456 | order = pagination_options[ :order ] || '' |
457 | |
458 | unless ( order.include? always_sort_by ) |
459 | order += ', ' unless order.blank? |
460 | pagination_options[ :order ] = order + always_sort_by |
461 | end |
462 | end |
463 | |
464 | pagination_options.merge!( { |
465 | :page => params[ :page ], |
466 | :conditions => search_query, |
467 | :include => includes |
468 | } ) |
469 | |
470 | if ( method == :all ) |
471 | @items = base_obj.paginate( pagination_options ) |
472 | elsif ( options.has_key?( :method_arg ) ) |
473 | @items = base_obj.send( method, options.delete( :method_arg ) ).paginate( pagination_options ) |
474 | else |
475 | @items = base_obj.send( method ).paginate( pagination_options ) |
476 | end |
477 | |
478 | # Render the default index.html.erb view for normal requests or, for AJAX |
479 | # requests, render the list table partial. |
480 | |
481 | if ( request.xhr? ) |
482 | return render( :partial => "#{ model.model_name.tableize }/list" ) |
483 | end |
484 | end |
485 | |
486 | # Make sure that "params[ :id ]" retrieves an object where "user_id" is equal |
487 | # to the ID of the current user. If not, an authorisation failure will be |
488 | # forced, unless the current user is an administrator. Usually called as a |
489 | # before_filter method for edit-like or destroy-like actions only. |
490 | # |
491 | # There must be a current user - a previous item in the filter chain must |
492 | # ensure this. |
493 | # |
494 | # On exit, either the controller will exit because the role requirements |
495 | # were not satisfied, or the current action proceeds with "@item" set to the |
496 | # looked up model. |
497 | # |
498 | def appctrl_ensure_is_owner |
499 | model = controller_name.camelize.singularize.constantize # :-) |
500 | @item = model.find( params[ :id ] ) |
501 | |
502 | if ( @item.user_id != current_user.id ) |
503 | render_optional_error_file( 401 ) unless current_user.is_admin? |
504 | end |
505 | end |
506 | |
507 | private |
508 | |
509 | # What do we do if access is denied? Pretend the location never existed if |
510 | # logged in, else redirect for login. |
511 | # |
512 | def access_denied |
513 | if ( user_signed_in? ) |
514 | render :file => File.join( RAILS_ROOT, 'public', '404.html' ), |
515 | :status => :not_found |
516 | else |
517 | authenticate_user! |
518 | end |
519 | end |
520 | |
521 | # Local User objects exist to manage associations between users, donations |
522 | # and polls, among other things. That means we need to lazy-create users if |
523 | # a Hub logged in user arrives who has no current database representation; |
524 | # or we need to dig out that user's information. See "current_user" for how |
525 | # this information is eventually exposed to other parts of the system. |
526 | # |
527 | def assign_real_user |
528 | if ( hubssolib_logged_in? && @current_user.nil? ) |
529 | |
530 | email = hubssolib_get_user_address() |
531 | name = hubssolib_unique_name() |
532 | roles = hubssolib_get_user_roles() |
533 | |
534 | # Not internationalised since these are Should Never Happen exceptions |
535 | # and in production mode, all the user would see is a 500 error. |
536 | |
537 | raise "Current Hub user appears to have no e-mail address" if ( email.nil? ) |
538 | raise "Current Hub user's name is unkonwn" if ( name.nil? ) |
539 | raise "Current Hub user's roles are unknown" if ( roles.nil? ) |
540 | |
541 | user = User.find_by_email( email ) # Want to associate payments by e-mail address |
542 | # since that's the primary association for e.g. |
543 | # PayPal, even though we're also using a human |
544 | # readable person's name that's been made |
545 | # unique by Hub adding the Hub user ID to it. |
546 | |
547 | if ( user.nil? ) |
548 | user = User.new |
549 | user.email = email |
550 | user.name = name |
551 | user.admin = roles.include?( :admin ) || roles.include?( :webmaster ) |
552 | user.save! |
553 | end |
554 | |
555 | @current_user = user |
556 | else |
557 | @current_user = nil |
558 | end |
559 | |
560 | return @current_user |
561 | end |
562 | |
563 | # Set the best available locale for the currently logged in user or based |
564 | # on HTTP headers if nobody is logged in right now. |
565 | # |
566 | def set_best_locale |
567 | if ( RAILS_ENV == 'test' ) |
568 | code = 'en' |
569 | else |
570 | code = Translation.reconcile_user_data_with_http_request_language( |
571 | request, |
572 | logged_in? ? current_user : nil |
573 | ) |
574 | end |
575 | |
576 | Translation.set_best_locale( code ) |
577 | end |
578 | |
579 | # To use sorting and translatable columns together, we have to reset the |
580 | # sort conditions for translatable models whenever the user's best choice |
581 | # of locale has been set in Rails. |
582 | # |
583 | # For more information, see Jason King's "good_sort" plugin: |
584 | # |
585 | # http://github.com/JasonKing/good_sort/tree/master |
586 | # |
587 | # ...and Iain Hecker's "translatable_columns" plugin: |
588 | # |
589 | # http://github.com/iain/translatable_columns/tree/master |
590 | # http://iain.nl/2008/09/plugin-translatable_columns/ |
591 | # |
592 | def set_language_dependent_sorting |
593 | for model in APPCTRL_TRANSLATABLE_MODELS |
594 | model.set_sorting() if model.respond_to?( :set_sorting ) |
595 | end |
596 | end |
597 | |
598 | # Rather annoyingly, ActionMailer templates have no knowledge of the context |
599 | # in which they are invokved, unlike normal view templates. This is strange |
600 | # and, at least for Artisan, unhelpful. We could insist that the system |
601 | # installer configures some static value for the default host for links, but |
602 | # that's a horrible kludge - once the application is running it always knows |
603 | # its host via the "request" object. |
604 | # |
605 | # This filter patches around this Rails hiccup by wasting a few CPU cycles on |
606 | # auto-setup of the e-mail host and protocol. |
607 | # |
608 | def set_email_host_and_protocol |
609 | unless ( ActionMailer::Base.default_url_options.has_key?( :host ) ) |
610 | ActionMailer::Base.default_url_options[ :host ] = request.host_with_port |
611 | end |
612 | |
613 | unless ( ActionMailer::Base.default_url_options.has_key?( :protocol ) ) |
614 | ActionMailer::Base.default_url_options[ :protocol ] = request.protocol |
615 | end |
616 | end |
617 | |
618 | # Run garbage collectors to tidy up resources which expire. |
619 | # |
620 | def run_garbage_collectors |
621 | Donation.garbage_collect( session ) |
622 | end |
623 | end |