fbooker 1.0.53

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 (142) hide show
  1. data/.autotest +15 -0
  2. data/CHANGELOG.rdoc +24 -0
  3. data/COPYING.rdoc +19 -0
  4. data/Manifest.txt +144 -0
  5. data/README.rdoc +113 -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 +252 -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 +13 -0
  52. data/lib/facebooker/batch_request.rb +45 -0
  53. data/lib/facebooker/data.rb +57 -0
  54. data/lib/facebooker/feed.rb +78 -0
  55. data/lib/facebooker/logging.rb +44 -0
  56. data/lib/facebooker/mobile.rb +20 -0
  57. data/lib/facebooker/mock/service.rb +50 -0
  58. data/lib/facebooker/mock/session.rb +18 -0
  59. data/lib/facebooker/model.rb +139 -0
  60. data/lib/facebooker/models/affiliation.rb +10 -0
  61. data/lib/facebooker/models/album.rb +11 -0
  62. data/lib/facebooker/models/applicationproperties.rb +39 -0
  63. data/lib/facebooker/models/applicationrestrictions.rb +10 -0
  64. data/lib/facebooker/models/cookie.rb +10 -0
  65. data/lib/facebooker/models/education_info.rb +11 -0
  66. data/lib/facebooker/models/event.rb +28 -0
  67. data/lib/facebooker/models/family_relative_info.rb +7 -0
  68. data/lib/facebooker/models/friend_list.rb +16 -0
  69. data/lib/facebooker/models/group.rb +36 -0
  70. data/lib/facebooker/models/info_item.rb +10 -0
  71. data/lib/facebooker/models/info_section.rb +10 -0
  72. data/lib/facebooker/models/location.rb +8 -0
  73. data/lib/facebooker/models/notifications.rb +17 -0
  74. data/lib/facebooker/models/page.rb +44 -0
  75. data/lib/facebooker/models/photo.rb +19 -0
  76. data/lib/facebooker/models/tag.rb +12 -0
  77. data/lib/facebooker/models/user.rb +552 -0
  78. data/lib/facebooker/models/video.rb +9 -0
  79. data/lib/facebooker/models/work_info.rb +10 -0
  80. data/lib/facebooker/parser.rb +717 -0
  81. data/lib/facebooker/rails/backwards_compatible_param_checks.rb +31 -0
  82. data/lib/facebooker/rails/controller.rb +353 -0
  83. data/lib/facebooker/rails/cucumber.rb +28 -0
  84. data/lib/facebooker/rails/cucumber/world.rb +40 -0
  85. data/lib/facebooker/rails/extensions/action_controller.rb +48 -0
  86. data/lib/facebooker/rails/extensions/rack_setup.rb +6 -0
  87. data/lib/facebooker/rails/extensions/routing.rb +15 -0
  88. data/lib/facebooker/rails/facebook_form_builder.rb +112 -0
  89. data/lib/facebooker/rails/facebook_pretty_errors.rb +22 -0
  90. data/lib/facebooker/rails/facebook_request_fix.rb +28 -0
  91. data/lib/facebooker/rails/facebook_request_fix_2-3.rb +31 -0
  92. data/lib/facebooker/rails/facebook_session_handling.rb +68 -0
  93. data/lib/facebooker/rails/facebook_url_helper.rb +192 -0
  94. data/lib/facebooker/rails/facebook_url_rewriting.rb +60 -0
  95. data/lib/facebooker/rails/helpers.rb +870 -0
  96. data/lib/facebooker/rails/helpers/fb_connect.rb +122 -0
  97. data/lib/facebooker/rails/integration_session.rb +38 -0
  98. data/lib/facebooker/rails/profile_publisher_extensions.rb +42 -0
  99. data/lib/facebooker/rails/publisher.rb +595 -0
  100. data/lib/facebooker/rails/routing.rb +49 -0
  101. data/lib/facebooker/rails/test_helpers.rb +68 -0
  102. data/lib/facebooker/rails/utilities.rb +22 -0
  103. data/lib/facebooker/server_cache.rb +24 -0
  104. data/lib/facebooker/service.rb +102 -0
  105. data/lib/facebooker/service/base_service.rb +19 -0
  106. data/lib/facebooker/service/curl_service.rb +44 -0
  107. data/lib/facebooker/service/net_http_service.rb +12 -0
  108. data/lib/facebooker/service/typhoeus_multi_service.rb +27 -0
  109. data/lib/facebooker/service/typhoeus_service.rb +17 -0
  110. data/lib/facebooker/session.rb +726 -0
  111. data/lib/facebooker/version.rb +9 -0
  112. data/lib/net/http_multipart_post.rb +123 -0
  113. data/lib/rack/facebook.rb +89 -0
  114. data/lib/tasks/facebooker.rake +18 -0
  115. data/lib/tasks/tunnel.rake +46 -0
  116. data/rails/init.rb +1 -0
  117. data/setup.rb +1585 -0
  118. data/templates/layout.erb +24 -0
  119. data/test/facebooker/adapters_test.rb +191 -0
  120. data/test/facebooker/admin_test.rb +102 -0
  121. data/test/facebooker/application_test.rb +41 -0
  122. data/test/facebooker/batch_request_test.rb +83 -0
  123. data/test/facebooker/data_test.rb +86 -0
  124. data/test/facebooker/logging_test.rb +43 -0
  125. data/test/facebooker/mobile_test.rb +45 -0
  126. data/test/facebooker/model_test.rb +133 -0
  127. data/test/facebooker/models/event_test.rb +15 -0
  128. data/test/facebooker/models/page_test.rb +46 -0
  129. data/test/facebooker/models/photo_test.rb +16 -0
  130. data/test/facebooker/models/user_test.rb +397 -0
  131. data/test/facebooker/rails/facebook_request_fix_2-3_test.rb +25 -0
  132. data/test/facebooker/rails/facebook_url_rewriting_test.rb +76 -0
  133. data/test/facebooker/rails/integration_session_test.rb +13 -0
  134. data/test/facebooker/rails/publisher_test.rb +519 -0
  135. data/test/facebooker/rails_integration_test.rb +1511 -0
  136. data/test/facebooker/server_cache_test.rb +44 -0
  137. data/test/facebooker/service_test.rb +58 -0
  138. data/test/facebooker/session_test.rb +883 -0
  139. data/test/facebooker_test.rb +951 -0
  140. data/test/net/http_multipart_post_test.rb +52 -0
  141. data/test/rack/facebook_test.rb +73 -0
  142. metadata +235 -0
@@ -0,0 +1,122 @@
1
+ module Facebooker
2
+ module Rails
3
+ module Helpers
4
+ module FbConnect
5
+
6
+ def fb_connect_javascript_tag(options = {})
7
+ # accept both Rails and Facebook locale formatting, i.e. "en-US" and "en_US".
8
+ lang = "/#{options[:lang].to_s.gsub('-', '_')}" if options[:lang]
9
+ # dont use the javascript_include_tag helper since it adds a .js at the end
10
+ if request.ssl?
11
+ "<script src=\"https://www.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php#{lang}\" type=\"text/javascript\"></script>"
12
+ else
13
+ "<script src=\"http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php#{lang}\" type=\"text/javascript\"></script>"
14
+ end
15
+ end
16
+
17
+ #
18
+ # For information on the :app_settings argument see http://wiki.developers.facebook.com/index.php/JS_API_M_FB.Facebook.Init_2
19
+ # While it would be nice to treat :app_settings as a hash, some of the arguments do different things if they are a string vs a javascript function
20
+ # and Rails' Hash#to_json always quotes strings so there is no way to indicate when the value should be a javascript function.
21
+ # For this reason :app_settings needs to be a string that is valid JSON (including the {}'s).
22
+ #
23
+ def init_fb_connect(*required_features,&proc)
24
+ additions = ""
25
+ if block_given?
26
+ additions = capture(&proc)
27
+ end
28
+
29
+ # Yes, app_settings is set to a string of an empty JSON element. That's intentional.
30
+ options = {:js => :prototype, :app_settings => '{}'}
31
+
32
+ if required_features.last.is_a?(Hash)
33
+ options.merge!(required_features.pop.symbolize_keys)
34
+ end
35
+
36
+ if request.ssl?
37
+ init_string = "FB.init('#{Facebooker.api_key}','/xd_receiver_ssl.html', #{options[:app_settings]});"
38
+ else
39
+ init_string = "FB.init('#{Facebooker.api_key}','/xd_receiver.html', #{options[:app_settings]});"
40
+ end
41
+ unless required_features.blank?
42
+ init_string = <<-FBML
43
+ #{case options[:js]
44
+ when :jquery then "$(document).ready("
45
+ when :dojo then "dojo.addOnLoad("
46
+ else "Element.observe(window,'load',"
47
+ end} function() {
48
+ FB_RequireFeatures(#{required_features.to_json}, function() {
49
+ #{init_string}
50
+ #{additions}
51
+ });
52
+ });
53
+ FBML
54
+ end
55
+
56
+ # block_is_within_action_view? is rails 2.1.x and has been
57
+ # deprecated. rails >= 2.2.x uses block_called_from_erb?
58
+ block_tester = respond_to?(:block_is_within_action_view?) ?
59
+ :block_is_within_action_view? : :block_called_from_erb?
60
+
61
+ if block_given? && send(block_tester, proc)
62
+ concat(javascript_tag(init_string))
63
+ else
64
+ javascript_tag init_string
65
+ end
66
+ end
67
+
68
+ # Render an <fb:login-button> element
69
+ #
70
+ # ==== Examples
71
+ #
72
+ # <%= fb_login_button%>
73
+ # => <fb:login-button></fb:login-button>
74
+ #
75
+ # Specifying a javascript callback
76
+ #
77
+ # <%= fb_login_button 'update_something();'%>
78
+ # => <fb:login-button onlogin='update_something();'></fb:login-button>
79
+ #
80
+ # Adding options <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:login-button
81
+ #
82
+ # <%= fb_login_button 'update_something();', :size => :small, :background => :dark%>
83
+ # => <fb:login-button background='dark' onlogin='update_something();' size='small'></fb:login-button>
84
+ #
85
+ def fb_login_button(*args)
86
+
87
+ callback = args.first
88
+ options = args[1] || {}
89
+ options.merge!(:onlogin=>callback)if callback
90
+
91
+ content_tag("fb:login-button",nil, options)
92
+ end
93
+
94
+ def fb_login_and_redirect(url, options = {})
95
+ js = update_page do |page|
96
+ page.redirect_to url
97
+ end
98
+ content_tag("fb:login-button",nil,options.merge(:onlogin=>js))
99
+ end
100
+
101
+ def fb_unconnected_friends_count
102
+ content_tag "fb:unconnected-friends-count",nil
103
+ end
104
+
105
+ def fb_logout_link(text,url,*args)
106
+ js = update_page do |page|
107
+ page.call "FB.Connect.logoutAndRedirect",url
108
+ end
109
+ link_to_function text, js, *args
110
+ end
111
+
112
+ def fb_user_action(action, user_message = nil, prompt = "", callback = nil)
113
+ defaulted_callback = callback || "null"
114
+ update_page do |page|
115
+ page.call("FB.Connect.showFeedDialog",action.template_id,action.data,action.target_ids,action.body_general,nil,page.literal("FB.RequireConnect.promptConnect"),page.literal(defaulted_callback),prompt,user_message.nil? ? nil : {:value=>user_message})
116
+ end
117
+ end
118
+
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,38 @@
1
+ require 'action_controller/integration'
2
+
3
+ class Facebooker::Rails::IntegrationSession < ActionController::Integration::Session
4
+ include Facebooker::Rails::TestHelpers
5
+ attr_accessor :default_request_params, :canvas
6
+
7
+ def process(method, path, parameters = nil, headers = nil)
8
+ if canvas
9
+ parameters = facebook_params(@default_request_params.merge(parameters || {}))
10
+ end
11
+ super method, path, parameters, headers
12
+ end
13
+
14
+ def reset!
15
+ self.default_request_params = {:fb_sig_in_canvas => '1', :fb_sig_api_key => Facebooker::Session.api_key}.with_indifferent_access
16
+ self.canvas = true
17
+ super
18
+ end
19
+
20
+ def get(path, parameters = nil, headers = nil)
21
+ if canvas
22
+ post path, (parameters || {}).merge('fb_sig_request_method' => 'GET'), headers
23
+ else
24
+ super path, parameters, headers
25
+ end
26
+ end
27
+
28
+ %w(put delete).each do |method|
29
+ define_method method do |*args|
30
+ if canvas
31
+ path, parameters, headers = *args
32
+ post path, (parameters || {}).merge('_method' => method.upcase), headers
33
+ else
34
+ super *args
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ module Facebooker
2
+ module Rails
3
+ module ProfilePublisherExtensions
4
+
5
+ ##
6
+ # returns true if Facebook is requesting the interface for a profile publisher
7
+ def wants_interface?
8
+ params[:method] == "publisher_getInterface"
9
+ end
10
+
11
+ ##
12
+ # render the interface for a publisher.
13
+ # fbml is the content in string form. Use render_to_string to get the content from a template
14
+ # publish_enabled controlls whether the post form is active by default. If it isn't, you'll need to use fbjs to activate it
15
+ # comment_enabled controls whether to include a comment box
16
+ def render_publisher_interface(fbml,publish_enabled=true,comment_enabled=false)
17
+ render :json=>{:content=>{:fbml=>fbml,:publishEnabled=>publish_enabled,:commentEnabled=>comment_enabled},
18
+ :method=>"publisher_getInterface"}
19
+ end
20
+
21
+ # render an error while publishing the template
22
+ # This can be used for validation errors
23
+ def render_publisher_error(title,body)
24
+ render :json=>{:errorCode=>1,:errorTitle=>title,:errorMessage=>body}.to_json
25
+ end
26
+
27
+ # render the response for a feed. This takes a user_action object like those returned from the Rails Publisher
28
+ # For instance, AttackPublisher.create_attack(@attack)
29
+ # The template must have been registered previously
30
+ def render_publisher_response(user_action)
31
+ render :json=>{:content=> {
32
+ :feed=>{
33
+ :template_id=>user_action.template_id,
34
+ :template_data=>user_action.data
35
+ }
36
+ },
37
+ :method=>"publisher_getFeedStory"
38
+ }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,595 @@
1
+ module Facebooker
2
+ module Rails
3
+ # ActionMailer like module for publishing Facbook messages
4
+ #
5
+ # To use, create a subclass and define methods
6
+ # Each method should start by calling send_as to specify the type of message
7
+ # Valid options are :email and :notification, :user_action, :profile, :ref, :publish_stream
8
+ #
9
+ #
10
+ # Below is an example of each type
11
+ #
12
+ # class TestPublisher < Facebooker::Rails::Publisher
13
+ # # The new message templates are supported as well
14
+ # # First, create a method that contains your templates:
15
+ # # You may include multiple one line story templates and short story templates
16
+ # # but only one full story template
17
+ # # Your most specific template should be first
18
+ # #
19
+ # # Before using, you must register your template by calling register. For this example
20
+ # # You would call TestPublisher.register_publish_action
21
+ # # Registering the template will store the template id returned from Facebook in the
22
+ # # facebook_templates table that is created when you create your first publisher
23
+ # def publish_action_template
24
+ # one_line_story_template "{*actor*} did stuff with {*friend*}"
25
+ # one_line_story_template "{*actor*} did stuff"
26
+ # short_story_template "{*actor*} has a title {*friend*}", render(:partial=>"short_body")
27
+ # short_story_template "{*actor*} has a title", render(:partial=>"short_body")
28
+ # full_story_template "{*actor*} has a title {*friend*}", render(:partial=>"full_body")
29
+ # action_links action_link("My text {*template_var*}","{*link_url*}")
30
+ # end
31
+ #
32
+ # # To send a registered template, you need to create a method to set the data
33
+ # # The publisher will look up the template id from the facebook_templates table
34
+ # def publish_action(f)
35
+ # send_as :user_action
36
+ # from f
37
+ # story_size SHORT # or ONE_LINE or FULL
38
+ # data :friend=>"Mike"
39
+ # end
40
+ #
41
+ #
42
+ # # Provide a from user to send a general notification
43
+ # # if from is nil, this will send an announcement
44
+ # def notification(to,f)
45
+ # send_as :notification
46
+ # recipients to
47
+ # from f
48
+ # fbml "Not"
49
+ # end
50
+ #
51
+ # def email(to,f)
52
+ # send_as :email
53
+ # recipients to
54
+ # from f
55
+ # title "Email"
56
+ # fbml 'text'
57
+ # text fbml
58
+ # end
59
+ # # This will render the profile in /users/profile.fbml.erb
60
+ # # it will set @user to user_to_update in the template
61
+ # # The mobile profile will be rendered from the app/views/test_publisher/_mobile.erb
62
+ # # template
63
+ # def profile_update(user_to_update,user_with_session_to_use)
64
+ # send_as :profile
65
+ # from user_with_session_to_use
66
+ # recipients user_to_update
67
+ # profile render(:file=>"users/profile.fbml.erb",:assigns=>{:user=>user_to_update})
68
+ # profile_action "A string"
69
+ # mobile_profile render(:partial=>"mobile",:assigns=>{:user=>user_to_update})
70
+ # end
71
+ #
72
+ # # Update the given handle ref with the content from a
73
+ # # template
74
+ # def ref_update(user)
75
+ # send_as :ref
76
+ # from user
77
+ # fbml render(:file=>"users/profile",:assigns=>{:user=>user_to_update})
78
+ # handle "a_ref_handle"
79
+ # end
80
+ #
81
+ # # Publish a post into the stream on the user's Wall and News Feed.
82
+ # def publish_stream(user_with_session_to_use, user_to_update, params)
83
+ # send_as :publish_stream
84
+ # from user_with_session_to_use
85
+ # target user_to_update
86
+ # attachment params[:attachment]
87
+ # message params[:message]
88
+ # action_links params[:action_links]
89
+ # end
90
+ #
91
+ #
92
+ # To send a message, use ActionMailer like semantics
93
+ # TestPublisher.deliver_action(@user)
94
+ #
95
+ # For testing, you may want to create an instance of the underlying message without sending it
96
+ # TestPublisher.create_action(@user)
97
+ # will create and return an instance of Facebooker::Feeds::Action
98
+ #
99
+ # Publisher makes many helpers available, including the linking and asset helpers
100
+ class Publisher
101
+
102
+ #story sizes from the Facebooker API
103
+ ONE_LINE=1
104
+ SHORT=2
105
+ FULL=4
106
+
107
+ def initialize
108
+ @from = nil
109
+ @full_story_template = nil
110
+ @recipients = nil
111
+ @controller = PublisherController.new(self)
112
+ end
113
+
114
+ def self.default_url_options
115
+ {:host => Facebooker.canvas_server_base + Facebooker.facebook_path_prefix}
116
+ end
117
+
118
+ def default_url_options
119
+ self.class.default_url_options
120
+ end
121
+
122
+ # use facebook options everywhere
123
+ def request_comes_from_facebook?
124
+ true
125
+ end
126
+
127
+ class FacebookTemplate < ::ActiveRecord::Base
128
+ cattr_accessor :template_cache
129
+ self.template_cache = {}
130
+
131
+ def self.inspect(*args)
132
+ "FacebookTemplate"
133
+ end
134
+
135
+ def template_changed?(hash)
136
+ if respond_to?(:content_hash)
137
+ content_hash != hash
138
+ else
139
+ false
140
+ end
141
+ end
142
+
143
+ def deactivate
144
+ Facebooker::Session.create.deactivate_template_bundle_by_id(self.bundle_id)
145
+ return true
146
+ rescue Facebooker::Session::TemplateBundleInvalid => e
147
+ return false
148
+ end
149
+
150
+
151
+
152
+ class << self
153
+
154
+ def register(klass,method)
155
+ publisher = setup_publisher(klass,method)
156
+ template_id = Facebooker::Session.create.register_template_bundle(publisher.one_line_story_templates,publisher.short_story_templates,publisher.full_story_template,publisher.action_links)
157
+ template = find_or_initialize_by_template_name(template_name(klass,method))
158
+ template.deactivate if template.bundle_id # deactivate old templates to avoid exceeding templates/app limit
159
+ template.bundle_id = template_id
160
+ template.content_hash = hashed_content(klass,method) if template.respond_to?(:content_hash)
161
+ template.save!
162
+ cache(klass,method,template)
163
+ template
164
+ end
165
+
166
+ def for_class_and_method(klass,method)
167
+ find_cached(klass,method)
168
+ end
169
+ def bundle_id_for_class_and_method(klass,method)
170
+ for_class_and_method(klass,method).bundle_id
171
+ end
172
+
173
+ def cache(klass,method,template)
174
+ template_cache[template_name(klass,method)] = template
175
+ end
176
+
177
+ def clear_cache!
178
+ self.template_cache = {}
179
+ end
180
+
181
+ def find_cached(klass,method)
182
+ template_cache[template_name(klass,method)] || find_in_db(klass,method)
183
+ end
184
+
185
+ def find_in_db(klass,method)
186
+ template = find_by_template_name(template_name(klass,method))
187
+
188
+ if template.nil? || template.template_changed?(hashed_content(klass, method))
189
+ template = register(klass,method)
190
+ end
191
+ template
192
+ end
193
+
194
+ def setup_publisher(klass,method)
195
+ publisher = klass.new
196
+ publisher.send method + '_template'
197
+ publisher
198
+ end
199
+
200
+ def hashed_content(klass, method)
201
+ publisher = setup_publisher(klass,method)
202
+ # sort the Hash elements (in the short_story and full_story) before generating MD5
203
+ Digest::MD5.hexdigest [publisher.one_line_story_templates,
204
+ (publisher.short_story_templates and publisher.short_story_templates.collect{|ss| ss.to_a.sort_by{|e| e[0].to_s}}),
205
+ (publisher.full_story_template and publisher.full_story_template.to_a.sort_by{|e| e[0].to_s})
206
+ ].to_json
207
+ end
208
+
209
+ def template_name(klass,method)
210
+ "#{Facebooker.api_key}: #{klass.name}::#{method}"
211
+ end
212
+ end
213
+ end
214
+
215
+ class_inheritable_accessor :master_helper_module
216
+ attr_accessor :one_line_story_templates, :short_story_templates
217
+ attr_writer :action_links
218
+
219
+ cattr_accessor :skip_registry
220
+ self.skip_registry = false
221
+
222
+
223
+ class InvalidSender < StandardError; end
224
+ class UnknownBodyType < StandardError; end
225
+ class UnspecifiedBodyType < StandardError; end
226
+ class Email
227
+ attr_accessor :title
228
+ attr_accessor :text
229
+ attr_accessor :fbml
230
+ end
231
+
232
+ class Notification
233
+ attr_accessor :fbml
234
+ end
235
+
236
+ class Profile
237
+ attr_accessor :profile
238
+ attr_accessor :profile_action
239
+ attr_accessor :mobile_profile
240
+ attr_accessor :profile_main
241
+ end
242
+ class Ref
243
+ attr_accessor :handle
244
+ attr_accessor :fbml
245
+ end
246
+ class UserAction
247
+ attr_accessor :data
248
+ attr_reader :target_ids
249
+ attr_accessor :body_general
250
+ attr_accessor :template_id
251
+ attr_accessor :template_name
252
+ attr_accessor :story_size
253
+
254
+ def target_ids=(val)
255
+ @target_ids = val.is_a?(Array) ? val.join(",") : val
256
+ end
257
+
258
+ def data_hash
259
+ data||{}
260
+ end
261
+ end
262
+
263
+ class PublishStream
264
+ attr_accessor :target
265
+ attr_accessor :attachment
266
+ attr_accessor :action_links
267
+ attr_accessor :message
268
+ end
269
+
270
+ cattr_accessor :ignore_errors
271
+ attr_accessor :_body
272
+
273
+ def recipients(*args)
274
+ if args.size==0
275
+ @recipients
276
+ else
277
+ @recipients=args.first
278
+ end
279
+ end
280
+
281
+ def from(*args)
282
+ if args.size==0
283
+ @from
284
+ else
285
+ @from=args.first
286
+ end
287
+ end
288
+
289
+
290
+ def send_as(option)
291
+ self._body=case option
292
+ when :action
293
+ Facebooker::Feed::Action.new
294
+ when :story
295
+ Facebooker::Feed::Story.new
296
+ when :templatized_action
297
+ Facebooker::Feed::TemplatizedAction.new
298
+ when :notification
299
+ Notification.new
300
+ when :email
301
+ Email.new
302
+ when :profile
303
+ Profile.new
304
+ when :ref
305
+ Ref.new
306
+ when :user_action
307
+ UserAction.new
308
+ when :publish_stream
309
+ PublishStream.new
310
+ else
311
+ raise UnknownBodyType.new("Unknown type to publish")
312
+ end
313
+ end
314
+
315
+ def full_story_template(title=nil,body=nil,params={})
316
+ if title.nil?
317
+ @full_story_template
318
+ else
319
+ @full_story_template=params.merge(:template_title=>title, :template_body=>body)
320
+ end
321
+ end
322
+
323
+ def one_line_story_template(str)
324
+ @one_line_story_templates ||= []
325
+ @one_line_story_templates << str
326
+ end
327
+
328
+ def short_story_template(title,body,params={})
329
+ @short_story_templates ||= []
330
+ @short_story_templates << params.merge(:template_title=>title, :template_body=>body)
331
+ end
332
+
333
+ def action_links(*links)
334
+ if links.blank?
335
+ @action_links
336
+ else
337
+ @action_links = links
338
+ end
339
+ end
340
+
341
+ def method_missing(name,*args)
342
+ if args.size==1 and self._body.respond_to?("#{name}=")
343
+ self._body.send("#{name}=",*args)
344
+ elsif self._body.respond_to?(name)
345
+ self._body.send(name,*args)
346
+ else
347
+ super
348
+ end
349
+ end
350
+
351
+ # work around the fact that facebook cares about the order of the keys in the hash
352
+ class ImageHolder
353
+ attr_accessor :src,:href
354
+ def initialize(src,href)
355
+ self.src=src
356
+ self.href=href
357
+ end
358
+
359
+ def ==(other)
360
+ self.src == other.src && self.href == other.href
361
+ end
362
+
363
+ def to_json(*args)
364
+ "{\"src\":#{src.to_json}, \"href\":#{href.to_json}}"
365
+ end
366
+ end
367
+
368
+ def image(src,target)
369
+ ImageHolder.new(image_path(src),target.respond_to?(:to_str) ? target : url_for(target))
370
+ end
371
+
372
+ def action_link(text,target)
373
+ {:text=>text, :href=>target}
374
+ end
375
+
376
+ def requires_from_user?(from,body)
377
+ ! (announcement_notification?(from,body) or ref_update?(body) or profile_update?(body))
378
+ end
379
+
380
+ def profile_update?(body)
381
+ body.is_a?(Profile)
382
+ end
383
+
384
+ def ref_update?(body)
385
+ body.is_a?(Ref)
386
+ end
387
+
388
+ def announcement_notification?(from,body)
389
+ from.nil? and body.is_a?(Notification)
390
+ end
391
+
392
+ def send_message(method)
393
+ @recipients = @recipients.is_a?(Array) ? @recipients : [@recipients]
394
+ if from.nil? and @recipients.size==1 and requires_from_user?(from,_body)
395
+ @from = @recipients.first
396
+ end
397
+ # notifications can
398
+ # omit the from address
399
+ raise InvalidSender.new("Sender must be a Facebooker::User") unless from.is_a?(Facebooker::User) || !requires_from_user?(from,_body)
400
+ case _body
401
+ when Facebooker::Feed::TemplatizedAction,Facebooker::Feed::Action
402
+ from.publish_action(_body)
403
+ when Facebooker::Feed::Story
404
+ @recipients.each {|r| r.publish_story(_body)}
405
+ when Notification
406
+ (from.nil? ? Facebooker::Session.create : from.session).send_notification(@recipients,_body.fbml)
407
+ when Email
408
+ from.session.send_email(@recipients,
409
+ _body.title,
410
+ _body.text,
411
+ _body.fbml)
412
+ when Profile
413
+ # If recipient and from aren't the same person, create a new user object using the
414
+ # userid from recipient and the session from from
415
+ @from = Facebooker::User.new(Facebooker::User.cast_to_facebook_id(@recipients.first),Facebooker::Session.create)
416
+ @from.set_profile_fbml(_body.profile, _body.mobile_profile, _body.profile_action, _body.profile_main)
417
+ when Ref
418
+ Facebooker::Session.create.server_cache.set_ref_handle(_body.handle,_body.fbml)
419
+ when UserAction
420
+ @from.session.publish_user_action(_body.template_id,_body.data_hash,_body.target_ids,_body.body_general,_body.story_size)
421
+ when PublishStream
422
+ @from.publish_to(_body.target, {:attachment => _body.attachment, :action_links => @action_links, :message => _body.message })
423
+ else
424
+ raise UnspecifiedBodyType.new("You must specify a valid send_as")
425
+ end
426
+ end
427
+
428
+ # nodoc
429
+ # needed for actionview
430
+ def logger
431
+ RAILS_DEFAULT_LOGGER
432
+ end
433
+
434
+ # nodoc
435
+ # delegate to action view. Set up assigns and render
436
+ def render(opts)
437
+ opts = opts.dup
438
+ body = opts.delete(:assigns) || {}
439
+ initialize_template_class(body.dup.merge(:controller=>self)).render(opts)
440
+ end
441
+
442
+
443
+ def initialize_template_class(assigns)
444
+ template_root = "#{RAILS_ROOT}/app/views"
445
+ controller_root = File.join(template_root,self.class.controller_path)
446
+ #only do this on Rails 2.1
447
+ if ActionController::Base.respond_to?(:append_view_path)
448
+ # only add the view path once
449
+ unless ActionController::Base.view_paths.include?(controller_root)
450
+ ActionController::Base.append_view_path(controller_root)
451
+ ActionController::Base.append_view_path(controller_root+"/..")
452
+ end
453
+ view_paths = ActionController::Base.view_paths
454
+ else
455
+ view_paths = [template_root, controller_root]
456
+ end
457
+ returning ActionView::Base.new(view_paths, assigns, self) do |template|
458
+ template.controller=self
459
+ template.extend(self.class.master_helper_module)
460
+ def template.request_comes_from_facebook?
461
+ true
462
+ end
463
+ end
464
+ end
465
+
466
+
467
+ self.master_helper_module = Module.new
468
+ self.master_helper_module.module_eval do
469
+ # url_helper delegates to @controller,
470
+ # so we need to define that in the template
471
+ # we make it point to the publisher
472
+ include ActionView::Helpers::UrlHelper
473
+ include ActionView::Helpers::TextHelper
474
+ include ActionView::Helpers::TagHelper
475
+ include ActionView::Helpers::FormHelper
476
+ include ActionView::Helpers::FormTagHelper
477
+ include ActionView::Helpers::AssetTagHelper
478
+ include ActionView::Helpers::NumberHelper
479
+ include Facebooker::Rails::Helpers
480
+
481
+ #define this for the publisher views
482
+ def protect_against_forgery?
483
+ @paf ||= ActionController::Base.new.send(:protect_against_forgery?)
484
+ end
485
+
486
+ # url_for calls in publishers tend to want full paths
487
+ def url_for(options = {})
488
+ super(options.kind_of?(Hash) ? {:only_path => false}.update(options) : options)
489
+ end
490
+ end
491
+ ActionController::Routing::Routes.named_routes.install(self.master_helper_module)
492
+ include self.master_helper_module
493
+ class <<self
494
+
495
+ def register_all_templates_on_all_applications
496
+ Facebooker.with_all_applications do
497
+ puts "Registering templates for #{Facebooker.api_key}"
498
+ register_all_templates
499
+ end
500
+ end
501
+
502
+ def register_all_templates
503
+ all_templates = instance_methods.grep(/_template$/) - %w(short_story_template full_story_template one_line_story_template)
504
+ all_templates.each do |template|
505
+ template_name=template.sub(/_template$/,"")
506
+ puts "Registering #{template_name}"
507
+ send("register_"+template_name)
508
+ end
509
+ end
510
+
511
+ def unregister_inactive_templates
512
+ session = Facebooker::Session.create
513
+ active_template_ids = FacebookTemplate.all.map(&:bundle_id)
514
+ all_template_ids = session.active_template_bundles.map {|t| t["template_bundle_id"]}
515
+ (all_template_ids - active_template_ids).each do |template_bundle_id|
516
+ session.deactivate_template_bundle_by_id(template_bundle_id)
517
+ end
518
+ end
519
+
520
+ def method_missing(name,*args)
521
+ should_send = false
522
+ method = ''
523
+ if md = /^create_(.*)$/.match(name.to_s)
524
+ method = md[1]
525
+ elsif md = /^deliver_(.*)$/.match(name.to_s)
526
+ method = md[1]
527
+ should_send = true
528
+ elsif md = /^register_(.*)$/.match(name.to_s)
529
+ return FacebookTemplate.register(self, md[1])
530
+ else
531
+ super
532
+ end
533
+
534
+ #now create the item
535
+ (publisher=new).send(method,*args)
536
+ case publisher._body
537
+ when UserAction
538
+ publisher._body.template_name = method
539
+ publisher._body.template_id ||= FacebookTemplate.bundle_id_for_class_and_method(self,method)
540
+ end
541
+
542
+ should_send ? publisher.send_message(method) : publisher._body
543
+ end
544
+
545
+ def controller_path
546
+ self.to_s.underscore
547
+ end
548
+
549
+ def helper(*args)
550
+ args.each do |arg|
551
+ case arg
552
+ when Symbol,String
553
+ add_template_helper("#{arg.to_s.camelcase}Helper".constantize)
554
+ when Module
555
+ add_template_helper(arg)
556
+ end
557
+ end
558
+ end
559
+
560
+ def add_template_helper(helper_module) #:nodoc:
561
+ master_helper_module.send :include,helper_module
562
+ include master_helper_module
563
+ end
564
+
565
+
566
+ def inherited(child)
567
+ super
568
+ child.master_helper_module=Module.new
569
+ child.master_helper_module.__send__(:include,self.master_helper_module)
570
+ child.send(:include, child.master_helper_module)
571
+ FacebookTemplate.clear_cache!
572
+ end
573
+
574
+ end
575
+ class PublisherController
576
+ include Facebooker::Rails::Publisher.master_helper_module
577
+ include ActionController::UrlWriter
578
+
579
+ def initialize(source)
580
+ self.class.url_option_source = source
581
+ end
582
+
583
+ class << self
584
+ attr_accessor :url_option_source
585
+ alias :old_default_url_options :default_url_options
586
+ def default_url_options(*args)
587
+ url_option_source.default_url_options(*args)
588
+ end
589
+ end
590
+
591
+ end
592
+
593
+ end
594
+ end
595
+ end