fbooker 1.0.53

Sign up to get free protection for your applications and to get access to all the features.
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