Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 457
- Log:
Add admin ability to revert an underway poll to an open state.
More sensible entries now presented in the state change menu.
- Author:
- rool
- Date:
- Thu Oct 24 05:58:50 +0100 2013
- Size:
- 35005 Bytes
1 | ######################################################################## |
2 | # File:: application_helper.rb |
3 | # (C):: Hipposoft 2011 |
4 | # |
5 | # Purpose:: General view utilities for the whole application. |
6 | # ---------------------------------------------------------------------- |
7 | # 30-Jan-2011 (ADH): Created. |
8 | # 16-Feb-2011 (ADH): Imported lots of code from Artisan. |
9 | ######################################################################## |
10 | |
11 | module ApplicationHelper |
12 | |
13 | # This has to be a class variable rather than a constant else Rails tends to |
14 | # crash with e.g. "cannot remove Object::APPHELP_BUTTON_MAPPINGS" from a deep |
15 | # backtrace including "cleanup_application", "clear" and |
16 | # "remove_unloadable_constants!". |
17 | # |
18 | # Mappings for button classes and icons under various actions. Bear in mind |
19 | # that a 'show' action is only likely to be used in a button context from an |
20 | # 'edit' form, hence it's cancel-like semantics. The 'index' case is harder |
21 | # as this depends on the context, but is hard-wired to positive semantics |
22 | # here. The user of the hash can modify this by whatever means are most |
23 | # appropriate, if necessary. |
24 | # |
25 | # See "apphelp_protected_button_to" and "apphelp_protected_buttons_to" for |
26 | # examples of use. |
27 | # |
28 | @@apphelp_button_mappings = { |
29 | |
30 | # For use in a context where the current page represents read-only |
31 | # information (e.g. show/index). |
32 | |
33 | :for_read_only_pages => { |
34 | :new => { |
35 | :variant => :positive, |
36 | :icon => :add |
37 | }, |
38 | :edit => { |
39 | :variant => :negative, |
40 | :icon => :edit |
41 | }, |
42 | :delete => { |
43 | :variant => :negative, |
44 | :icon => :delete |
45 | }, |
46 | :index => { |
47 | :variant => :standard, |
48 | :icon => :table |
49 | }, |
50 | :show => { |
51 | :variant => :standard, |
52 | :icon => :show |
53 | } |
54 | }, |
55 | |
56 | # For use in a context where the current page represents a read/write |
57 | # activity (e.g. edit/update). |
58 | |
59 | :for_read_write_pages => { |
60 | :new => { |
61 | :variant => :positive, |
62 | :icon => :add |
63 | }, |
64 | :edit => { |
65 | :variant => :positive, |
66 | :icon => :edit |
67 | }, |
68 | :index => { |
69 | :variant => :negative, |
70 | :icon => :cancel |
71 | }, |
72 | :show => { |
73 | :variant => :negative, |
74 | :icon => :cancel |
75 | } |
76 | }, |
77 | |
78 | # General use (not read/write dependent). |
79 | |
80 | :for_general_use => { |
81 | |
82 | # Special entries for the log in / sign up pages. |
83 | |
84 | :login => { |
85 | :variant => :negative, |
86 | :icon => :account |
87 | }, |
88 | :signup => { |
89 | :variant => :negative, |
90 | :icon => :account |
91 | }, |
92 | |
93 | # Special entry - use this if your action name is not found in the hash. |
94 | |
95 | :default => { |
96 | :variant => :standard, |
97 | :icon => :action |
98 | } |
99 | } |
100 | } |
101 | |
102 | # Return an internationalised version of the web site's name. |
103 | # |
104 | def apphelp_site_name |
105 | t( :'uk.org.pond.canvass.site_name' ) |
106 | end |
107 | |
108 | # Return an internationalised version of the web site's tagline if defined, |
109 | # else a blank string. |
110 | # |
111 | def apphelp_site_tagline |
112 | t( :'uk.org.pond.canvass.site_tagline', :default => '' ) |
113 | end |
114 | |
115 | # Return an internationalised version of the given action name. If 'true' |
116 | # is passed in the second parameter, a default fallback of the humanized |
117 | # version of the non-internationalised action name will be chosen. If this |
118 | # parameter is omitted or 'false' is given, the I18n engine's "missing token" |
119 | # message is returned instead (no default string is used). |
120 | # |
121 | def apphelp_action_name( action, use_default = false ) |
122 | options = use_default ? { :default => action.to_s.humanize } : {} |
123 | t( "uk.org.pond.canvass.action_names.#{ action }", options ) |
124 | end |
125 | |
126 | # Return an internationalised title appropriate for a page handling the |
127 | # current action for the current controller, or the given controller. |
128 | # |
129 | def apphelp_title( ctrl = controller ) |
130 | "#{ apphelp_site_name }: #{ apphelp_heading( ctrl ) }" |
131 | end |
132 | |
133 | # Return an internationalised heading appropriate for a page handling the |
134 | # current action for the current controller, or the given controller and |
135 | # optional given action name. If you want to use a default string, pass it |
136 | # in the optional third parameter. Headings like this can be (and are) also |
137 | # used as descriptive action link text. |
138 | # |
139 | def apphelp_heading( ctrl = controller, action = nil, default = nil ) |
140 | action ||= ctrl.action_name |
141 | |
142 | t( |
143 | "uk.org.pond.canvass.controllers.#{ ctrl.controller_name }.action_title_#{ action }", |
144 | :default => default |
145 | ) |
146 | end |
147 | |
148 | # Shortcut to avoid long references to "uk.org.pond.canvass.generic_messages" |
149 | # when reading generic messages from the locale file. Pass the message token |
150 | # part (e.g. "yes", "no", "confirmation"). Only useful for basic messages |
151 | # which require no parameter substitution or default lookup values. |
152 | # |
153 | def apphelp_generic( message_name ) |
154 | t( "uk.org.pond.canvass.generic_messages.#{ message_name }" ) |
155 | end |
156 | |
157 | # Return an internationalised "are you sure?" confirmation prompt. |
158 | # |
159 | def apphelp_confirm |
160 | heading = apphelp_heading( controller, :delete, '' ) |
161 | heading << "\n\n" unless ( heading.empty? ) |
162 | |
163 | return heading + apphelp_generic( :confirmation ) |
164 | end |
165 | |
166 | # Return an internationalised, emphasised "Unknown" quantity message. |
167 | # |
168 | def apphelp_unknown_quantity_warning |
169 | "<em>#{ apphelp_generic( :unknown ) }</em>" |
170 | end |
171 | |
172 | # Return a controller view hint, based on looking up "view_<foo>" in the |
173 | # locale file for the given value of "<foo>" (as a string or symbol). The |
174 | # controller handling the current request is consulted by default, else |
175 | # pass a reference to the controller of interest in the optional second |
176 | # parameter. If the hint includes subsitution tokens, pass them in an |
177 | # optional third parameter as a hash. |
178 | # |
179 | def apphelp_view_hint( hint_name, ctrl = controller, substitutions = {} ) |
180 | t( "uk.org.pond.canvass.controllers.#{ ctrl.controller_name }.view_#{ hint_name }", substitutions ) |
181 | end |
182 | |
183 | # Return 'yes' or 'no', internationalised, according to the given value, |
184 | # which is evaluated as (or should already be) a boolean. Remember that in |
185 | # Ruby the boolean evaluation of certain types can be unexpected - e.g. |
186 | # integer zero is not "nil", so it evaluates to 'true' in a boolean context. |
187 | # |
188 | def apphelp_boolean( bool ) |
189 | apphelp_generic( bool ? :yes : :no ) |
190 | end |
191 | |
192 | # Return a piece of text wrapped in a SPAN of class "current_link". Assumes |
193 | # given text is HTML-safe. |
194 | # |
195 | def apphelp_current_link( text ) |
196 | "<span class=\"current_link\">#{ text }</span>" |
197 | end |
198 | |
199 | # Return a view hint (see apphelp_view_hint) based on the given workflow |
200 | # state, by compiling token "view_state_<statename>" and looking it up for |
201 | # the current controller (pass no second parameter) or the given controller |
202 | # (pass this in the second parameter). |
203 | # |
204 | def apphelp_state( state, ctrl = controller ) |
205 | apphelp_view_hint( "state_#{ state }", ctrl ) |
206 | end |
207 | |
208 | # As "apphelp_state", but works with event names. |
209 | # |
210 | def apphelp_event( event, ctrl = controller ) |
211 | apphelp_view_hint( "event_#{ event }", ctrl ) |
212 | end |
213 | |
214 | # Constructs links to controllers and actions based on whether or not users |
215 | # can perform given actions. |
216 | # |
217 | # Pass the action name as a symbol, then two options hashes. The first is |
218 | # used by this function and the second is passed to both a permissions |
219 | # checking method written in the User model and to a Rails URL generation |
220 | # method. The various methods are determined from the first options hash. |
221 | # Things to specify there: |
222 | # |
223 | # :method - the name of the Rails URL generation method to call, e.g. |
224 | # "edit_user_url" or "show_language_url". |
225 | # |
226 | # :url - You should always try to keep things simple and for such cases |
227 | # ":method" ought to be sufficient for generating a correct URL |
228 | # for the link. In some cases though the URL generation may be |
229 | # too complex for the ":method" mechanism to handle, so you can |
230 | # circumvent it by passing in a URL directly, |
231 | # |
232 | # :controller - A Controller (e.g. UsersController or LanguagesController) |
233 | # from which an ActiveRecord model is inferred (via |
234 | # "controller_name.classify.constantize" (thus, e.g. |
235 | # 'LanguagesController' => 'languages' => 'Language' => |
236 | # model). Permissions methods are invoked on the model. The |
237 | # controller name is also used to look up appropriate link |
238 | # text in the languages file. If omitted, the controller |
239 | # handling the current request is used. |
240 | # |
241 | # :model - A model (e.g. User or Language) if :controller does not lead |
242 | # to the model on which to invoke a permissions method. |
243 | # |
244 | # :short - if 'true', the link text is obtained from apphelp_action_name |
245 | # which returns short one or two word results. If omitted or |
246 | # 'false', apphelp_heading is used, which returns more |
247 | # descriptive results. Short versions tend to be useful for |
248 | # action links in index tables of objects, long versions tend |
249 | # to be useful e.g. near the footer of other kinds of page. |
250 | # |
251 | # :class - If present, the value is used for a 'class' attribute on the |
252 | # link ("<a>" tag), should one be generated. |
253 | # |
254 | # :title - If present, the value is used for a 'title' attribute on the |
255 | # link ("<a>" tag), should one be generated. |
256 | # |
257 | # :bypass - In very rare cases you may wish to use all the facilities of |
258 | # this generator, but bypass security and assume the link should |
259 | # definitely be generated. An example is an 'Add to cart' link, |
260 | # where the relevant controller's action explicitly supports |
261 | # working when no user is logged in via the redirect-to-sign-in- |
262 | # or-sign-up-then-add-product dance. In the vast majority of |
263 | # cases though, you should never set this option to 'true'. |
264 | # |
265 | # :text - You can override the visible link text here, or by passing in |
266 | # a block which evaluates to a string for the link text (see |
267 | # below for more). |
268 | # |
269 | # In Artisan, the action name is used to construct a method called on the |
270 | # model class, but Canvass' hub-integrated permission model differs. Instead, |
271 | # the action name will be checked against the model's associated controller's |
272 | # permissions table. |
273 | # |
274 | # The permissions method is passed the second options hash as its only |
275 | # input parameter. |
276 | # |
277 | # Should the current user have permission to perform the relevant action |
278 | # under the relevant controller, the Rails URL generator method in :method |
279 | # is invoked. It is also passed the second options hash. |
280 | # |
281 | # Since Rails URL generator methods in newer Rails versions often support |
282 | # being passed a variety of different types, the "options hash" can actually |
283 | # be whatever type is supported by the method named in ":method". So long as |
284 | # both that method and the permissions method in the User object support |
285 | # that data type as an input parameter, everything should work fine. |
286 | # |
287 | # Note that if the second options hash is omitted, it defaults to a value |
288 | # of 'true' - useful for actions which require no URL generator parameters. |
289 | # |
290 | # If you pass a block after the function call, the block will be invoked |
291 | # with no parameters when the link is being generated. The return value is |
292 | # used as link text instead of the internationalised controller and action |
293 | # name composition described above. Alternatively, if you prefer, specify the |
294 | # text using the ":text" link option. |
295 | # |
296 | # For example, in any Home Page view: |
297 | # |
298 | # <%= apphelp_protected_link_to( :edit, :method => :edit_home_page_url ) %> |
299 | # |
300 | # ...would check HomePagesController for the current user's ability to invoke |
301 | # the 'edit' action link to "edit_home_page_url" with internationalised link |
302 | # text if so. |
303 | # |
304 | def apphelp_protected_link_to( action, options_for_link, options_for_url = nil ) |
305 | ctrl = options_for_link[ :controller ] || controller.class |
306 | bypass = options_for_link[ :bypass ] |
307 | model = ( options_for_link[ :model ] || ctrl.controller_name.classify.constantize ) unless bypass |
308 | clsnm = options_for_link[ :class ] |
309 | title = options_for_link[ :title ] |
310 | trans = options_for_link[ :text ] |
311 | |
312 | permission = bypass || get_permission( model, action, options_for_url ) |
313 | return permission unless permission == true |
314 | |
315 | # For the link text, we try to find the "action_title_<action_name>" string |
316 | # for the given controller, but if that fails fall back to the basic list |
317 | # of translated action names, or just quote the name without translation. |
318 | |
319 | if ( options_for_link.has_key?( :url ) ) |
320 | url = options_for_link.delete( :url ) |
321 | else |
322 | url = send( options_for_link[ :method ], options_for_url ) |
323 | end |
324 | |
325 | if ( trans.nil? ) |
326 | if ( block_given? ) |
327 | trans = yield() |
328 | elsif ( options_for_link[ :short ] ) |
329 | trans = apphelp_action_name( action, true ) |
330 | else |
331 | trans = apphelp_heading( |
332 | ctrl, |
333 | action, |
334 | apphelp_action_name( action, true ) |
335 | ) |
336 | end |
337 | end |
338 | |
339 | return link_to( trans, url, :class => clsnm, :title => title ) |
340 | end |
341 | |
342 | # Writes numerous protected links via apphelp_protected_link_to. See that |
343 | # method for details. Pass a separator string to output in between any two |
344 | # consecutive links (including white space if desired) and then a series of |
345 | # arrays. Each array lists parameters, in order, to be passed to |
346 | # apphelp_protected_link_to. |
347 | # |
348 | # For example, the Users controller's "show" view might want to offer links |
349 | # to edit the shown account or list all accounts. An agent may be able to |
350 | # show details of other users' accounts but not edit them. A client will be |
351 | # able to show and edit their own account, but not list other accounts. An |
352 | # administrator can always do both. In the show.html.erb file describing a |
353 | # user object stored in "@user", we might add: |
354 | # |
355 | # <%= |
356 | # apphelp_protected_link_to( |
357 | # ' | ', |
358 | # [ :edit, { :method => :edit_user_path }, @user ], |
359 | # [ :index, { :method => :users_path } ] |
360 | # ) |
361 | # %> |
362 | # |
363 | # This will produce conditional "edit" and "index" links with " | " text |
364 | # separating them, if both are present (else no separator text is shown). |
365 | # |
366 | # To help cases where you may want entire links to be present or absent due |
367 | # to some external factor (e.g. a "show profile" link for users with a |
368 | # profile should be omitted for users without), parameters may be 'nil'. Any |
369 | # such parameter entries are simply ignored. |
370 | # |
371 | # As a special extension the separator string can be replaced by an options |
372 | # hash currently accepting these keys: |
373 | # |
374 | # :prefix => String to insert at start of each line if line is not empty |
375 | # :suffix => String to add to end of each line if line is not empty |
376 | # |
377 | # For example, you may choose to use a prefix option of "<li>" and suffix of |
378 | # "</li>" to wrap each link in a list item container. |
379 | # |
380 | def apphelp_protected_links_to( separator, *link_data ) |
381 | links = '' |
382 | |
383 | if ( separator.is_a?( Hash ) ) |
384 | options = separator |
385 | separator = '' |
386 | else |
387 | options = {} |
388 | end |
389 | |
390 | link_data.each do | data | |
391 | next if data.nil? |
392 | |
393 | link = apphelp_protected_link_to( data[ 0 ], data[ 1 ], data[ 2 ] ) |
394 | |
395 | # If the call generates link text, add it. Except for the very first |
396 | # added link (when 'links' is empty), always add a separator. |
397 | |
398 | unless link.empty? |
399 | links << separator unless ( links.empty? ) |
400 | links << ( options[ :prefix ] || '' ) |
401 | links << link |
402 | links << ( options[ :suffix ] || '' ) |
403 | end |
404 | end |
405 | |
406 | return links |
407 | end |
408 | |
409 | # A simple version of "apphelp_protected_link_to". Pass a model object |
410 | # instance and the display text to use to represent that instance as part |
411 | # of a "show" link, to show details of the object. If the current user has |
412 | # permission to view the object a link will be returned, else just a plain |
413 | # piece of the given text is returned. HTML safety is ensured. |
414 | # |
415 | # If the first parameter is "nil", the return value of a call to |
416 | # "apphelp_unknown_quantity_warning" is returned instead. |
417 | # |
418 | # If the second parameter is omitted, a special case of using the object's |
419 | # "name" method to retrieve the display text applies - this is a very common |
420 | # case for callers. |
421 | # |
422 | def apphelp_protected_show_link( obj, name = nil ) |
423 | return apphelp_unknown_quantity_warning() if ( obj.nil? ) |
424 | |
425 | name = obj.name if ( name.nil? ) |
426 | link = apphelp_protected_link_to( |
427 | :show, |
428 | { |
429 | :model => obj.class, |
430 | :method => :url_for |
431 | }, |
432 | obj |
433 | ) { h( name ) } |
434 | |
435 | return ( link.empty? ) ? h( name ) : link |
436 | end |
437 | |
438 | # Works as "apphelp_protected_link_to". In this case, though, the code calls |
439 | # through to "apphelp_protected_buttons_to" (rather than the other way around |
440 | # as for the "_link[s]_" methods), so see that method for more information,. |
441 | # |
442 | def apphelp_protected_button_to( action, options_for_link, options_for_url = nil ) |
443 | apphelp_protected_buttons_to( |
444 | [ action, options_for_link, options_for_url ] |
445 | ) |
446 | end |
447 | |
448 | # Works as "apphelp_protected_links_to", but writes links in the style of |
449 | # buttons (assuming supporting CSS). You will usually want to wrap the output |
450 | # in a container DIV of class "buttons"; this is not done automatically in |
451 | # case you want to add other things inside the same container (such as a |
452 | # 'real' form submission button). See: |
453 | # |
454 | # http://particletree.com/features/rediscovering-the-button-element/ |
455 | # |
456 | # Method "apphelp_protected_links_to" takes a separator string followed by |
457 | # arrays of up to three entries which are used as parameters for method |
458 | # "apphelp_protected_link_to". This method has no separator string but takes |
459 | # the same kind of array. |
460 | # |
461 | # In the second entry in each array (the link options hash) there are some |
462 | # additional keys you can specify: |
463 | # |
464 | # :variant => a class name used for the link |
465 | # :icon => an icon name used for the link (e.g. "add" for "add.png") - |
466 | # drawn from "/images/icons/" automatically |
467 | # :text => text to use for the link, else 'apphelp_heading' is called |
468 | # |
469 | # The variant and icon will be automatically determined from the action name |
470 | # given in the first array entry via @@apphelp_button_mappings falling to the |
471 | # @@apphelp_button_mappings[ :default ] entry if the action is not found. You |
472 | # can use the link options hash's ":variant" and/or ":icon" keys to override |
473 | # this if necessary. |
474 | # |
475 | # A class name from APPHELP_BUTTON_MAPPINGS or ":variant" is assigned to the |
476 | # actual link tag, along with class name "round" (e.g. "positive round"). If |
477 | # you want to override this, specify the ":class" key in the link options. |
478 | # |
479 | def apphelp_protected_buttons_to( *button_data ) |
480 | buttons = '' |
481 | |
482 | button_data.each do | data | |
483 | next if ( data.nil? ) |
484 | |
485 | action = data[ 0 ] # NB: This is the action the button will represent, not the current, user-requested action for the enclosing page. |
486 | options_for_link = data[ 1 ] || {} |
487 | options_for_url = data[ 2 ] |
488 | |
489 | case action_name.to_sym # NB: This is the current, user-requested action for the enclosing page, not the action the button will represent. |
490 | when :new, :create, :edit, :update |
491 | mapping_hash = @@apphelp_button_mappings[ :for_read_write_pages ] |
492 | when :show, :index |
493 | mapping_hash = @@apphelp_button_mappings[ :for_read_only_pages ] |
494 | else |
495 | mapping_hash = @@apphelp_button_mappings[ :for_general_use ] |
496 | end |
497 | |
498 | mapping = mapping_hash[ action ] || @@apphelp_button_mappings[ :for_general_use ][ :default ] |
499 | ctrl = options_for_link[ :controller ] || controller.class |
500 | variant = options_for_link.delete( :variant ) || mapping[ :variant ] |
501 | icon = options_for_link.delete( :icon ) || mapping[ :icon ] |
502 | text = options_for_link.delete( :text ) || apphelp_heading( ctrl, action ) |
503 | |
504 | options_for_link[ :class ] ||= "#{ variant } round" |
505 | |
506 | buttons += ' ' unless ( buttons.empty? ) |
507 | buttons += apphelp_protected_link_to( |
508 | action, |
509 | options_for_link, |
510 | options_for_url |
511 | ) do |
512 | image_tag( |
513 | "icons/#{ icon }.png", |
514 | :alt => '' # Explicit empty ALT text => icon not important for screen reading |
515 | ) << text |
516 | end |
517 | end |
518 | |
519 | return buttons |
520 | end |
521 | |
522 | # Generate a submit BUTTON with name "submit_changes" and a more traditional |
523 | # INPUT TYPE="submit" with name "msie6_commit_changes" wrapped in conditional |
524 | # comments, in the context of the given form, using text based on the given |
525 | # action name (as a symbol), the generated HTML indented by the optional |
526 | # prefix string given in the third parameter. |
527 | # |
528 | # The second parameter can be a string rather than an action name given as a |
529 | # symbol. If a symbol, button text is found via "apphelp_action_name". If a |
530 | # string, the value is used directly. |
531 | # |
532 | # If the first parameter (form) is 'nil', then a context-less 'submit_tag' |
533 | # call will be used for the 'INPUT TYPE="submit"' button, rather than calling |
534 | # the 'submit' method on the given form object. |
535 | # |
536 | def apphelp_submit( form, action_or_string, indent = nil ) |
537 | return apphelp_complex_button( |
538 | form, |
539 | action_or_string, |
540 | { |
541 | :indent => indent, |
542 | :button_image => 'accept' |
543 | } |
544 | ) |
545 | end |
546 | |
547 | # A more complex version of 'apphelp_submit' which is used for more general |
548 | # buttons. Pass the form and action parameters as for 'apphelp_submit' then |
549 | # an options hash: |
550 | # |
551 | # Key Value meaning |
552 | # ========== ============================================================ |
553 | # indent Optional indent string, used as a prefix on each line of |
554 | # output. |
555 | # input_class Class name for INPUT tag; default is "obvious" if omitted. |
556 | # input_name Name for INPUT tag; default is "msie6_commit_changes". |
557 | # button_class Class name for BUTTON tag; always has "round" added; |
558 | # default is "positive". |
559 | # button_name Name for BUTTON tag; default is "commit_changes". |
560 | # button_image Image leaf, e.g. "add" for "icons/add.png" |
561 | # - if omitted, no image is used within the BUTTON container. |
562 | # confirm Confirmation text, if you want JavaScript confirmation. |
563 | # |
564 | def apphelp_complex_button( form, action_or_string, options = {} ) |
565 | |
566 | # Process the various input arguments and options. |
567 | |
568 | if ( form.nil? ) |
569 | obj = self |
570 | method = :submit_tag |
571 | else |
572 | obj = form |
573 | method = :submit |
574 | end |
575 | |
576 | if ( action_or_string.is_a?( Symbol ) ) |
577 | action_or_string = apphelp_action_name( action_or_string ) |
578 | end |
579 | |
580 | indent = options.delete( :indent ) |
581 | input_name = options.delete( :input_name ) || 'msie6_commit_changes' |
582 | input_class = options.delete( :input_class ) || 'obvious' |
583 | button_name = options.delete( :button_name ) || 'commit_changes' |
584 | button_class = options.delete( :button_class ) || 'positive' |
585 | button_image = options.delete( :button_image ) |
586 | confirm = options.delete( :confirm ) |
587 | |
588 | if ( button_class.empty? ) |
589 | button_class = 'round' |
590 | else |
591 | button_class << ' round' |
592 | end |
593 | |
594 | if ( button_image.nil? ) |
595 | button_html = '' |
596 | else |
597 | button_html = " #{ image_tag( 'icons/' + button_image + '.png', :alt => '' ) }\n" |
598 | end |
599 | |
600 | if ( confirm.nil? ) |
601 | confirm_data = nil |
602 | confirm_html = '' |
603 | else |
604 | confirm = j( confirm ).gsub( "\n", "\\n" ); |
605 | confirm_data = "return confirm("#{ confirm }")" |
606 | confirm_html = " onclick=\"#{ confirm_data }\";" |
607 | end |
608 | |
609 | # Create the HTML string. |
610 | |
611 | html = <<HTML |
612 | <!--[if IE 6]> |
613 | #{ obj.send( method, action_or_string, { :class => input_class, :name => input_name, :onclick => confirm_data } ) } |
614 | <div style="display: none;"> |
615 | <![endif]--> |
616 | <button type="submit" class="#{ button_class }" id="#{ button_name }" name="#{ button_name }"#{ confirm_html }> |
617 | #{ button_html } #{ action_or_string } |
618 | </button> |
619 | <!--[if IE 6]> |
620 | </div> |
621 | <![endif]--> |
622 | HTML |
623 | |
624 | # Indent and return the data. |
625 | |
626 | html.gsub!( /^/, indent ) unless ( indent.nil? || indent.empty? ) # Not 'indent.blank?', as this would ignore all-white-space strings |
627 | return html |
628 | end |
629 | |
630 | # Return a form containing a button with JS deletion confirmation, which |
631 | # if activated will delete the given object. Works on the same principles |
632 | # as apphelp_protected_link_to but everything runs from the controller |
633 | # handling the current request. so its name is used to get a Model and a |
634 | # "can_destroy?" method is invoked on it or, if that is missing, a |
635 | # "can_modify?" method is invoked. Special case: A current user MUST be |
636 | # logged in. |
637 | # |
638 | # The function returns an empty string if there is no current user or if |
639 | # one of the permissions methods returns 'false'. The function returns an |
640 | # empty string in production mode or warning string in development mode if |
641 | # both methods are missing. |
642 | # |
643 | # The input parameter gets passed through to button_to's second parameter, |
644 | # so you can use any url_for() parameters here, including specifying a URL |
645 | # directly if you have unusual requirements, or just passing in an |
646 | # ActiveRecord model object instance reference for simple cases. |
647 | # |
648 | # For a long button name, pass "true" in the optional second parameter - |
649 | # else a short version is used based only on the action name. |
650 | # |
651 | def apphelp_protected_delete_button( obj, short = true ) |
652 | model = controller.controller_name.classify.constantize |
653 | permission = get_permission( model, :destroy, obj ) |
654 | |
655 | if ( permission == true ) |
656 | button_text = apphelp_action_name( :destroy ) |
657 | button_text = apphelp_heading( controller, :delete, button_text ) unless ( short ) |
658 | |
659 | return button_to( |
660 | button_text, |
661 | obj, |
662 | { |
663 | :confirm => apphelp_confirm(), |
664 | :method => :delete |
665 | } |
666 | ) |
667 | else |
668 | return permission |
669 | end |
670 | end |
671 | |
672 | # Return a small table containing action links / buttons for a row in an |
673 | # index table. Pass a string (not symbol) to use to construct helper method |
674 | # names for URLs for the links (e.g. "language" to use "language_path" for a |
675 | # 'show' action link and "edit_language_path" for an 'edit' action link) and |
676 | # the Active Record object for which the links / buttons are being generated. |
677 | # |
678 | # If an optional third parameter is provided, it is assumed to be a user ID - |
679 | # usually an owner of the given object - and this ID is used for RESTful |
680 | # user-based editing instead of context free editing. That is, rather than |
681 | # generating a link based on "edit_foo_path", "edit_user_foo_path" will be |
682 | # called instead. |
683 | # |
684 | # As a special case you can actually pass a model instance instead of a user |
685 | # ID in the user ID field. This will be turned into a name and used for the |
686 | # resourceful URL; e.g. pass a Currency instance for "currency_..." paths. |
687 | # |
688 | def apphelp_index_actions( name, obj, user_id_or_obj = nil ) |
689 | |
690 | if ( user_id_or_obj.nil? ) |
691 | show_method = "#{ name }_path" |
692 | edit_method = "edit_#{ name }_path" |
693 | arg = obj |
694 | elsif ( user_id_or_obj.is_a?( String ) || user_id_or_obj.is_a?( Fixnum ) ) |
695 | show_method = "user_#{ name }_path" |
696 | edit_method = "edit_user_#{ name }_path" |
697 | arg = { :id => obj.id, :user_id => user_id_or_obj } |
698 | else |
699 | resname = user_id_or_obj.class.table_name.singularize |
700 | show_method = "#{ resname }_#{ name }_path" |
701 | edit_method = "edit_#{ resname }_#{ name }_path" |
702 | arg = { :id => obj.id } |
703 | arg[ "#{ resname }_id".to_sym ] = user_id_or_obj.id |
704 | end |
705 | |
706 | show = apphelp_protected_link_to( :show, { :short => true, :method => show_method }, arg ) |
707 | edit = apphelp_protected_link_to( :edit, { :short => true, :method => edit_method }, arg ) |
708 | |
709 | if ( respond_to? "delete_#{ name }_path" ) |
710 | dsty = apphelp_protected_link_to( :delete, { :short => true, :method => "delete_#{ name }_path" }, obj ) |
711 | else |
712 | dsty = apphelp_protected_delete_button( obj ) |
713 | end |
714 | |
715 | result = "<table class=\"no_border\" border=\"0\"><tr>" |
716 | |
717 | result << '<td>' << show << '</td>' unless ( show.empty? ) |
718 | result << '<td>' << edit << '</td>' unless ( edit.empty? ) |
719 | result << '<td>' << dsty << '</td>' unless ( dsty.empty? ) |
720 | |
721 | return result << "</tr></table>" |
722 | end |
723 | |
724 | # As will_paginate, but uses internationalised next/previous link text and |
725 | # sets a few common preferred preferences in passing. |
726 | # |
727 | def apphelp_i18n_will_paginate( what ) |
728 | will_paginate( |
729 | what, |
730 | :previous_label => t( :'uk.org.pond.canvass.pagination.previous' ), |
731 | :next_label => t( :'uk.org.pond.canvass.pagination.next' ) |
732 | ) |
733 | end |
734 | |
735 | # Return the result of 'select_tag' / 'form.select' for a list of values |
736 | # given in the first parameter, to set a form field (and thus params hash |
737 | # entry) with an ID determined by the second parameter, according to options |
738 | # given the optional third parameter. |
739 | # |
740 | # The values in the first parameter are specified as an array. Each array |
741 | # entry is itself another array with a pair of entries - the first gives the |
742 | # menu text to show in the selection menu and the second gives the associated |
743 | # value to store if the menu entry is selected (often a database object ID), |
744 | # e.g.: |
745 | # |
746 | # [ |
747 | # [ "Cambridge", "16" ], # Menu text "Cambridge", form field value "16" |
748 | # [ "London", "15" ], |
749 | # [ "UK", "4" ], |
750 | # ] |
751 | # |
752 | # Values are made HTML-safe by Rails in passing, so don't do this as the |
753 | # caller, else you will see double escaping problems. |
754 | # |
755 | # The other parameters vary according to the two main ways this method should |
756 | # be used: |
757 | # |
758 | # (1) To fill in a field of an object which is being created or edited, |
759 | # in which case view code along the lines of |
760 | # "form_for <object> do | form |" is in use. |
761 | # |
762 | # (2) To generate a stand-alone menu which will reflect the result of the |
763 | # menu selection in a form field accessible via the "params" hash. |
764 | # |
765 | # In case (1), the options hash must contain key ":form" with a value of the |
766 | # form being created (the 'form' in "do | form |" above). The menu is built |
767 | # by calling "form.select(...)". The 'method' parameter is the method name |
768 | # (or property name) of the object that is being updated, for example, a list |
769 | # of languages associated with a location might be generated in which case |
770 | # the location's ":language_id" method name would be specified, since this is |
771 | # the name of the accessor method for the property that must be filled in. |
772 | # |
773 | # If the options hash is not given or contains no ":form" key, use case (2) |
774 | # will apply. |
775 | # |
776 | # In case (2), the options hash may contain key ":selected" which contains a |
777 | # value indicating what should be initially selected in the menu. This must |
778 | # match one of the second entries in the two-entry arrays used for the wider |
779 | # array of menu values (so "16", "15" or "4" in the example above). If there |
780 | # is no match results are undefined. If you specify no ":selected" key then |
781 | # the first item in the values array is used as default instead. In addition, |
782 | # the second input parameter names the key that will be set in the "params" |
783 | # hash containing the selected value (via an appropriately named form field |
784 | # in the returned HTML data). |
785 | # |
786 | # Other options values: |
787 | # |
788 | # :include_blank => An array containing an item to push in as an extra |
789 | # menu value: [<menu text>, <value to submit with form>]. |
790 | # |
791 | # Any remaining options are passed to select_tag or form.select as HTML |
792 | # options, so you can do things like, for example, ":onchange => 'foo()'" to |
793 | # have JavaScript function 'foo' execute when a menu selection is made. |
794 | # |
795 | def apphelp_menu( values, method, options = {} ) |
796 | form = options.delete( :form ) |
797 | vals = values.dup # Don't alter passed-by-reference array from caller |
798 | vals.unshift( options.delete( :include_blank ) ) if ( options.has_key?( :include_blank ) ) |
799 | |
800 | if ( form.nil? ) |
801 | return select_tag( |
802 | method, |
803 | options_for_select( |
804 | vals, |
805 | options.delete( :selected ) || vals[ 0 ][ 1 ] |
806 | ), |
807 | options |
808 | ) |
809 | else |
810 | return form.select( method, vals, {}, options ) |
811 | end |
812 | end |
813 | |
814 | private # Meaningless in a module - just put here as a separator... |
815 | |
816 | # NOTE UNUSUAL RETURN VALUE. This function is inherited from Artisan but |
817 | # rewritten for the Hub permissions model. |
818 | # |
819 | # Pass a model in the first parameter - a Controller name is derived from |
820 | # this (e.g. User => UsersController, Poll => PollsController). Pass an |
821 | # action in the second and either a hash containing a "user_id" key, or 'nil' |
822 | # to get permissions for the currently logged in user. |
823 | # |
824 | # Returns an empty string if the user is NOT permitted to perform that action |
825 | # according to the controller derived from the given model. Returns 'true' if |
826 | # the user IS permitted to perform the action. |
827 | # |
828 | # Summary - returns a string if permission is denied, else 'true'. |
829 | # |
830 | def get_permission( model, action, options_for_permission ) |
831 | |
832 | # Note 'ctrl' will be set to the *class*, not an instance. The exception |
833 | # handler block lets us handle "ModelsController" in the common pluralized |
834 | # case, then try "ModelController" in the unusual singleton resource case, |
835 | # then drop out with 'nil' if something goes wrong. |
836 | |
837 | ctrl = begin |
838 | "#{ model.name.pluralize }Controller".constantize |
839 | rescue |
840 | "#{ model.name }Controller".constantize |
841 | end |
842 | |
843 | return true unless ctrl.nil? == false && ctrl.respond_to?( :hubssolib_permissions ) |
844 | |
845 | # From-Artisan backwards compatibility: Options should be a User, but we |
846 | # allow a hash with user_id too. |
847 | |
848 | if ( options_for_permission.is_a? Hash ) |
849 | user = User.find_by_id( options_for_permission[ :user_id ] ) |
850 | elsif ( logged_in? ) |
851 | user = controller.current_user() # (sic. - query current action's controller *instance* for current user) |
852 | else |
853 | user = nil |
854 | end |
855 | |
856 | roles = HubSsoLib::Roles.new( user.nil? ? false : user.admin ) |
857 | permissions = ctrl.hubssolib_permissions() |
858 | |
859 | roles.clear if ( user.nil? ) |
860 | |
861 | return permissions.permitted?( roles, action ) == true ? true : '' |
862 | end |
863 | end |