facebooker 0.9.5

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 (60) hide show
  1. data/CHANGELOG.txt +0 -0
  2. data/COPYING +19 -0
  3. data/History.txt +3 -0
  4. data/Manifest.txt +60 -0
  5. data/README +44 -0
  6. data/README.txt +79 -0
  7. data/Rakefile +38 -0
  8. data/TODO.txt +10 -0
  9. data/facebooker.yml.tpl +36 -0
  10. data/init.rb +52 -0
  11. data/install.rb +5 -0
  12. data/lib/facebooker.rb +63 -0
  13. data/lib/facebooker/affiliation.rb +10 -0
  14. data/lib/facebooker/album.rb +11 -0
  15. data/lib/facebooker/cookie.rb +10 -0
  16. data/lib/facebooker/data.rb +38 -0
  17. data/lib/facebooker/education_info.rb +11 -0
  18. data/lib/facebooker/event.rb +26 -0
  19. data/lib/facebooker/feed.rb +65 -0
  20. data/lib/facebooker/group.rb +35 -0
  21. data/lib/facebooker/location.rb +8 -0
  22. data/lib/facebooker/model.rb +118 -0
  23. data/lib/facebooker/notifications.rb +17 -0
  24. data/lib/facebooker/parser.rb +386 -0
  25. data/lib/facebooker/photo.rb +9 -0
  26. data/lib/facebooker/rails/controller.rb +174 -0
  27. data/lib/facebooker/rails/facebook_asset_path.rb +18 -0
  28. data/lib/facebooker/rails/facebook_form_builder.rb +112 -0
  29. data/lib/facebooker/rails/facebook_request_fix.rb +14 -0
  30. data/lib/facebooker/rails/facebook_session_handling.rb +58 -0
  31. data/lib/facebooker/rails/facebook_url_rewriting.rb +31 -0
  32. data/lib/facebooker/rails/helpers.rb +535 -0
  33. data/lib/facebooker/rails/routing.rb +49 -0
  34. data/lib/facebooker/rails/test_helpers.rb +11 -0
  35. data/lib/facebooker/rails/utilities.rb +22 -0
  36. data/lib/facebooker/server_cache.rb +24 -0
  37. data/lib/facebooker/service.rb +25 -0
  38. data/lib/facebooker/session.rb +385 -0
  39. data/lib/facebooker/tag.rb +12 -0
  40. data/lib/facebooker/user.rb +200 -0
  41. data/lib/facebooker/version.rb +9 -0
  42. data/lib/facebooker/work_info.rb +9 -0
  43. data/lib/net/http_multipart_post.rb +123 -0
  44. data/lib/tasks/facebooker.rake +16 -0
  45. data/lib/tasks/tunnel.rake +39 -0
  46. data/setup.rb +1585 -0
  47. data/test/event_test.rb +15 -0
  48. data/test/facebook_cache_test.rb +43 -0
  49. data/test/facebook_data_test.rb +50 -0
  50. data/test/facebooker_test.rb +766 -0
  51. data/test/fixtures/multipart_post_body_with_only_parameters.txt +33 -0
  52. data/test/fixtures/multipart_post_body_with_single_file.txt +38 -0
  53. data/test/fixtures/multipart_post_body_with_single_file_that_has_nil_key.txt +38 -0
  54. data/test/http_multipart_post_test.rb +54 -0
  55. data/test/model_test.rb +79 -0
  56. data/test/rails_integration_test.rb +732 -0
  57. data/test/session_test.rb +396 -0
  58. data/test/test_helper.rb +54 -0
  59. data/test/user_test.rb +101 -0
  60. metadata +130 -0
@@ -0,0 +1,9 @@
1
+ require 'facebooker/model'
2
+ module Facebooker
3
+ class Photo
4
+ include Model
5
+ attr_accessor :pid, :aid, :owner, :title,
6
+ :link, :caption, :created,
7
+ :src, :src_big, :src_small
8
+ end
9
+ end
@@ -0,0 +1,174 @@
1
+ require 'facebooker'
2
+ module Facebooker
3
+ module Rails
4
+ module Controller
5
+ def self.included(controller)
6
+ controller.extend(ClassMethods)
7
+ controller.before_filter :set_fbml_format
8
+ controller.helper_attr :facebook_session_parameters
9
+
10
+ end
11
+
12
+ def facebook_session
13
+ @facebook_session
14
+ end
15
+
16
+ def facebook_session_parameters
17
+ {:fb_sig_session_key=>params[:fb_sig_session_key]}
18
+ end
19
+
20
+
21
+ def set_facebook_session
22
+
23
+ returning session_set = session_already_secured? || secure_with_token! || secure_with_facebook_params! do
24
+ if session_set
25
+ capture_facebook_friends_if_available!
26
+ Session.current = facebook_session
27
+ end
28
+ end
29
+ end
30
+
31
+ def facebook_params
32
+ @facebook_params ||= verified_facebook_params
33
+ end
34
+
35
+ private
36
+
37
+ def session_already_secured?
38
+ (@facebook_session = session[:facebook_session]) && session[:facebook_session].secured?
39
+ end
40
+
41
+ def secure_with_token!
42
+ if params['auth_token']
43
+ @facebook_session = new_facebook_session
44
+ @facebook_session.auth_token = params['auth_token']
45
+ @facebook_session.secure!
46
+ session[:facebook_session] = @facebook_session
47
+ end
48
+ end
49
+
50
+ def secure_with_facebook_params!
51
+ return unless request_is_for_a_facebook_canvas?
52
+
53
+ if ['user', 'session_key'].all? {|element| facebook_params[element]}
54
+ @facebook_session = new_facebook_session
55
+ @facebook_session.secure_with!(facebook_params['session_key'], facebook_params['user'], facebook_params['expires'])
56
+ session[:facebook_session] = @facebook_session
57
+ end
58
+ end
59
+
60
+ def create_new_facebook_session_and_redirect!
61
+ session[:facebook_session] = new_facebook_session
62
+ redirect_to session[:facebook_session].login_url unless @installation_required
63
+ false
64
+ end
65
+
66
+ def new_facebook_session
67
+ Facebooker::Session.create(Facebooker::Session.api_key, Facebooker::Session.secret_key)
68
+ end
69
+
70
+ def capture_facebook_friends_if_available!
71
+ return unless request_is_for_a_facebook_canvas?
72
+ if friends = facebook_params['friends']
73
+ facebook_session.user.friends = friends.map do |friend_uid|
74
+ User.new(friend_uid, facebook_session)
75
+ end
76
+ end
77
+ end
78
+
79
+ def blank?(value)
80
+ (value == '0' || value.nil? || value == '')
81
+ end
82
+
83
+ def verified_facebook_params
84
+ facebook_sig_params = params.inject({}) do |collection, pair|
85
+ collection[pair.first.sub(/^fb_sig_/, '')] = pair.last if pair.first[0,7] == 'fb_sig_'
86
+ collection
87
+ end
88
+ verify_signature(facebook_sig_params,params['fb_sig'])
89
+
90
+ facebook_sig_params.inject(HashWithIndifferentAccess.new) do |collection, pair|
91
+ collection[pair.first] = facebook_parameter_conversions[pair.first].call(pair.last)
92
+ collection
93
+ end
94
+ end
95
+
96
+ def earliest_valid_session
97
+ 48.hours.ago
98
+ end
99
+
100
+ def verify_signature(facebook_sig_params,expected_signature)
101
+ raw_string = facebook_sig_params.map{ |*args| args.join('=') }.sort.join
102
+ actual_sig = Digest::MD5.hexdigest([raw_string, Facebooker::Session.secret_key].join)
103
+ raise Facebooker::Session::IncorrectSignature if actual_sig != expected_signature
104
+ raise Facebooker::Session::SignatureTooOld if Time.at(facebook_sig_params['time'].to_f) < earliest_valid_session
105
+ true
106
+ end
107
+
108
+ def facebook_parameter_conversions
109
+ @facebook_parameter_conversions ||= Hash.new do |hash, key|
110
+ lambda{|value| value}
111
+ end.merge(
112
+ 'time' => lambda{|value| Time.at(value.to_f)},
113
+ 'in_canvas' => lambda{|value| !blank?(value)},
114
+ 'added' => lambda{|value| !blank?(value)},
115
+ 'expires' => lambda{|value| blank?(value) ? nil : Time.at(value.to_f)},
116
+ 'friends' => lambda{|value| value.split(/,/)}
117
+ )
118
+ end
119
+
120
+ def redirect_to(*args)
121
+ if request_is_for_a_facebook_canvas?
122
+ render :text => fbml_redirect_tag(*args)
123
+ else
124
+ super
125
+ end
126
+ end
127
+
128
+ def fbml_redirect_tag(url)
129
+ "<fb:redirect url=\"#{url_for(url)}\" />"
130
+ end
131
+
132
+ def request_is_for_a_facebook_canvas?
133
+ !params['fb_sig_in_canvas'].blank?
134
+ end
135
+
136
+ def application_is_installed?
137
+ facebook_params['added']
138
+ end
139
+
140
+ def ensure_authenticated_to_facebook
141
+ set_facebook_session || create_new_facebook_session_and_redirect!
142
+ end
143
+
144
+ def ensure_application_is_installed_by_facebook_user
145
+ @installation_required = true
146
+ returning ensure_authenticated_to_facebook && application_is_installed? do |authenticated_and_installed|
147
+ application_is_not_installed_by_facebook_user unless authenticated_and_installed
148
+ end
149
+ end
150
+
151
+ def application_is_not_installed_by_facebook_user
152
+ redirect_to session[:facebook_session].install_url
153
+ end
154
+
155
+ def set_fbml_format
156
+ params[:format]="fbml" if request_is_for_a_facebook_canvas?
157
+ end
158
+
159
+ module ClassMethods
160
+ #
161
+ # Creates a filter which reqires a user to have already authenticated to
162
+ # Facebook before executing actions. Accepts the same optional options hash which
163
+ # before_filter and after_filter accept.
164
+ def ensure_authenticated_to_facebook(options = {})
165
+ before_filter :ensure_authenticated_to_facebook, options
166
+ end
167
+
168
+ def ensure_application_is_installed_by_facebook_user(options = {})
169
+ before_filter :ensure_application_is_installed_by_facebook_user, options
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,18 @@
1
+ # module ActionView
2
+ # module Helpers
3
+ # module AssetTagHelper
4
+ # def compute_public_path_with_facebooker(*args)
5
+ # public_path=compute_public_path_without_facebooker(*args)
6
+ # if public_path.starts_with?(ActionController::Base.asset_host)
7
+ # str=ActionController::Base.asset_host
8
+ # str += "/" unless str.ends_with?("/")
9
+ # public_path.gsub(/#{Regexp.escape(ActionController::Base.asset_host)}#{@controller.request.relative_url_root}\//,str)
10
+ # else
11
+ # public_path
12
+ # end
13
+ # end
14
+ #
15
+ # alias_method_chain :compute_public_path, :facebooker
16
+ # end
17
+ # end
18
+ # end
@@ -0,0 +1,112 @@
1
+ module Facebooker
2
+ module Rails
3
+ class FacebookFormBuilder < ActionView::Helpers::FormBuilder
4
+
5
+
6
+ second_param = %w(password_field file_field check_box date_select datetime_select time_select)
7
+ third_param = %w(radio_button country_select select time_zone_select)
8
+ fifth_param = %w(collection_select)
9
+
10
+ def self.create_with_offset(name,offset)
11
+ define_method name do |field,*args|
12
+ options = args[offset] || {}
13
+ build_shell(field,options) do
14
+ super
15
+ end
16
+ end
17
+ end
18
+
19
+ second_param.each do |name|
20
+ create_with_offset(name,0)
21
+ end
22
+ third_param.each do |name|
23
+ create_with_offset(name,1)
24
+ end
25
+ fifth_param.each do |name|
26
+ create_with_offset(name,3)
27
+ end
28
+
29
+ def build_shell(field,options)
30
+ @template.content_tag "fb:editor-custom", :label=>label_for(field,options) do
31
+ yield
32
+ end
33
+ end
34
+
35
+ def label_for(field,options)
36
+ options[:label] || field.to_s.humanize
37
+ end
38
+
39
+ def text(string,options={})
40
+ @template.content_tag "fb:editor-custom",string, :label=>label_for("",options)
41
+ end
42
+
43
+
44
+ def text_field(method, options = {})
45
+ options[:label] ||= label_for(method,options)
46
+ add_default_name_and_id(options,method)
47
+ options["value"] ||= value_before_type_cast(object,method)
48
+ @template.content_tag("fb:editor-text","",options)
49
+ end
50
+
51
+
52
+ def text_area(method, options = {})
53
+ options[:label] ||= label_for(method,options)
54
+ add_default_name_and_id(options,method)
55
+ @template.content_tag("fb:editor-textarea",value_before_type_cast(object,method),options)
56
+ end
57
+
58
+ #
59
+ # Build a text input area that uses typeahed
60
+ # options are like collection_select
61
+ def collection_typeahead(method,collection,value_method,text_method,options={})
62
+ build_shell(method,options) do
63
+ collection_typeahead_internal(method,collection,value_method,text_method,options)
64
+ end
65
+ end
66
+
67
+ def collection_typeahead_internal(method,collection,value_method,text_method,options={})
68
+ option_values = collection.map do |item|
69
+ value=item.send(value_method)
70
+ text=item.send(text_method)
71
+ @template.content_tag "fb:typeahead-option",text,:value=>value
72
+ end.join
73
+ add_default_name_and_id(options,method)
74
+ options["value"] ||= value_before_type_cast(object,method)
75
+ @template.content_tag("fb:typeahead-input",option_values,options)
76
+ end
77
+
78
+ def value_before_type_cast(object,method)
79
+ unless object.nil?
80
+ method_name = method.to_s
81
+ object.respond_to?(method_name + "_before_type_cast") ?
82
+ object.send(method_name + "_before_type_cast") :
83
+ object.send(method_name)
84
+ end
85
+ end
86
+
87
+ def multi_friend_input(options={})
88
+ build_shell(:friends,options) do
89
+ @template.content_tag("fb:multi-friend-input","",options)
90
+ end
91
+ end
92
+
93
+ def buttons(*names)
94
+ buttons=names.map do |name|
95
+ create_button(name)
96
+ end.join
97
+
98
+ @template.content_tag "fb:editor-buttonset",buttons
99
+ end
100
+
101
+ def create_button(name)
102
+ @template.content_tag("fb:editor-button","",:value=>name)
103
+ end
104
+
105
+ def add_default_name_and_id(options,method)
106
+ options[:name] = "#{object.class.name.downcase}[#{method}]"
107
+ options[:id] ||= "#{object.class.name.downcase}_#{method}"
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,14 @@
1
+ module ::ActionController
2
+ class AbstractRequest
3
+ def request_method_with_facebooker
4
+ if parameters[:fb_sig_request_method]=="GET" and parameters[:_method].blank?
5
+ parameters[:_method]="GET"
6
+ end
7
+ request_method_without_facebooker
8
+ end
9
+
10
+ if new.methods.include?("request_method")
11
+ alias_method_chain :request_method, :facebooker
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,58 @@
1
+ module ActionController
2
+ class CgiRequest
3
+ alias :initialize_aliased_by_facebooker :initialize
4
+
5
+ def initialize(cgi, session_options = {})
6
+ initialize_aliased_by_facebooker(cgi, session_options)
7
+ @cgi.instance_variable_set("@request_params", request_parameters.merge(query_parameters))
8
+ end
9
+
10
+ DEFAULT_SESSION_OPTIONS[:cookie_only] = false
11
+ end
12
+ end
13
+
14
+ class CGI
15
+ class Session
16
+ private
17
+ alias :initialize_aliased_by_facebooker :initialize
18
+ attr_reader :request, :initialization_options
19
+
20
+ def initialize(request, option={})
21
+ @request = request
22
+ @initialization_options = option
23
+ option['session_id'] = set_session_id
24
+ initialize_aliased_by_facebooker(request, option)
25
+ end
26
+
27
+ def set_session_id
28
+ if session_key_should_be_set_with_facebook_session_key?
29
+ request_parameters[facebook_session_key]
30
+ else
31
+ request_parameters[session_key]
32
+ end
33
+ end
34
+
35
+ def request_parameters
36
+ request.instance_variable_get("@request_params")
37
+ end
38
+
39
+ def session_key_should_be_set_with_facebook_session_key?
40
+ request_parameters[session_key].blank? && !request_parameters[facebook_session_key].blank?
41
+ end
42
+
43
+ def session_key
44
+ initialization_options['session_key'] || '_session_id'
45
+ end
46
+
47
+ def facebook_session_key
48
+ 'fb_sig_session_key'
49
+ end
50
+
51
+ alias :create_new_id_aliased_by_facebooker :create_new_id
52
+
53
+ def create_new_id
54
+ @new_session = true
55
+ @session_id || create_new_id_aliased_by_facebooker
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,31 @@
1
+ module ::ActionController
2
+ class AbstractRequest
3
+ def relative_url_root
4
+ Facebooker.path_prefix
5
+ end
6
+ end
7
+
8
+ class UrlRewriter
9
+ RESERVED_OPTIONS << :canvas
10
+
11
+ def link_to_canvas?(params, options)
12
+ option_override = options[:canvas]
13
+ return false if option_override == false # important to check for false. nil should use default behavior
14
+ option_override || @request.parameters["fb_sig_in_canvas"] == "1" || @request.parameters[:fb_sig_in_canvas] == "1"
15
+ end
16
+
17
+ def rewrite_url_with_facebooker(*args)
18
+ options = args.first.is_a?(Hash) ? args.first : args.last
19
+ is_link_to_canvas = link_to_canvas?(@request.request_parameters, options)
20
+ if is_link_to_canvas && !options.has_key?(:host)
21
+ options[:host] = "apps.facebook.com"
22
+ end
23
+ options.delete(:canvas)
24
+ Facebooker.request_for_canvas(is_link_to_canvas) do
25
+ rewrite_url_without_facebooker(*args)
26
+ end
27
+ end
28
+
29
+ alias_method_chain :rewrite_url, :facebooker
30
+ end
31
+ end
@@ -0,0 +1,535 @@
1
+ module Facebooker
2
+ module Rails
3
+
4
+ # Facebook specific helpers for creating FBML
5
+ #
6
+ # All helpers that take a user as a parameter will get the Facebook UID from the facebook_id attribute if it exists.
7
+ # It will use to_s if the facebook_id attribute is not present.
8
+ #
9
+ module Helpers
10
+
11
+ # Create an fb:request-form without a selector
12
+ #
13
+ # The block passed to this tag is used as the content of the form
14
+ #
15
+ # The message param is the name sent to content_for that specifies the body of the message
16
+ #
17
+ # For example,
18
+ #
19
+ # <% content_for("invite_message") do %>
20
+ # This gets sent in the invite. <%= fb_req_choice("with a button!",new_poke_path) %>
21
+ # <% end %>
22
+ # <% fb_request_form("Poke","invite_message",create_poke_path) do %>
23
+ # Send a poke to: <%= fb_friend_selector %> <br />
24
+ # <%= fb_request_form_submit %>
25
+ # <% end %>
26
+ def fb_request_form(type,message_param,url,&block)
27
+ content = capture(&block)
28
+ message = @template.instance_variable_get("@content_for_#{message_param}")
29
+ concat(content_tag("fb:request-form", content,
30
+ {:action=>url,:method=>"post",:invite=>true,:type=>type,:content=>message}),
31
+ block.binding)
32
+ end
33
+
34
+ # Create a submit button for an <fb:request-form>
35
+ # If the request is for an individual user you can optionally
36
+ # Provide the user and a label for the request button.
37
+ # For example
38
+ # <% content_for("invite_user") do %>
39
+ # This gets sent in the invite. <%= fb_req_choice("Come join us!",new_invite_path) %>
40
+ # <% end %>
41
+ # <% fb_request_form("Invite","invite_user",create_invite_path) do %>
42
+ # Invite <%= fb_name(@facebook_user.friends.first.id)%> to the party <br />
43
+ # <%= fb_request_form_submit(@facebook_user.friends.first.id,"Invite %n") %>
44
+ # <% end %>
45
+ # <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:request-form-submit for options
46
+ def fb_request_form_submit(options={})
47
+ tag("fb:request-form-submit",options)
48
+ end
49
+
50
+
51
+ # Create an fb:request-form with an fb_multi_friend_selector inside
52
+ #
53
+ # The content of the block are used as the message on the form,
54
+ #
55
+ # For example:
56
+ # <% fb_multi_friend_request("Poke","Choose some friends to Poke",create_poke_path) do %>
57
+ # If you select some friends, they will see this message.
58
+ # <%= fb_req_choice("They will get this button, too",new_poke_path) %>
59
+ # <% end %>
60
+ def fb_multi_friend_request(type,friend_selector_message,url,&block)
61
+ content = capture(&block)
62
+ concat(content_tag("fb:request-form",
63
+ fb_multi_friend_selector(friend_selector_message),
64
+ {:action=>url,:method=>"post",:invite=>true,:type=>type,:content=>content}
65
+ ),
66
+ block.binding)
67
+ end
68
+
69
+ # Render an <fb:friend-selector> element
70
+ # <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:friend-selector for options
71
+ #
72
+ def fb_friend_selector(options={})
73
+ tag("fb:friend-selector",options)
74
+ end
75
+
76
+ # Render an <fb:multi-friend-input> element
77
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:multi-friend-input for options
78
+ def fb_multi_friend_input(options={})
79
+ tag "fb:multi-friend-input",options
80
+ end
81
+
82
+ # Render an <fb:multi-friend-selector> with the passed in welcome message
83
+ # Full version shows all profile pics for friends.
84
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:multi-friend-selector for options
85
+ # <em> Note: </em> I don't think the block is used here.
86
+ def fb_multi_friend_selector(message,options={},&block)
87
+ tag("fb:multi-friend-selector",options.merge(:showborder=>false,:actiontext=>message,:max=>20))
88
+ end
89
+
90
+ # Render a condensed <fb:multi-friend-selector> with the passed in welcome message
91
+ # Condensed version show checkboxes for each friend.
92
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:multi-friend-selector_%28condensed%29 for options
93
+ # <em> Note: </em> I don't think the block is used here.
94
+ def fb_multi_friend_selector_condensed(options={},&block)
95
+ tag("fb:multi-friend-selector",options.merge(:condensed=>true))
96
+ end
97
+
98
+ # Render a button in a request using the <fb:req-choice> tag
99
+ # url must be an absolute url
100
+ # This should be used inside the block of an fb_multi_friend_request
101
+ def fb_req_choice(label,url)
102
+ tag "fb:req-choice",:label=>label,:url=>url
103
+ end
104
+
105
+ # Create a facebook form using <fb:editor>
106
+ #
107
+ # It yields a form builder that will convert the standard rails form helpers
108
+ # into the facebook specific version.
109
+ #
110
+ # Example:
111
+ # <% facebook_form_for(:poke,@poke,:url => create_poke_path) do |f| %>
112
+ # <%= f.text_field :message, :label=>"message" %>
113
+ # <%= f.buttons "Save Poke" %>
114
+ # <% end %>
115
+ #
116
+ # will generate
117
+ #
118
+ # <fb:editor action="/pokes/create">
119
+ # <fb:editor-text name="poke[message]" id="poke_message" value="" label="message" />
120
+ # <fb:editor-buttonset>
121
+ # <fb:editor-button label="Save Poke"
122
+ # </fb:editor-buttonset>
123
+ # </fb:editor>
124
+ def facebook_form_for( record_or_name_or_array,*args, &proc)
125
+
126
+ raise ArgumentError, "Missing block" unless block_given?
127
+ options = args.last.is_a?(Hash) ? args.pop : {}
128
+
129
+ case record_or_name_or_array
130
+ when String, Symbol
131
+ object_name = record_or_name_or_array
132
+ when Array
133
+ object = record_or_name_or_array.last
134
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
135
+ apply_form_for_options!(record_or_name_or_array, options)
136
+ args.unshift object
137
+ else
138
+ object = record_or_name_or_array
139
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
140
+ apply_form_for_options!([object], options)
141
+ args.unshift object
142
+ end
143
+ method = (options[:html]||{})[:method]
144
+ options[:builder] ||= Facebooker::Rails::FacebookFormBuilder
145
+ editor_options={}
146
+
147
+ action=options.delete(:url)
148
+ editor_options[:action]= action unless action.blank?
149
+ width=options.delete(:width)
150
+ editor_options[:width]=width unless width.blank?
151
+ width=options.delete(:labelwidth)
152
+ editor_options[:labelwidth]=width unless width.blank?
153
+
154
+ concat(tag("fb:editor",editor_options,true) , proc.binding)
155
+ concat(tag(:input,{:type=>"hidden",:name=>:_method, :value=>method},false), proc.binding) unless method.blank?
156
+ fields_for( object_name,*(args << options), &proc)
157
+ concat("</fb:editor>",proc.binding)
158
+ end
159
+
160
+ # Render an fb:name tag for the given user
161
+ # This renders the name of the user specified. You can use this tag as both subject and object of
162
+ # a sentence. <em> See </em> http://wiki.developers.facebook.com/index.php/Fb:name for full description.
163
+ # Use this tag on FBML pages instead of retrieving the user's info and rendering the name explicitly.
164
+ #
165
+ def fb_name(user, options={})
166
+ options.transform_keys!(FB_NAME_OPTION_KEYS_TO_TRANSFORM)
167
+ options.assert_valid_keys(FB_NAME_VALID_OPTION_KEYS)
168
+ options.merge!(:uid => cast_to_facebook_id(user))
169
+ tag("fb:name", options)
170
+ end
171
+
172
+ FB_NAME_OPTION_KEYS_TO_TRANSFORM = {:first_name_only => :firstnameonly,
173
+ :last_name_only => :lastnameonly,
174
+ :show_network => :shownetwork,
175
+ :use_you => :useyou,
176
+ :if_cant_see => :ifcantsee,
177
+ :subject_id => :subjectid}
178
+ FB_NAME_VALID_OPTION_KEYS = [:firstnameonly, :linked, :lastnameonly, :possessive, :reflexive,
179
+ :shownetwork, :useyou, :ifcantsee, :capitalize, :subjectid]
180
+
181
+ # Render an <fb:pronoun> tag for the specified user
182
+ # Options give flexibility for placing in any part of a sentence.
183
+ # <em> See </em> http://wiki.developers.facebook.com/index.php/Fb:pronoun for complete list of options.
184
+ #
185
+ def fb_pronoun(user, options={})
186
+ options.transform_keys!(FB_PRONOUN_OPTION_KEYS_TO_TRANSFORM)
187
+ options.assert_valid_keys(FB_PRONOUN_VALID_OPTION_KEYS)
188
+ options.merge!(:uid => cast_to_facebook_id(user))
189
+ tag("fb:pronoun", options)
190
+ end
191
+
192
+ FB_PRONOUN_OPTION_KEYS_TO_TRANSFORM = {:use_you => :useyou, :use_they => :usethey}
193
+ FB_PRONOUN_VALID_OPTION_KEYS = [:useyou, :possessive, :reflexive, :objective,
194
+ :usethey, :capitalize]
195
+
196
+ # Render an fb:ref tag.
197
+ # Options must contain either url or handle.
198
+ # * <em> url </em> The URL from which to fetch the FBML. You may need to call fbml.refreshRefUrl to refresh cache
199
+ # * <em> handle </em> The string previously set by fbml.setRefHandle that identifies the FBML
200
+ # <em> See </em> http://wiki.developers.facebook.com/index.php/Fb:ref for complete description
201
+ def fb_ref(options)
202
+ options.assert_valid_keys(FB_REF_VALID_OPTION_KEYS)
203
+ validate_fb_ref_has_either_url_or_handle(options)
204
+ validate_fb_ref_does_not_have_both_url_and_handle(options)
205
+ tag("fb:ref", options)
206
+ end
207
+
208
+ def validate_fb_ref_has_either_url_or_handle(options)
209
+ unless options.has_key?(:url) || options.has_key?(:handle)
210
+ raise ArgumentError, "fb_ref requires :url or :handle"
211
+ end
212
+ end
213
+
214
+ def validate_fb_ref_does_not_have_both_url_and_handle(options)
215
+ if options.has_key?(:url) && options.has_key?(:handle)
216
+ raise ArgumentError, "fb_ref may not have both :url and :handle"
217
+ end
218
+ end
219
+
220
+ FB_REF_VALID_OPTION_KEYS = [:url, :handle]
221
+
222
+ # Render an <fb:profile-pic> for the specified user.
223
+ #
224
+ # You can optionally specify the size using the :size=> option.
225
+ #
226
+ # Valid sizes are :thumb, :small, :normal and :square
227
+ def fb_profile_pic(user, options={})
228
+ validate_fb_profile_pic_size(options)
229
+ options.merge!(:uid => cast_to_facebook_id(user))
230
+ tag("fb:profile-pic", options)
231
+ end
232
+
233
+ # Render an fb:photo tag.
234
+ # photo is either a Facebooker::Photo or an id of a Facebook photo or an object that responds to photo_id.
235
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:photo for complete list of options.
236
+ def fb_photo(photo, options={})
237
+ options.assert_valid_keys(FB_PHOTO_VALID_OPTION_KEYS)
238
+ options.merge!(:pid => cast_to_photo_id(photo))
239
+ validate_fb_photo_size(options)
240
+ validate_fb_photo_align_value(options)
241
+ tag("fb:photo", options)
242
+ end
243
+
244
+ FB_PHOTO_VALID_OPTION_KEYS = [:uid, :size, :align]
245
+
246
+ def cast_to_photo_id(object)
247
+ object.respond_to?(:photo_id) ? object.photo_id : object
248
+ end
249
+
250
+ VALID_FB_SHARED_PHOTO_SIZES = [:thumb, :small, :normal, :square]
251
+ VALID_FB_PHOTO_SIZES = VALID_FB_SHARED_PHOTO_SIZES
252
+ VALID_FB_PROFILE_PIC_SIZES = VALID_FB_SHARED_PHOTO_SIZES
253
+
254
+ # Deprecated
255
+ #
256
+ # set ActionController::Base.asset_host and use the regular image_tag method.
257
+ def facebook_image_tag(name,options={})
258
+ tag "img",:src=>"http://#{ENV['FACEBOOKER_STATIC_HOST']}#{image_path(name)}"
259
+ end
260
+
261
+ # Render an fb:tabs tag containing some number of fb:tab_item tags.
262
+ # Example:
263
+ # <% fb_tabs do %>
264
+ # <%= fb_tab_item("Home", "home") %>
265
+ # <%= fb_tab_item("Office", "office") %>
266
+ # <% end %>
267
+ def fb_tabs(&block)
268
+ content = capture(&block)
269
+ concat(content_tag("fb:tabs", content), block.binding)
270
+ end
271
+
272
+ # Render an fb:tab_item tag.
273
+ # Use this in conjunction with fb_tabs
274
+ # Options can contains :selected => true to indicate that a tab is the current tab.
275
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:tab-item for complete list of options
276
+ def fb_tab_item(title, url, options={})
277
+ options.assert_valid_keys(FB_TAB_ITEM_VALID_OPTION_KEYS)
278
+ options.merge!(:title => title, :href => url)
279
+ validate_fb_tab_item_align_value(options)
280
+ tag("fb:tab-item", options)
281
+ end
282
+
283
+ FB_TAB_ITEM_VALID_OPTION_KEYS = [:align, :selected]
284
+
285
+ def validate_fb_tab_item_align_value(options)
286
+ if options.has_key?(:align) && !VALID_FB_TAB_ITEM_ALIGN_VALUES.include?(options[:align].to_sym)
287
+ raise(ArgumentError, "Unkown value for align: #{options[:align]}")
288
+ end
289
+ end
290
+
291
+ def validate_fb_photo_align_value(options)
292
+ if options.has_key?(:align) && !VALID_FB_PHOTO_ALIGN_VALUES.include?(options[:align].to_sym)
293
+ raise(ArgumentError, "Unkown value for align: #{options[:align]}")
294
+ end
295
+ end
296
+
297
+ VALID_FB_SHARED_ALIGN_VALUES = [:left, :right]
298
+ VALID_FB_PHOTO_ALIGN_VALUES = VALID_FB_SHARED_ALIGN_VALUES
299
+ VALID_FB_TAB_ITEM_ALIGN_VALUES = VALID_FB_SHARED_ALIGN_VALUES
300
+
301
+
302
+ # Create a Facebook wall. It can contain fb_wall_posts
303
+ #
304
+ # For Example:
305
+ # <% fb_wall do %>
306
+ # <%= fb_wall_post(@user,"This is my message") %>
307
+ # <%= fb_wall_post(@otheruser,"This is another message") %>
308
+ # <% end %>
309
+ def fb_wall(&proc)
310
+ content = capture(&proc)
311
+ concat(content_tag("fb:wall",content,{}),proc.binding)
312
+ end
313
+
314
+ # Render an <fb:wallpost> tag
315
+ # TODO: Optionally takes a time parameter t = int The current time, which is displayed in epoch seconds.
316
+ def fb_wallpost(user,message)
317
+ content_tag("fb:wallpost",message,:uid=>cast_to_facebook_id(user))
318
+ end
319
+ alias_method :fb_wall_post, :fb_wallpost
320
+
321
+ # Render an <fb:error> tag
322
+ # If message and text are present then this will render fb:error and fb:message tag
323
+ # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten'
324
+ def fb_error(message, text=nil)
325
+ fb_status_msg("error", message, text)
326
+ end
327
+
328
+ # Render an <fb:explanation> tag
329
+ # If message and text are present then this will render fb:error and fb:message tag
330
+ # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten'
331
+ def fb_explanation(message, text=nil)
332
+ fb_status_msg("explanation", message, text)
333
+ end
334
+
335
+ # Render an <fb:success> tag
336
+ # If message and text are present then this will render fb:error and fb:message tag
337
+ # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten'
338
+ def fb_success(message, text=nil)
339
+ fb_status_msg("success", message, text)
340
+ end
341
+
342
+ # Render flash values as <fb:message> and <fb:error> tags
343
+ #
344
+ # values in flash[:notice] will be rendered as an <fb:message>
345
+ #
346
+ # values in flash[:error] will be rendered as an <fb:error>
347
+ # TODO: Allow flash[:info] to render fb_explanation
348
+ def facebook_messages
349
+ message=""
350
+ unless flash[:notice].blank?
351
+ message += fb_success(flash[:notice])
352
+ end
353
+ unless flash[:error].blank?
354
+ message += fb_error(flash[:error])
355
+ end
356
+ message
357
+ end
358
+
359
+ # Create a dashboard. It can contain fb_action, fb_help, and fb_create_button
360
+ #
361
+ # For Example:
362
+ # <% fb_dashboard do %>
363
+ # <%= APP_NAME %>
364
+ # <%= fb_action 'My Matches', search_path %>
365
+ # <%= fb_help 'Feedback', "http://www.facebook.com/apps/application.php?id=6236036681" %>
366
+ # <%= fb_create_button 'Invite Friends', main_path %>
367
+ # <% end %>
368
+ def fb_dashboard(&proc)
369
+ if block_given?
370
+ content = capture(&proc)
371
+ concat(content_tag("fb:dashboard",content,{}),proc.binding)
372
+ else
373
+ content_tag("fb:dashboard",content,{})
374
+ end
375
+ end
376
+
377
+ # Content for the wide profile box goes in this tag
378
+ def fb_wide(&proc)
379
+ content = capture(&proc)
380
+ concat(content_tag("fb:wide", content, {}), proc.binding)
381
+ end
382
+
383
+ # Content for the narrow profile box goes in this tag
384
+ def fb_narrow(&proc)
385
+ content = capture(&proc)
386
+ concat(content_tag("fb:narrow", content, {}), proc.binding)
387
+ end
388
+
389
+ # Renders an action using the <fb:action> tag
390
+ def fb_action(name, url)
391
+ "<fb:action href=\"#{url_for(url)}\">#{name}</fb:action>"
392
+ end
393
+
394
+ # Render a <fb:help> tag
395
+ # For use inside <fb:dashboard>
396
+ def fb_help(name, url)
397
+ "<fb:help href=\"#{url_for(url)}\">#{name}</fb:help>"
398
+ end
399
+
400
+ # Render a <fb:create-button> tag
401
+ # For use inside <fb:dashboard>
402
+ def fb_create_button(name, url)
403
+ "<fb:create-button href=\"#{url_for(url)}\">#{name}</fb:create-button>"
404
+ end
405
+
406
+ # Create a comment area
407
+ # All the data for this content area is stored on the facebook servers.
408
+ # <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:comments for full details
409
+ # TODO: Comments can optionally take an fb:title tag.
410
+ def fb_comments(xid,canpost=true,candelete=false,numposts=5,options={})
411
+ tag "fb:comments",options.merge(:xid=>xid,:canpost=>canpost.to_s,:candelete=>candelete.to_s,:numposts=>numposts)
412
+ end
413
+
414
+ # Adds a title to the title bar
415
+ #
416
+ # Facebook | App Name | This is the canvas page window title
417
+ #
418
+ # +title+: This is the canvas page window
419
+ def fb_title(title)
420
+ "<fb:title>#{title}</fb:title>"
421
+ end
422
+
423
+ # Create a Google Analytics tag
424
+ #
425
+ # +uacct+: Your Urchin/Google Analytics account ID.
426
+ def fb_google_analytics(uacct, options={})
427
+ tag "fb:google-analytics", options.merge(:uacct => uacct)
428
+ end
429
+
430
+ # Render if-is-app-user tag
431
+ # This tag renders the enclosing content only if the user specified has accepted the terms of service for the application.
432
+ # Use fb_if_user_has_added_app to determine wether the user has added the app.
433
+ # Example:
434
+ # <% fb_if_is_app_user(@facebook_user) do %>
435
+ # Thanks for accepting our terms of service!
436
+ # <% fb_else do %>
437
+ # Hey you haven't agreed to our terms. <%= link_to("Please accept our terms of service.", :action => "terms_of_service") %>
438
+ # <% end %>
439
+ #<% end %>
440
+ def fb_if_is_app_user(user,options={},&proc)
441
+ content = capture(&proc)
442
+ concat(content_tag("fb:if-is-app-user",content,options.merge(:uid=>cast_to_facebook_id(user))),proc.binding)
443
+ end
444
+
445
+ # Render if-user-has-added-app tag
446
+ # This tag renders the enclosing content only if the user specified has installed the application
447
+ #
448
+ # Example:
449
+ # <% fb_if_user_has_added_app(@facebook_user) do %>
450
+ # Hey you are an app user!
451
+ # <% fb_else do %>
452
+ # Hey you aren't an app user. <%= link_to("Add App and see the other side.", :action => "added_app") %>
453
+ # <% end %>
454
+ #<% end %>
455
+ def fb_if_user_has_added_app(user,options={},&proc)
456
+ content = capture(&proc)
457
+ concat(content_tag("fb:if-user-has-added-app",content,options.merge(:uid=>cast_to_facebook_id(user))),proc.binding)
458
+ end
459
+
460
+ # Render fb:if-is-user tag
461
+ # This tag only renders enclosing content if the user is one of those specified
462
+ # user can be a single user or an Array of users
463
+ # Example:
464
+ # <% fb_if_is_user(@check_user) do %>
465
+ # <%= fb_name(@facebook_user) %> are one of the users. <%= link_to("Check the other side", :action => "friend") %>
466
+ # <% fb_else do %>
467
+ # <%= fb_name(@facebook_user) %> are not one of the users <%= fb_name(@check_user) %>
468
+ # <%= link_to("Check the other side", :action => "you") %>
469
+ # <% end %>
470
+ # <% end %>
471
+ def fb_if_is_user(user,&proc)
472
+ content = capture(&proc)
473
+ user = [user] unless user.is_a? Array
474
+ user_list=user.map{|u| cast_to_facebook_id(u)}.join(",")
475
+ concat(content_tag("fb:if-is-user",content,{:uid=>user_list}),proc.binding)
476
+ end
477
+
478
+ # Render fb:else tag
479
+ # 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
480
+ def fb_else
481
+ content = capture(&proc)
482
+ concat(content_tag("fb:else",content),proc.binding)
483
+ end
484
+
485
+ def fb_about_url
486
+ "http://www.facebook.com/apps/application.php?api_key=#{ENV["FACEBOOK_API_KEY"]}"
487
+ end
488
+
489
+
490
+ protected
491
+
492
+ def cast_to_facebook_id(object)
493
+ Facebooker::User.cast_to_facebook_id(object)
494
+ end
495
+
496
+ def validate_fb_profile_pic_size(options)
497
+ if options.has_key?(:size) && !VALID_FB_PROFILE_PIC_SIZES.include?(options[:size].to_sym)
498
+ raise(ArgumentError, "Unkown value for size: #{options[:size]}")
499
+ end
500
+ end
501
+
502
+ def validate_fb_photo_size(options)
503
+ if options.has_key?(:size) && !VALID_FB_PHOTO_SIZES.include?(options[:size].to_sym)
504
+ raise(ArgumentError, "Unkown value for size: #{options[:size]}")
505
+ end
506
+ end
507
+
508
+ private
509
+
510
+ def fb_status_msg(type, message, text)
511
+ if text.blank?
512
+ tag("fb:#{type}", :message => message)
513
+ else
514
+ content_tag("fb:#{type}", content_tag("fb:message", message) + text)
515
+ end
516
+ end
517
+ end
518
+ end
519
+ end
520
+
521
+ class Hash
522
+ def transform_keys!(transformation_hash)
523
+ transformation_hash.each_pair{|key, value| transform_key!(key, value)}
524
+ end
525
+
526
+ def transform_key!(old_key, new_key)
527
+ swapkey!(new_key, old_key)
528
+ end
529
+
530
+ ### This method is lifted from Ruby Facets core
531
+ def swapkey!( newkey, oldkey )
532
+ self[newkey] = self.delete(oldkey) if self.has_key?(oldkey)
533
+ self
534
+ end
535
+ end