fs-facebooker 1.0.37

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