jet-facebooker 1.0.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. data/.autotest +15 -0
  2. data/CHANGELOG.rdoc +24 -0
  3. data/COPYING.rdoc +19 -0
  4. data/Manifest.txt +152 -0
  5. data/README.rdoc +119 -0
  6. data/Rakefile +94 -0
  7. data/TODO.rdoc +4 -0
  8. data/examples/desktop_login.rb +14 -0
  9. data/facebooker.gemspec +42 -0
  10. data/generators/facebook/facebook_generator.rb +14 -0
  11. data/generators/facebook/templates/config/facebooker.yml +49 -0
  12. data/generators/facebook/templates/public/javascripts/facebooker.js +332 -0
  13. data/generators/facebook_controller/USAGE +33 -0
  14. data/generators/facebook_controller/facebook_controller_generator.rb +40 -0
  15. data/generators/facebook_controller/templates/controller.rb +7 -0
  16. data/generators/facebook_controller/templates/functional_test.rb +11 -0
  17. data/generators/facebook_controller/templates/helper.rb +2 -0
  18. data/generators/facebook_controller/templates/view.fbml.erb +2 -0
  19. data/generators/facebook_controller/templates/view.html.erb +2 -0
  20. data/generators/facebook_publisher/facebook_publisher_generator.rb +14 -0
  21. data/generators/facebook_publisher/templates/create_facebook_templates.rb +15 -0
  22. data/generators/facebook_publisher/templates/publisher.rb +3 -0
  23. data/generators/facebook_scaffold/USAGE +27 -0
  24. data/generators/facebook_scaffold/facebook_scaffold_generator.rb +118 -0
  25. data/generators/facebook_scaffold/templates/controller.rb +93 -0
  26. data/generators/facebook_scaffold/templates/facebook_style.css +2579 -0
  27. data/generators/facebook_scaffold/templates/functional_test.rb +89 -0
  28. data/generators/facebook_scaffold/templates/helper.rb +2 -0
  29. data/generators/facebook_scaffold/templates/layout.fbml.erb +6 -0
  30. data/generators/facebook_scaffold/templates/layout.html.erb +17 -0
  31. data/generators/facebook_scaffold/templates/style.css +74 -0
  32. data/generators/facebook_scaffold/templates/view_edit.fbml.erb +13 -0
  33. data/generators/facebook_scaffold/templates/view_edit.html.erb +18 -0
  34. data/generators/facebook_scaffold/templates/view_index.fbml.erb +24 -0
  35. data/generators/facebook_scaffold/templates/view_index.html.erb +24 -0
  36. data/generators/facebook_scaffold/templates/view_new.fbml.erb +12 -0
  37. data/generators/facebook_scaffold/templates/view_new.html.erb +17 -0
  38. data/generators/facebook_scaffold/templates/view_show.fbml.erb +10 -0
  39. data/generators/facebook_scaffold/templates/view_show.html.erb +10 -0
  40. data/generators/publisher/publisher_generator.rb +14 -0
  41. data/generators/xd_receiver/templates/xd_receiver.html +10 -0
  42. data/generators/xd_receiver/templates/xd_receiver_ssl.html +10 -0
  43. data/generators/xd_receiver/xd_receiver_generator.rb +10 -0
  44. data/init.rb +26 -0
  45. data/install.rb +12 -0
  46. data/lib/facebooker.rb +261 -0
  47. data/lib/facebooker/adapters/adapter_base.rb +91 -0
  48. data/lib/facebooker/adapters/bebo_adapter.rb +77 -0
  49. data/lib/facebooker/adapters/facebook_adapter.rb +60 -0
  50. data/lib/facebooker/admin.rb +42 -0
  51. data/lib/facebooker/application.rb +37 -0
  52. data/lib/facebooker/attachment.rb +51 -0
  53. data/lib/facebooker/batch_request.rb +45 -0
  54. data/lib/facebooker/data.rb +57 -0
  55. data/lib/facebooker/feed.rb +78 -0
  56. data/lib/facebooker/logging.rb +44 -0
  57. data/lib/facebooker/mobile.rb +20 -0
  58. data/lib/facebooker/mock/service.rb +50 -0
  59. data/lib/facebooker/mock/session.rb +18 -0
  60. data/lib/facebooker/model.rb +139 -0
  61. data/lib/facebooker/models/affiliation.rb +10 -0
  62. data/lib/facebooker/models/album.rb +11 -0
  63. data/lib/facebooker/models/applicationproperties.rb +39 -0
  64. data/lib/facebooker/models/applicationrestrictions.rb +10 -0
  65. data/lib/facebooker/models/comment.rb +9 -0
  66. data/lib/facebooker/models/cookie.rb +10 -0
  67. data/lib/facebooker/models/education_info.rb +11 -0
  68. data/lib/facebooker/models/event.rb +28 -0
  69. data/lib/facebooker/models/family_relative_info.rb +7 -0
  70. data/lib/facebooker/models/friend_list.rb +16 -0
  71. data/lib/facebooker/models/group.rb +36 -0
  72. data/lib/facebooker/models/info_item.rb +10 -0
  73. data/lib/facebooker/models/info_section.rb +10 -0
  74. data/lib/facebooker/models/location.rb +8 -0
  75. data/lib/facebooker/models/message_thread.rb +89 -0
  76. data/lib/facebooker/models/notifications.rb +17 -0
  77. data/lib/facebooker/models/page.rb +46 -0
  78. data/lib/facebooker/models/photo.rb +19 -0
  79. data/lib/facebooker/models/tag.rb +12 -0
  80. data/lib/facebooker/models/user.rb +722 -0
  81. data/lib/facebooker/models/video.rb +9 -0
  82. data/lib/facebooker/models/work_info.rb +10 -0
  83. data/lib/facebooker/parser.rb +951 -0
  84. data/lib/facebooker/rails/backwards_compatible_param_checks.rb +31 -0
  85. data/lib/facebooker/rails/controller.rb +353 -0
  86. data/lib/facebooker/rails/cucumber.rb +28 -0
  87. data/lib/facebooker/rails/cucumber/world.rb +40 -0
  88. data/lib/facebooker/rails/extensions/action_controller.rb +48 -0
  89. data/lib/facebooker/rails/extensions/rack_setup.rb +16 -0
  90. data/lib/facebooker/rails/extensions/routing.rb +15 -0
  91. data/lib/facebooker/rails/facebook_form_builder.rb +141 -0
  92. data/lib/facebooker/rails/facebook_pretty_errors.rb +22 -0
  93. data/lib/facebooker/rails/facebook_request_fix.rb +28 -0
  94. data/lib/facebooker/rails/facebook_request_fix_2-3.rb +31 -0
  95. data/lib/facebooker/rails/facebook_session_handling.rb +68 -0
  96. data/lib/facebooker/rails/facebook_url_helper.rb +192 -0
  97. data/lib/facebooker/rails/facebook_url_rewriting.rb +61 -0
  98. data/lib/facebooker/rails/helpers.rb +835 -0
  99. data/lib/facebooker/rails/helpers/fb_connect.rb +161 -0
  100. data/lib/facebooker/rails/helpers/stream_publish.rb +22 -0
  101. data/lib/facebooker/rails/integration_session.rb +38 -0
  102. data/lib/facebooker/rails/profile_publisher_extensions.rb +42 -0
  103. data/lib/facebooker/rails/publisher.rb +600 -0
  104. data/lib/facebooker/rails/routing.rb +49 -0
  105. data/lib/facebooker/rails/test_helpers.rb +68 -0
  106. data/lib/facebooker/rails/utilities.rb +22 -0
  107. data/lib/facebooker/server_cache.rb +24 -0
  108. data/lib/facebooker/service.rb +103 -0
  109. data/lib/facebooker/service/base_service.rb +19 -0
  110. data/lib/facebooker/service/curl_service.rb +44 -0
  111. data/lib/facebooker/service/net_http_service.rb +12 -0
  112. data/lib/facebooker/service/typhoeus_multi_service.rb +27 -0
  113. data/lib/facebooker/service/typhoeus_service.rb +17 -0
  114. data/lib/facebooker/session.rb +771 -0
  115. data/lib/facebooker/stream_post.rb +19 -0
  116. data/lib/facebooker/version.rb +9 -0
  117. data/lib/net/http_multipart_post.rb +123 -0
  118. data/lib/rack/facebook.rb +89 -0
  119. data/lib/rack/facebook_session.rb +21 -0
  120. data/lib/tasks/facebooker.rake +19 -0
  121. data/lib/tasks/facebooker.rb +2 -0
  122. data/lib/tasks/tunnel.rake +46 -0
  123. data/rails/init.rb +1 -0
  124. data/setup.rb +1585 -0
  125. data/templates/layout.erb +24 -0
  126. data/test/facebooker/adapters_test.rb +191 -0
  127. data/test/facebooker/admin_test.rb +102 -0
  128. data/test/facebooker/application_test.rb +110 -0
  129. data/test/facebooker/attachment_test.rb +72 -0
  130. data/test/facebooker/batch_request_test.rb +83 -0
  131. data/test/facebooker/data_test.rb +86 -0
  132. data/test/facebooker/logging_test.rb +43 -0
  133. data/test/facebooker/mobile_test.rb +45 -0
  134. data/test/facebooker/model_test.rb +133 -0
  135. data/test/facebooker/models/event_test.rb +15 -0
  136. data/test/facebooker/models/page_test.rb +56 -0
  137. data/test/facebooker/models/photo_test.rb +16 -0
  138. data/test/facebooker/models/user_test.rb +1074 -0
  139. data/test/facebooker/rails/facebook_request_fix_2-3_test.rb +25 -0
  140. data/test/facebooker/rails/facebook_url_rewriting_test.rb +76 -0
  141. data/test/facebooker/rails/integration_session_test.rb +13 -0
  142. data/test/facebooker/rails/publisher_test.rb +532 -0
  143. data/test/facebooker/rails_integration_test.rb +1543 -0
  144. data/test/facebooker/server_cache_test.rb +44 -0
  145. data/test/facebooker/service_test.rb +58 -0
  146. data/test/facebooker/session_test.rb +883 -0
  147. data/test/facebooker_test.rb +1175 -0
  148. data/test/fixtures/multipart_post_body_with_only_parameters.txt +33 -0
  149. data/test/fixtures/multipart_post_body_with_single_file.txt +38 -0
  150. data/test/fixtures/multipart_post_body_with_single_file_that_has_nil_key.txt +38 -0
  151. data/test/net/http_multipart_post_test.rb +52 -0
  152. data/test/rack/facebook_session_test.rb +34 -0
  153. data/test/rack/facebook_test.rb +73 -0
  154. data/test/rails_test_helper.rb +36 -0
  155. data/test/test_helper.rb +74 -0
  156. metadata +289 -0
@@ -0,0 +1,61 @@
1
+ module ::ActionController
2
+ if Rails.version < '2.3'
3
+ class AbstractRequest
4
+ def relative_url_root
5
+ Facebooker.path_prefix
6
+ end
7
+ end
8
+ else
9
+ class Request
10
+ def relative_url_root
11
+ Facebooker.path_prefix
12
+ end
13
+ end
14
+ end
15
+
16
+ class Base
17
+ class << self
18
+ # alias :old_relative_url_root :relative_url_root
19
+ def relative_url_root
20
+ Facebooker.path_prefix
21
+ end
22
+ alias :old_relative_url_root :relative_url_root
23
+ end
24
+ end
25
+
26
+ class UrlRewriter
27
+ include Facebooker::Rails::BackwardsCompatibleParamChecks
28
+
29
+ RESERVED_OPTIONS << :canvas
30
+
31
+ def link_to_new_canvas?
32
+ one_or_true @request.parameters["fb_sig_in_new_facebook"]
33
+ end
34
+
35
+ def link_to_canvas?(params, options)
36
+ option_override = options[:canvas]
37
+ return false if option_override == false # important to check for false. nil should use default behavior
38
+ option_override || (can_safely_access_request_parameters? && (one_or_true(@request.parameters["fb_sig_in_canvas"]) || one_or_true(@request.parameters[:fb_sig_in_canvas]) || one_or_true(@request.parameters["fb_sig_is_ajax"]) ))
39
+ end
40
+
41
+ #rails blindly tries to merge things that may be nil into the parameters. Make sure this won't break
42
+ def can_safely_access_request_parameters?
43
+ @request.request_parameters
44
+ end
45
+
46
+ def rewrite_url_with_facebooker(*args)
47
+ options = args.first.is_a?(Hash) ? args.first : args.last
48
+ is_link_to_canvas = link_to_canvas?(@request.request_parameters, options)
49
+ if is_link_to_canvas && !options.has_key?(:host)
50
+ options[:host] = Facebooker.canvas_server_base
51
+ end
52
+ options.delete(:canvas)
53
+ Facebooker.request_for_canvas(is_link_to_canvas) do
54
+ rewrite_url_without_facebooker(*args)
55
+ end
56
+ end
57
+
58
+ alias_method_chain :rewrite_url, :facebooker
59
+
60
+ end
61
+ end
@@ -0,0 +1,835 @@
1
+ require 'action_pack'
2
+
3
+ module Facebooker
4
+ module Rails
5
+
6
+ # Facebook specific helpers for creating FBML
7
+ #
8
+ # All helpers that take a user as a parameter will get the Facebook UID from the facebook_id attribute if it exists.
9
+ # It will use to_s if the facebook_id attribute is not present.
10
+ #
11
+ module Helpers
12
+
13
+ include Facebooker::Rails::Helpers::FbConnect
14
+
15
+ def versioned_concat(string,binding)
16
+ if ignore_binding?
17
+ concat(string)
18
+ else
19
+ concat(string,binding)
20
+ end
21
+ end
22
+
23
+ # Create an fb:dialog
24
+ # id must be a unique name e.g. "my_dialog"
25
+ # cancel_button is true or false
26
+ def fb_dialog( id, cancel_button, &block )
27
+ content = capture(&block)
28
+ cancel_button = cancel_button ? 1 : 0 unless cancel_button == 0
29
+ versioned_concat( content_tag("fb:dialog", content, {:id => id, :cancel_button => cancel_button}), block.binding )
30
+ end
31
+
32
+ def fb_stream_publish(stream_post,user_message_prompt=nil,callback=nil,auto_publish=false,actor=nil)
33
+ stream_publish("Facebook.streamPublish",stream_post,user_message_prompt,callback,auto_publish,actor)
34
+ end
35
+
36
+
37
+ def fbjs_library
38
+ "<script>var _token = '#{form_authenticity_token}';var _hostname = '#{ActionController::Base.asset_host}'</script>"+
39
+ "#{javascript_include_tag 'facebooker'}"
40
+ end
41
+
42
+ def fb_iframe(src, options = {})
43
+ content_tag "fb:iframe", '', options.merge({ :src => src })
44
+ end
45
+
46
+ def fb_swf(src, options = {})
47
+ tag "fb:swf", options.merge(:swfsrc => src)
48
+ end
49
+
50
+ def fb_dialog_title( title )
51
+ content_tag "fb:dialog-title", title
52
+ end
53
+
54
+ def fb_dialog_content( &block )
55
+ content = capture(&block)
56
+ versioned_concat( content_tag("fb:dialog-content", content), block.binding )
57
+ end
58
+
59
+ def fb_dialog_button( type, value, options={} )
60
+ options.assert_valid_keys FB_DIALOG_BUTTON_VALID_OPTION_KEYS
61
+ options.merge! :type => type, :value => value
62
+ tag "fb:dialog-button", options
63
+ end
64
+
65
+ FB_DIALOG_BUTTON_VALID_OPTION_KEYS = [:close_dialog, :href, :form_id, :clickrewriteurl, :clickrewriteid, :clickrewriteform]
66
+
67
+ def fb_show_feed_dialog(action, user_message = "", prompt = "", callback = nil)
68
+ update_page do |page|
69
+ page.call "Facebook.showFeedDialog",action.template_id,action.data,action.body_general,action.target_ids,callback,prompt,user_message
70
+ end
71
+ end
72
+
73
+
74
+ # Create an fb:request-form without a selector
75
+ #
76
+ # The block passed to this tag is used as the content of the form
77
+ #
78
+ # The message param is the name sent to content_for that specifies the body of the message
79
+ #
80
+ # For example,
81
+ #
82
+ # <% content_for("invite_message") do %>
83
+ # This gets sent in the invite. <%= fb_req_choice("with a button!",new_poke_path) %>
84
+ # <% end %>
85
+ # <% fb_request_form("Poke","invite_message",create_poke_path) do %>
86
+ # Send a poke to: <%= fb_friend_selector %> <br />
87
+ # <%= fb_request_form_submit %>
88
+ # <% end %>
89
+ def fb_request_form(type,message_param,url,options={},&block)
90
+ content = capture(&block)
91
+ message = @template.instance_variable_get("@content_for_#{message_param}")
92
+ versioned_concat(content_tag("fb:request-form", content + token_tag,
93
+ {:action=>url,:method=>"post",:invite=>true,:type=>type,:content=>message}.merge(options)),
94
+ block.binding)
95
+ end
96
+
97
+ # Create a submit button for an <fb:request-form>
98
+ # If the request is for an individual user you can optionally
99
+ # Provide the user and a label for the request button.
100
+ # For example
101
+ # <% content_for("invite_user") do %>
102
+ # This gets sent in the invite. <%= fb_req_choice("Come join us!",new_invite_path) %>
103
+ # <% end %>
104
+ # <% fb_request_form("Invite","invite_user",create_invite_path) do %>
105
+ # Invite <%= fb_name(@facebook_user.friends.first.id)%> to the party <br />
106
+ # <%= fb_request_form_submit(:uid => @facebook_user.friends.first.id, :label => "Invite %n") %>
107
+ # <% end %>
108
+ # <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:request-form-submit for options
109
+ def fb_request_form_submit(options={})
110
+ tag("fb:request-form-submit",stringify_vals(options))
111
+ end
112
+
113
+
114
+ # Create an fb:request-form with an fb_multi_friend_selector inside
115
+ #
116
+ # The content of the block are used as the message on the form, the options hash is passed onto fb_multi_friend_selector.
117
+ #
118
+ # For example:
119
+ # <% fb_multi_friend_request("Poke","Choose some friends to Poke",create_poke_path,:exclude_ids => "123456789,987654321") do %>
120
+ # If you select some friends, they will see this message.
121
+ # <%= fb_req_choice("They will get this button, too",new_poke_path) %>
122
+ # <% end %>
123
+ def fb_multi_friend_request(type,friend_selector_message,url, fb_multi_friend_selector_options = {},&block)
124
+ content = capture(&block)
125
+ versioned_concat(content_tag("fb:request-form",
126
+ fb_multi_friend_selector(friend_selector_message, fb_multi_friend_selector_options) + token_tag,
127
+ {:action=>url,:method=>"post",:invite=>true,:type=>type,:content=>content}
128
+ ),
129
+ block.binding)
130
+ end
131
+
132
+ # Render an <fb:friend-selector> element
133
+ # <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:friend-selector for options
134
+ #
135
+ def fb_friend_selector(options={})
136
+ tag("fb:friend-selector",stringify_vals(options))
137
+ end
138
+
139
+ # Render an <fb:multi-friend-input> element
140
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:multi-friend-input for options
141
+ def fb_multi_friend_input(options={})
142
+ tag "fb:multi-friend-input",stringify_vals(options)
143
+ end
144
+
145
+ # Render an <fb:multi-friend-selector> with the passed in welcome message
146
+ # Full version shows all profile pics for friends.
147
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:multi-friend-selector for options
148
+ # <em> Note: </em> I don't think the block is used here.
149
+ def fb_multi_friend_selector(message,options={},&block)
150
+ options = options.dup
151
+ tag("fb:multi-friend-selector",stringify_vals({:showborder=>false,:actiontext=>message,:max=>20}.merge(options)))
152
+ end
153
+
154
+ # Render a condensed <fb:multi-friend-selector> with the passed in welcome message
155
+ # Condensed version show checkboxes for each friend.
156
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:multi-friend-selector_%28condensed%29 for options
157
+ # <em> Note: </em> I don't think the block is used here.
158
+ def fb_multi_friend_selector_condensed(options={},&block)
159
+ options = options.dup
160
+ tag("fb:multi-friend-selector",stringify_vals(options.merge(:condensed=>true)))
161
+ end
162
+
163
+ # Render a button in a request using the <fb:req-choice> tag
164
+ # url must be an absolute url
165
+ # This should be used inside the block of an fb_multi_friend_request
166
+ def fb_req_choice(label,url)
167
+ tag "fb:req-choice",:label=>label,:url=>url
168
+ end
169
+
170
+ # Create a facebook form using <fb:editor>
171
+ #
172
+ # It yields a form builder that will convert the standard rails form helpers
173
+ # into the facebook specific version.
174
+ #
175
+ # Example:
176
+ # <% facebook_form_for(:poke,@poke,:url => create_poke_path) do |f| %>
177
+ # <%= f.text_field :message, :label=>"message" %>
178
+ # <%= f.buttons "Save Poke" %>
179
+ # <% end %>
180
+ #
181
+ # will generate
182
+ #
183
+ # <fb:editor action="/pokes/create">
184
+ # <fb:editor-text name="poke[message]" id="poke_message" value="" label="message" />
185
+ # <fb:editor-buttonset>
186
+ # <fb:editor-button label="Save Poke"
187
+ # </fb:editor-buttonset>
188
+ # </fb:editor>
189
+ def facebook_form_for( record_or_name_or_array,*args, &proc)
190
+
191
+ raise ArgumentError, "Missing block" unless block_given?
192
+ options = args.last.is_a?(Hash) ? args.pop : {}
193
+
194
+ case record_or_name_or_array
195
+ when String, Symbol
196
+ object_name = record_or_name_or_array
197
+ when Array
198
+ object = record_or_name_or_array.last
199
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
200
+ apply_form_for_options!(record_or_name_or_array, options)
201
+ args.unshift object
202
+ else
203
+ object = record_or_name_or_array
204
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
205
+ apply_form_for_options!([object], options)
206
+ args.unshift object
207
+ end
208
+ method = (options[:html]||{})[:method]
209
+ options[:builder] ||= Facebooker::Rails::FacebookFormBuilder
210
+ editor_options={}
211
+
212
+ action=options.delete(:url)
213
+ editor_options[:action]= action unless action.blank?
214
+ width=options.delete(:width)
215
+ editor_options[:width]=width unless width.blank?
216
+ width=options.delete(:labelwidth)
217
+ editor_options[:labelwidth]=width unless width.blank?
218
+
219
+ versioned_concat(tag("fb:editor",editor_options,true) , proc.binding)
220
+ versioned_concat(tag(:input,{:type=>"hidden",:name=>:_method, :value=>method},false), proc.binding) unless method.blank?
221
+ versioned_concat(token_tag, proc.binding)
222
+ fields_for( object_name,*(args << options), &proc)
223
+ versioned_concat("</fb:editor>",proc.binding)
224
+ end
225
+
226
+ # Render an fb:application-name tag
227
+ #
228
+ # This renders the current application name via fbml. See
229
+ # http://wiki.developers.facebook.com/index.php/Fb:application-name
230
+ # for a full description.
231
+ #
232
+ def fb_application_name(options={})
233
+ tag "fb:application-name", stringify_vals(options)
234
+ end
235
+
236
+ # Render an fb:name tag for the given user
237
+ # This renders the name of the user specified. You can use this tag as both subject and object of
238
+ # a sentence. <em> See </em> http://wiki.developers.facebook.com/index.php/Fb:name for full description.
239
+ # Use this tag on FBML pages instead of retrieving the user's info and rendering the name explicitly.
240
+ #
241
+ def fb_name(user, options={})
242
+ options = options.dup
243
+ options.transform_keys!(FB_NAME_OPTION_KEYS_TO_TRANSFORM)
244
+ options.assert_valid_keys(FB_NAME_VALID_OPTION_KEYS)
245
+ options.merge!(:uid => cast_to_facebook_id(user))
246
+ content_tag("fb:name",nil, stringify_vals(options))
247
+ end
248
+
249
+ FB_NAME_OPTION_KEYS_TO_TRANSFORM = {:first_name_only => :firstnameonly,
250
+ :last_name_only => :lastnameonly,
251
+ :show_network => :shownetwork,
252
+ :use_you => :useyou,
253
+ :if_cant_see => :ifcantsee,
254
+ :subject_id => :subjectid}
255
+ FB_NAME_VALID_OPTION_KEYS = [:firstnameonly, :linked, :lastnameonly, :possessive, :reflexive,
256
+ :shownetwork, :useyou, :ifcantsee, :capitalize, :subjectid]
257
+
258
+ # Render an <fb:pronoun> tag for the specified user
259
+ # Options give flexibility for placing in any part of a sentence.
260
+ # <em> See </em> http://wiki.developers.facebook.com/index.php/Fb:pronoun for complete list of options.
261
+ #
262
+ def fb_pronoun(user, options={})
263
+ options = options.dup
264
+ options.transform_keys!(FB_PRONOUN_OPTION_KEYS_TO_TRANSFORM)
265
+ options.assert_valid_keys(FB_PRONOUN_VALID_OPTION_KEYS)
266
+ options.merge!(:uid => cast_to_facebook_id(user))
267
+ content_tag("fb:pronoun",nil, stringify_vals(options))
268
+ end
269
+
270
+ FB_PRONOUN_OPTION_KEYS_TO_TRANSFORM = {:use_you => :useyou, :use_they => :usethey}
271
+ FB_PRONOUN_VALID_OPTION_KEYS = [:useyou, :possessive, :reflexive, :objective,
272
+ :usethey, :capitalize]
273
+
274
+ # Render an fb:ref tag.
275
+ # Options must contain either url or handle.
276
+ # * <em> url </em> The URL from which to fetch the FBML. You may need to call fbml.refreshRefUrl to refresh cache
277
+ # * <em> handle </em> The string previously set by fbml.setRefHandle that identifies the FBML
278
+ # <em> See </em> http://wiki.developers.facebook.com/index.php/Fb:ref for complete description
279
+ def fb_ref(options)
280
+ options.assert_valid_keys(FB_REF_VALID_OPTION_KEYS)
281
+ validate_fb_ref_has_either_url_or_handle(options)
282
+ validate_fb_ref_does_not_have_both_url_and_handle(options)
283
+ tag("fb:ref", stringify_vals(options))
284
+ end
285
+
286
+ def validate_fb_ref_has_either_url_or_handle(options)
287
+ unless options.has_key?(:url) || options.has_key?(:handle)
288
+ raise ArgumentError, "fb_ref requires :url or :handle"
289
+ end
290
+ end
291
+
292
+ def validate_fb_ref_does_not_have_both_url_and_handle(options)
293
+ if options.has_key?(:url) && options.has_key?(:handle)
294
+ raise ArgumentError, "fb_ref may not have both :url and :handle"
295
+ end
296
+ end
297
+
298
+ FB_REF_VALID_OPTION_KEYS = [:url, :handle]
299
+
300
+ # Render an <fb:profile-pic> for the specified user.
301
+ #
302
+ # You can optionally specify the size using the :size=> option. Valid
303
+ # sizes are :thumb, :small, :normal and :square. Or, you can specify
304
+ # width and height settings instead, just like an img tag.
305
+ #
306
+ # You can optionally specify whether or not to include the facebook icon
307
+ # overlay using the :facebook_logo => true option. Default is false.
308
+ #
309
+ def fb_profile_pic(user, options={})
310
+ options = options.dup
311
+ validate_fb_profile_pic_size(options)
312
+ options.transform_keys!(FB_PROFILE_PIC_OPTION_KEYS_TO_TRANSFORM)
313
+ options.assert_valid_keys(FB_PROFILE_PIC_VALID_OPTION_KEYS)
314
+ options.merge!(:uid => cast_to_facebook_id(user))
315
+ content_tag("fb:profile-pic", nil,stringify_vals(options))
316
+ end
317
+
318
+ FB_PROFILE_PIC_OPTION_KEYS_TO_TRANSFORM = {:facebook_logo => 'facebook-logo'}
319
+ FB_PROFILE_PIC_VALID_OPTION_KEYS = [:size, :linked, 'facebook-logo', :width, :height]
320
+
321
+
322
+ # Render an fb:photo tag.
323
+ # photo is either a Facebooker::Photo or an id of a Facebook photo or an object that responds to photo_id.
324
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:photo for complete list of options.
325
+ def fb_photo(photo, options={})
326
+ options = options.dup
327
+ options.assert_valid_keys(FB_PHOTO_VALID_OPTION_KEYS)
328
+ options.merge!(:pid => cast_to_photo_id(photo))
329
+ validate_fb_photo_size(options)
330
+ validate_fb_photo_align_value(options)
331
+ content_tag("fb:photo",nil, stringify_vals(options))
332
+ end
333
+
334
+ FB_PHOTO_VALID_OPTION_KEYS = [:uid, :size, :align]
335
+
336
+ def cast_to_photo_id(object)
337
+ object.respond_to?(:photo_id) ? object.photo_id : object
338
+ end
339
+
340
+ VALID_FB_SHARED_PHOTO_SIZES = [:thumb, :small, :normal, :square]
341
+ VALID_FB_PHOTO_SIZES = VALID_FB_SHARED_PHOTO_SIZES
342
+ VALID_FB_PROFILE_PIC_SIZES = VALID_FB_SHARED_PHOTO_SIZES
343
+ VALID_PERMISSIONS=[:email, :offline_access, :status_update, :photo_upload, :create_listing, :create_event, :rsvp_event, :sms, :video_upload,
344
+ :publish_stream, :read_stream, :read_mailbox]
345
+
346
+ # Render an fb:tabs tag containing some number of fb:tab_item tags.
347
+ # Example:
348
+ # <% fb_tabs do %>
349
+ # <%= fb_tab_item("Home", "home") %>
350
+ # <%= fb_tab_item("Office", "office") %>
351
+ # <% end %>
352
+ def fb_tabs(&block)
353
+ content = capture(&block)
354
+ versioned_concat(content_tag("fb:tabs", content), block.binding)
355
+ end
356
+
357
+ # Render an fb:tab_item tag.
358
+ # Use this in conjunction with fb_tabs
359
+ # Options can contains :selected => true to indicate that a tab is the current tab.
360
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:tab-item for complete list of options
361
+ def fb_tab_item(title, url, options={})
362
+ options= options.dup
363
+ options.assert_valid_keys(FB_TAB_ITEM_VALID_OPTION_KEYS)
364
+ options.merge!(:title => title, :href => url)
365
+ validate_fb_tab_item_align_value(options)
366
+ tag("fb:tab-item", stringify_vals(options))
367
+ end
368
+
369
+ FB_TAB_ITEM_VALID_OPTION_KEYS = [:align, :selected]
370
+
371
+ def validate_fb_tab_item_align_value(options)
372
+ if options.has_key?(:align) && !VALID_FB_TAB_ITEM_ALIGN_VALUES.include?(options[:align].to_sym)
373
+ raise(ArgumentError, "Unknown value for align: #{options[:align]}")
374
+ end
375
+ end
376
+
377
+ def validate_fb_photo_align_value(options)
378
+ if options.has_key?(:align) && !VALID_FB_PHOTO_ALIGN_VALUES.include?(options[:align].to_sym)
379
+ raise(ArgumentError, "Unknown value for align: #{options[:align]}")
380
+ end
381
+ end
382
+
383
+ VALID_FB_SHARED_ALIGN_VALUES = [:left, :right]
384
+ VALID_FB_PHOTO_ALIGN_VALUES = VALID_FB_SHARED_ALIGN_VALUES
385
+ VALID_FB_TAB_ITEM_ALIGN_VALUES = VALID_FB_SHARED_ALIGN_VALUES
386
+
387
+
388
+ # Create a Facebook wall. It can contain fb_wall_posts
389
+ #
390
+ # For Example:
391
+ # <% fb_wall do %>
392
+ # <%= fb_wall_post(@user,"This is my message") %>
393
+ # <%= fb_wall_post(@otheruser,"This is another message") %>
394
+ # <% end %>
395
+ def fb_wall(&proc)
396
+ content = capture(&proc)
397
+ versioned_concat(content_tag("fb:wall",content,{}),proc.binding)
398
+ end
399
+
400
+ # Render an <fb:wallpost> tag
401
+ # TODO: Optionally takes a time parameter t = int The current time, which is displayed in epoch seconds.
402
+ def fb_wallpost(user,message)
403
+ content_tag("fb:wallpost",message,:uid=>cast_to_facebook_id(user))
404
+ end
405
+ alias_method :fb_wall_post, :fb_wallpost
406
+
407
+ # Render an <fb:error> tag
408
+ # If message and text are present then this will render fb:error and fb:message tag
409
+ # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten'
410
+ def fb_error(message, text=nil)
411
+ fb_status_msg("error", message, text)
412
+ end
413
+
414
+ # Render an <fb:explanation> tag
415
+ # If message and text are present then this will render fb:error and fb:message tag
416
+ # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten'
417
+ def fb_explanation(message, text=nil)
418
+ fb_status_msg("explanation", message, text)
419
+ end
420
+
421
+ # Render an <fb:success> tag
422
+ # If message and text are present then this will render fb:error and fb:message tag
423
+ # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten'
424
+ def fb_success(message, text=nil)
425
+ fb_status_msg("success", message, text)
426
+ end
427
+
428
+ # Render flash values as <fb:message> and <fb:error> tags
429
+ #
430
+ # values in flash[:notice] will be rendered as an <fb:message>
431
+ #
432
+ # values in flash[:error] will be rendered as an <fb:error>
433
+ # TODO: Allow flash[:info] to render fb_explanation
434
+ def facebook_messages
435
+ message=""
436
+ unless flash[:notice].blank?
437
+ message += fb_success(*flash[:notice])
438
+ end
439
+ unless flash[:error].blank?
440
+ message += fb_error(*flash[:error])
441
+ end
442
+ message
443
+ end
444
+
445
+ # Create a dashboard. It can contain fb_action, fb_help, and fb_create_button
446
+ #
447
+ # For Example:
448
+ # <% fb_dashboard do %>
449
+ # <%= APP_NAME %>
450
+ # <%= fb_action 'My Matches', search_path %>
451
+ # <%= fb_help 'Feedback', "http://www.facebook.com/apps/application.php?id=6236036681" %>
452
+ # <%= fb_create_button 'Invite Friends', main_path %>
453
+ # <% end %>
454
+ def fb_dashboard(&proc)
455
+ if block_given?
456
+ content = capture(&proc)
457
+ versioned_concat(content_tag("fb:dashboard",content,{}),proc.binding)
458
+ else
459
+ content_tag("fb:dashboard",content,{})
460
+ end
461
+ end
462
+
463
+ # Content for the wide profile box goes in this tag
464
+ def fb_wide(&proc)
465
+ content = capture(&proc)
466
+ versioned_concat(content_tag("fb:wide", content, {}), proc.binding)
467
+ end
468
+
469
+ # Content for the narrow profile box goes in this tag
470
+ def fb_narrow(&proc)
471
+ content = capture(&proc)
472
+ versioned_concat(content_tag("fb:narrow", content, {}), proc.binding)
473
+ end
474
+
475
+ # Renders an action using the <fb:action> tag
476
+ def fb_action(name, url)
477
+ "<fb:action href=\"#{url_for(url)}\">#{name}</fb:action>"
478
+ end
479
+
480
+ # Render a <fb:help> tag
481
+ # For use inside <fb:dashboard>
482
+ def fb_help(name, url)
483
+ "<fb:help href=\"#{url_for(url)}\">#{name}</fb:help>"
484
+ end
485
+
486
+ # Render a <fb:create-button> tag
487
+ # For use inside <fb:dashboard>
488
+ def fb_create_button(name, url)
489
+ "<fb:create-button href=\"#{url_for(url)}\">#{name}</fb:create-button>"
490
+ end
491
+
492
+ # Create a comment area
493
+ # All the data for this content area is stored on the facebook servers.
494
+ # <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:comments for full details
495
+ def fb_comments(xid,canpost=true,candelete=false,numposts=5,options={})
496
+ options = options.dup
497
+ title = (title = options.delete(:title)) ? fb_title(title) : nil
498
+ content_tag "fb:comments",title,stringify_vals(options.merge(:xid=>xid,:canpost=>canpost.to_s,:candelete=>candelete.to_s,:numposts=>numposts))
499
+ end
500
+
501
+ # Adds a title to the title bar
502
+ #
503
+ # Facebook | App Name | This is the canvas page window title
504
+ #
505
+ # +title+: This is the canvas page window
506
+ def fb_title(title)
507
+ "<fb:title>#{title}</fb:title>"
508
+ end
509
+
510
+ # Create a Google Analytics tag
511
+ #
512
+ # +uacct+: Your Urchin/Google Analytics account ID.
513
+ def fb_google_analytics(uacct, options={})
514
+ options = options.dup
515
+ tag "fb:google-analytics", stringify_vals(options.merge(:uacct => uacct))
516
+ end
517
+
518
+ # Render if-is-app-user tag
519
+ # This tag renders the enclosing content only if the user specified has accepted the terms of service for the application.
520
+ # Use fb_if_user_has_added_app to determine wether the user has added the app.
521
+ # Example:
522
+ # <% fb_if_is_app_user(@facebook_user) do %>
523
+ # Thanks for accepting our terms of service!
524
+ # <% fb_else do %>
525
+ # Hey you haven't agreed to our terms. <%= link_to("Please accept our terms of service.", :action => "terms_of_service") %>
526
+ # <% end %>
527
+ #<% end %>
528
+ def fb_if_is_app_user(user=nil,options={},&proc)
529
+ content = capture(&proc)
530
+ options = options.dup
531
+ options.merge!(:uid=>cast_to_facebook_id(user)) if user
532
+ versioned_concat(content_tag("fb:if-is-app-user",content,stringify_vals(options)),proc.binding)
533
+ end
534
+
535
+ # Render if-user-has-added-app tag
536
+ # This tag renders the enclosing content only if the user specified has installed the application
537
+ #
538
+ # Example:
539
+ # <% fb_if_user_has_added_app(@facebook_user) do %>
540
+ # Hey you are an app user!
541
+ # <% fb_else do %>
542
+ # Hey you aren't an app user. <%= link_to("Add App and see the other side.", :action => "added_app") %>
543
+ # <% end %>
544
+ #<% end %>
545
+ def fb_if_user_has_added_app(user,options={},&proc)
546
+ content = capture(&proc)
547
+ options = options.dup
548
+ versioned_concat(content_tag("fb:if-user-has-added-app", content, stringify_vals(options.merge(:uid=>cast_to_facebook_id(user)))),proc.binding)
549
+ end
550
+
551
+ # Render fb:if-is-user tag
552
+ # This tag only renders enclosing content if the user is one of those specified
553
+ # user can be a single user or an Array of users
554
+ # Example:
555
+ # <% fb_if_is_user(@check_user) do %>
556
+ # <%= fb_name(@facebook_user) %> are one of the users. <%= link_to("Check the other side", :action => "friend") %>
557
+ # <% fb_else do %>
558
+ # <%= fb_name(@facebook_user) %> are not one of the users <%= fb_name(@check_user) %>
559
+ # <%= link_to("Check the other side", :action => "you") %>
560
+ # <% end %>
561
+ # <% end %>
562
+ def fb_if_is_user(user,&proc)
563
+ content = capture(&proc)
564
+ user = [user] unless user.is_a? Array
565
+ user_list=user.map{|u| cast_to_facebook_id(u)}.join(",")
566
+ versioned_concat(content_tag("fb:if-is-user",content,{:uid=>user_list}),proc.binding)
567
+ end
568
+
569
+ # Render fb:else tag
570
+ # Must be used within if block such as fb_if_is_user or fb_if_is_app_user . See example in fb_if_is_app_user
571
+ def fb_else(&proc)
572
+ content = capture(&proc)
573
+ versioned_concat(content_tag("fb:else",content),proc.binding)
574
+ end
575
+
576
+ #
577
+ # Return the URL for the about page of the application
578
+ def fb_about_url
579
+ "http://#{Facebooker.www_server_base_url}/apps/application.php?api_key=#{Facebooker.api_key}"
580
+ end
581
+
582
+ #
583
+ # Embed a discussion board named xid on the current page
584
+ # <em>See</em http://wiki.developers.facebook.com/index.php/Fb:board for more details
585
+ # Options are:
586
+ # * canpost
587
+ # * candelete
588
+ # * canmark
589
+ # * cancreatet
590
+ # * numtopics
591
+ # * callbackurl
592
+ # * returnurl
593
+ #
594
+ def fb_board(xid,options={})
595
+ options = options.dup
596
+ title = (title = options.delete(:title)) ? fb_title(title) : nil
597
+ content_tag("fb:board", title, stringify_vals(options.merge(:xid=>xid)))
598
+ end
599
+
600
+ # Renders an 'Add to Profile' button
601
+ # The button allows a user to add condensed profile box to the main profile
602
+ def fb_add_profile_section
603
+ tag "fb:add-section-button",:section=>"profile"
604
+ end
605
+
606
+ # Renders an 'Add to Info' button
607
+ # The button allows a user to add an application info section to her Info tab
608
+ def fb_add_info_section
609
+ tag "fb:add-section-button",:section=>"info"
610
+ end
611
+
612
+ # Renders a link that, when clicked, initiates a dialog requesting the specified extended permission from the user.
613
+ #
614
+ # You can prompt a user with the following permissions:
615
+ # * email
616
+ # * read_stream
617
+ # * publish_stream
618
+ # * offline_access
619
+ # * status_update
620
+ # * photo_upload
621
+ # * create_event
622
+ # * rsvp_event
623
+ # * sms
624
+ # * video_upload
625
+ # * create_note
626
+ # * share_item
627
+ # * read_mailbox
628
+ # Example:
629
+ # <%= fb_prompt_permission('email', "Would you like to receive email from our application?" ) %>
630
+ #
631
+ # See http://wiki.developers.facebook.com/index.php/Fb:prompt-permission for
632
+ # more details. Correct as of 7th June 2009.
633
+ #
634
+ def fb_prompt_permission(permission,message,callback=nil)
635
+ raise(ArgumentError, "Unknown value for permission: #{permission}") unless VALID_PERMISSIONS.include?(permission.to_sym)
636
+ args={:perms=>permission}
637
+ args[:next_fbjs]=callback unless callback.nil?
638
+ content_tag("fb:prompt-permission",message,args)
639
+ end
640
+
641
+ # Renders a link to prompt for multiple permissions at once.
642
+ #
643
+ # Example:
644
+ # <%= fb_prompt_permissions(['email','offline_access','status_update'], 'Would you like to grant some permissions?')
645
+ def fb_prompt_permissions(permissions,message,callback=nil)
646
+ permissions.each do |p|
647
+ raise(ArgumentError, "Unknown value for permission: #{p}") unless VALID_PERMISSIONS.include?(p.to_sym)
648
+ end
649
+ args={:perms=>permissions*','}
650
+ args[:next_fbjs]=callback unless callback.nil?
651
+ content_tag("fb:prompt-permission",message,args)
652
+ end
653
+
654
+ # Renders an <fb:eventlink /> tag that displays the event name and links to the event's page.
655
+ def fb_eventlink(eid)
656
+ content_tag "fb:eventlink",nil,:eid=>eid
657
+ end
658
+
659
+ # Renders an <fb:grouplink /> tag that displays the group name and links to the group's page.
660
+ def fb_grouplink(gid)
661
+ content_tag "fb:grouplink",nil,:gid=>gid
662
+ end
663
+
664
+ # Returns the status of the user
665
+ def fb_user_status(user,linked=true)
666
+ content_tag "fb:user-status",nil,stringify_vals(:uid=>cast_to_facebook_id(user), :linked=>linked)
667
+ end
668
+
669
+ # Renders a standard 'Share' button for the specified URL.
670
+ def fb_share_button(url)
671
+ content_tag "fb:share-button",nil,:class=>"url",:href=>url
672
+ end
673
+
674
+ # Renders the FBML on a Facebook server inside an iframe.
675
+ #
676
+ # Meant to be used for a Facebook Connect site or an iframe application
677
+ def fb_serverfbml(options={},&proc)
678
+ inner = capture(&proc)
679
+ versioned_concat(content_tag("fb:serverfbml",inner,options), proc.binding)
680
+ end
681
+
682
+ def fb_container(options={},&proc)
683
+ inner = capture(&proc)
684
+ versioned_concat(content_tag("fb:container",inner,options), proc.binding)
685
+ end
686
+
687
+ # Renders an fb:time element
688
+ #
689
+ # Example:
690
+ # <%= fb_time(Time.now, :tz => 'America/New York', :preposition => true) %>
691
+ #
692
+ # See http://wiki.developers.facebook.com/index.php/Fb:time for
693
+ # more details
694
+ def fb_time(time, options={})
695
+ tag "fb:time",stringify_vals({:t => time.to_i}.merge(options))
696
+ end
697
+
698
+ # Renders a fb:intl element
699
+ #
700
+ # Example:
701
+ # <%= fb_intl('Age', :desc => 'Label for the age form field', :delimiters => '[]') %>
702
+ #
703
+ # See http://wiki.developers.facebook.com/index.php/Fb:intl for
704
+ # more details
705
+ def fb_intl(text=nil, options={}, &proc)
706
+ raise ArgumentError, "Missing block or text" unless block_given? or text
707
+ if block_given?
708
+ versioned_concat(fb_intl(capture(&proc), options))
709
+ else
710
+ content_tag("fb:intl", text, stringify_vals(options))
711
+ end
712
+ end
713
+
714
+ # Renders a fb:intl-token element
715
+ #
716
+ # Example:
717
+ # <%= fb_intl-token('number', 5) %>
718
+ #
719
+ # See http://wiki.developers.facebook.com/index.php/Fb:intl-token for
720
+ # more details
721
+ def fb_intl_token(name, text=nil, &proc)
722
+ raise ArgumentError, "Missing block or text" unless block_given? or text
723
+ if block_given?
724
+ versioned_concat(fb_intl_token(name, capture(&proc)))
725
+ else
726
+ content_tag("fb:intl-token", text, stringify_vals({:name => name}))
727
+ end
728
+ end
729
+
730
+ # Renders a fb:date element
731
+ #
732
+ # Example:
733
+ # <%= fb_date(Time.now, :format => 'verbose', :tz => 'America/New York') %>
734
+ #
735
+ # See http://wiki.developers.facebook.com/index.php/Fb:date for
736
+ # more details
737
+ def fb_date(time, options={})
738
+ tag "fb:date", stringify_vals({:t => time.to_i}.merge(options))
739
+ end
740
+
741
+ # Renders the Facebook bookmark button
742
+ #
743
+ # See http://wiki.developers.facebook.com/index.php/Fb:bookmark for
744
+ # more details
745
+ def fb_bookmark_button
746
+ content_tag "fb:bookmark"
747
+ end
748
+
749
+ # Renders a fb:fbml-attribute element
750
+ #
751
+ # Example:
752
+ # <%= fb_fbml_attribute('title', Education) %>
753
+ #
754
+ # The options hash is passed to the fb:intl element that is generated inside this element
755
+ # and can have the keys available for the fb:intl element.
756
+ #
757
+ # See http://wiki.developers.facebook.com/index.php/Fb:fbml-attribute for
758
+ # more details
759
+ def fb_fbml_attribute(name, text, options={})
760
+ content_tag("fb:fbml-attribute", fb_intl(text, options), stringify_vals({:name => name}))
761
+ end
762
+
763
+ protected
764
+
765
+ def cast_to_facebook_id(object)
766
+ Facebooker::User.cast_to_facebook_id(object)
767
+ end
768
+
769
+ def validate_fb_profile_pic_size(options)
770
+ if options.has_key?(:size) && !VALID_FB_PROFILE_PIC_SIZES.include?(options[:size].to_sym)
771
+ raise(ArgumentError, "Unknown value for size: #{options[:size]}")
772
+ end
773
+ end
774
+
775
+ def validate_fb_photo_size(options)
776
+ if options.has_key?(:size) && !VALID_FB_PHOTO_SIZES.include?(options[:size].to_sym)
777
+ raise(ArgumentError, "Unknown value for size: #{options[:size]}")
778
+ end
779
+ end
780
+
781
+ private
782
+ def stringify_vals(hash)
783
+ result={}
784
+ hash.each do |key,value|
785
+ result[key]=value.to_s
786
+ end
787
+ result
788
+ end
789
+
790
+ def fb_status_msg(type, message, text)
791
+ if text.blank?
792
+ tag("fb:#{type}", :message => message)
793
+ else
794
+ content_tag("fb:#{type}", content_tag("fb:message", message) + text)
795
+ end
796
+ end
797
+
798
+ def token_tag
799
+ unless protect_against_forgery?
800
+ ''
801
+ else
802
+ tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
803
+ end
804
+ end
805
+
806
+ def ignore_binding?
807
+ ActionPack::VERSION::MAJOR >= 2 && ActionPack::VERSION::MINOR >= 2
808
+ end
809
+ end
810
+ end
811
+ end
812
+
813
+ class Hash
814
+ def transform_keys!(transformation_hash)
815
+ transformation_hash.each_pair{|key, value| transform_key!(key, value)}
816
+ end
817
+
818
+
819
+ def transform_key!(old_key, new_key)
820
+ swapkey!(new_key, old_key)
821
+ end
822
+
823
+ ### This method is lifted from Ruby Facets core
824
+ def swapkey!( newkey, oldkey )
825
+ self[newkey] = self.delete(oldkey) if self.has_key?(oldkey)
826
+ self
827
+ end
828
+
829
+ # We can allow css attributes.
830
+ FB_ALWAYS_VALID_OPTION_KEYS = [:class, :style]
831
+ def assert_valid_keys(*valid_keys)
832
+ unknown_keys = keys - [valid_keys + FB_ALWAYS_VALID_OPTION_KEYS].flatten
833
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
834
+ end
835
+ end