mail_manager 0.0.1

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 (197) hide show
  1. checksums.yaml +15 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +1 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/Gemfile +27 -0
  8. data/Guardfile +24 -0
  9. data/LICENSE.txt +22 -0
  10. data/MIT-LICENSE +20 -0
  11. data/Manifest.txt +141 -0
  12. data/Procfile +4 -0
  13. data/README +243 -0
  14. data/README.md +29 -0
  15. data/README.rdoc +3 -0
  16. data/Rakefile +33 -0
  17. data/app/.DS_Store +0 -0
  18. data/app/assets/javascripts/mail_manager/application.js +15 -0
  19. data/app/assets/stylesheets/mail_manager/application.css +13 -0
  20. data/app/controllers/mail_manager/application_controller.rb +4 -0
  21. data/app/controllers/mail_manager/base_controller.rb +22 -0
  22. data/app/controllers/mail_manager/bounces_controller.rb +32 -0
  23. data/app/controllers/mail_manager/contacts_controller.rb +75 -0
  24. data/app/controllers/mail_manager/mailing_lists_controller.rb +49 -0
  25. data/app/controllers/mail_manager/mailings_controller.rb +102 -0
  26. data/app/controllers/mail_manager/messages_controller.rb +30 -0
  27. data/app/controllers/mail_manager/subscriptions_controller.rb +104 -0
  28. data/app/helpers/mail_manager/application_helper.rb +4 -0
  29. data/app/helpers/mail_manager/subscriptions_helper.rb +8 -0
  30. data/app/models/.DS_Store +0 -0
  31. data/app/models/mail_manager.rb +12 -0
  32. data/app/models/mail_manager/bounce.rb +133 -0
  33. data/app/models/mail_manager/contact.rb +91 -0
  34. data/app/models/mail_manager/contactable_registry.rb +190 -0
  35. data/app/models/mail_manager/mailable_registry.rb +127 -0
  36. data/app/models/mail_manager/mailer.rb +267 -0
  37. data/app/models/mail_manager/mailing.rb +266 -0
  38. data/app/models/mail_manager/mailing_list.rb +36 -0
  39. data/app/models/mail_manager/message.rb +127 -0
  40. data/app/models/mail_manager/subscription.rb +126 -0
  41. data/app/models/mail_manager/test_message.rb +175 -0
  42. data/app/models/status_history.rb +60 -0
  43. data/app/views/layouts/mail_manager/application.html.erb +14 -0
  44. data/app/views/mail_manager/bounces/_email_parts.html.erb +30 -0
  45. data/app/views/mail_manager/bounces/index.html.erb +32 -0
  46. data/app/views/mail_manager/bounces/show.html.erb +38 -0
  47. data/app/views/mail_manager/contacts/_form.html.erb +27 -0
  48. data/app/views/mail_manager/contacts/double_opt_in.html.erb +1 -0
  49. data/app/views/mail_manager/contacts/edit.html.erb +12 -0
  50. data/app/views/mail_manager/contacts/index.html.erb +86 -0
  51. data/app/views/mail_manager/contacts/new.html.erb +9 -0
  52. data/app/views/mail_manager/contacts/show.html.erb +22 -0
  53. data/app/views/mail_manager/contacts/subscribe.html.erb +2 -0
  54. data/app/views/mail_manager/contacts/thank_you.html.erb +12 -0
  55. data/app/views/mail_manager/help/_available_email_substitutions.html.erb +5 -0
  56. data/app/views/mail_manager/mailer/double_opt_in.erb +6 -0
  57. data/app/views/mail_manager/mailer/unsubscribed.erb +5 -0
  58. data/app/views/mail_manager/mailer/unsubscribed.html.erb +5 -0
  59. data/app/views/mail_manager/mailing_lists/_form.html.erb +20 -0
  60. data/app/views/mail_manager/mailing_lists/edit.html.erb +13 -0
  61. data/app/views/mail_manager/mailing_lists/index.html.erb +39 -0
  62. data/app/views/mail_manager/mailing_lists/new.html.erb +9 -0
  63. data/app/views/mail_manager/mailing_lists/show.html.erb +13 -0
  64. data/app/views/mail_manager/mailings/_form.html.erb +81 -0
  65. data/app/views/mail_manager/mailings/edit.html.erb +12 -0
  66. data/app/views/mail_manager/mailings/index.html.erb +52 -0
  67. data/app/views/mail_manager/mailings/new.html.erb +9 -0
  68. data/app/views/mail_manager/mailings/show.html.erb +28 -0
  69. data/app/views/mail_manager/mailings/test.html.erb +12 -0
  70. data/app/views/mail_manager/messages/index.html.erb +37 -0
  71. data/app/views/mail_manager/subscriptions/_form.html.erb +37 -0
  72. data/app/views/mail_manager/subscriptions/_subscriptions.html.erb +13 -0
  73. data/app/views/mail_manager/subscriptions/edit.html.erb +11 -0
  74. data/app/views/mail_manager/subscriptions/index.html.erb +32 -0
  75. data/app/views/mail_manager/subscriptions/new.html.erb +9 -0
  76. data/app/views/mail_manager/subscriptions/show.html.erb +8 -0
  77. data/app/views/mail_manager/subscriptions/unsubscribe.html.erb +2 -0
  78. data/app/views/mail_manager/subscriptions/unsubscribe_by_email_address.html.erb +13 -0
  79. data/config/daemons.yml +5 -0
  80. data/config/routes.rb +43 -0
  81. data/db/migrate/001_mail_mgr_initial.rb +84 -0
  82. data/db/migrate/002_mail_mgr_create_contact.rb +60 -0
  83. data/db/migrate/003_mail_mgr_test_message.rb +23 -0
  84. data/db/migrate/004_add_deleted_at_to_mailing_lists.rb +15 -0
  85. data/db/migrate/005_contacts_deleted_at.rb +15 -0
  86. data/db/migrate/006_mail_mgr_mailing_list_add_defaults_to_active.rb +15 -0
  87. data/db/migrate/007_mail_mgr_message_add_from_email_address.rb +15 -0
  88. data/db/mlm_migrate/001_mlm_initial.rb +67 -0
  89. data/db/mlm_migrate/002_mailable_as_polymorphic.rb +27 -0
  90. data/db/mlm_migrate/003_contact_as_polymorphic.rb +64 -0
  91. data/db/mlm_migrate/004_bounce_mlm_mailing_id.rb +26 -0
  92. data/db/mlm_migrate/005_mlm_to_mail_mgr_scoped.rb +29 -0
  93. data/engine_plan.rb +13 -0
  94. data/features/bounce_management.feature +0 -0
  95. data/features/contact_management.feature +24 -0
  96. data/features/mailable.feature +23 -0
  97. data/features/mailing_management.feature +78 -0
  98. data/features/message.feature +11 -0
  99. data/features/step_definitions/email_steps.rb +50 -0
  100. data/features/step_definitions/mlm_steps.rb +11 -0
  101. data/features/step_definitions/pickle_steps.rb +41 -0
  102. data/features/step_definitions/webrat_steps.rb +115 -0
  103. data/features/subscription_management.feature +17 -0
  104. data/features/support/env.rb +31 -0
  105. data/features/support/paths.rb +44 -0
  106. data/lib/daemons/mail_manager.rb +38 -0
  107. data/lib/daemons/mail_manager_ctl +15 -0
  108. data/lib/deleteable.rb +50 -0
  109. data/lib/lock.rb +36 -0
  110. data/lib/mail_manager.rb +5 -0
  111. data/lib/mail_manager/config.rb +50 -0
  112. data/lib/mail_manager/engine.rb +43 -0
  113. data/lib/mail_manager/version.rb +3 -0
  114. data/lib/tasks/mail_manager.rake +143 -0
  115. data/lib/tasks/mail_manager_tasks.rake +4 -0
  116. data/lib/tasks/rspec.rake +165 -0
  117. data/lib/workers/mail_manager/bounce_job.rb +52 -0
  118. data/lib/workers/mail_manager/mailing_job.rb +30 -0
  119. data/lib/workers/mail_manager/message_job.rb +38 -0
  120. data/lib/workers/mail_manager/test_message_job.rb +37 -0
  121. data/mail_manager.gemspec +29 -0
  122. data/script/rails +8 -0
  123. data/spec/rcov.opts +2 -0
  124. data/spec/spec.opts +4 -0
  125. data/spec/spec_helper.rb +47 -0
  126. data/spec/test_app/README.rdoc +261 -0
  127. data/spec/test_app/Rakefile +7 -0
  128. data/spec/test_app/app/assets/javascripts/application.js +15 -0
  129. data/spec/test_app/app/assets/javascripts/users.js +2 -0
  130. data/spec/test_app/app/assets/stylesheets/application.css +13 -0
  131. data/spec/test_app/app/assets/stylesheets/scaffold.css +56 -0
  132. data/spec/test_app/app/assets/stylesheets/users.css +4 -0
  133. data/spec/test_app/app/controllers/application_controller.rb +3 -0
  134. data/spec/test_app/app/controllers/users_controller.rb +83 -0
  135. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  136. data/spec/test_app/app/helpers/users_helper.rb +2 -0
  137. data/spec/test_app/app/models/user.rb +13 -0
  138. data/spec/test_app/app/views/layouts/application.html.erb +14 -0
  139. data/spec/test_app/app/views/users/_form.html.erb +33 -0
  140. data/spec/test_app/app/views/users/edit.html.erb +6 -0
  141. data/spec/test_app/app/views/users/index.html.erb +29 -0
  142. data/spec/test_app/app/views/users/new.html.erb +5 -0
  143. data/spec/test_app/app/views/users/show.html.erb +25 -0
  144. data/spec/test_app/config.ru +4 -0
  145. data/spec/test_app/config/application.rb +65 -0
  146. data/spec/test_app/config/boot.rb +10 -0
  147. data/spec/test_app/config/database.yml +25 -0
  148. data/spec/test_app/config/environment.rb +14 -0
  149. data/spec/test_app/config/environments/development.rb +37 -0
  150. data/spec/test_app/config/environments/production.rb +67 -0
  151. data/spec/test_app/config/environments/test.rb +37 -0
  152. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  153. data/spec/test_app/config/initializers/inflections.rb +15 -0
  154. data/spec/test_app/config/initializers/mime_types.rb +5 -0
  155. data/spec/test_app/config/initializers/secret_token.rb +7 -0
  156. data/spec/test_app/config/initializers/session_store.rb +8 -0
  157. data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
  158. data/spec/test_app/config/locales/en.yml +5 -0
  159. data/spec/test_app/config/lockable.yml +3 -0
  160. data/spec/test_app/config/mail_manager.yml +21 -0
  161. data/spec/test_app/config/routes.rb +7 -0
  162. data/spec/test_app/db/migrate/20131217101010_create_users.rb +13 -0
  163. data/spec/test_app/db/migrate/20131221064151_mail_mgr_initial.rb +84 -0
  164. data/spec/test_app/db/migrate/20131221064152_mail_mgr_create_contact.rb +60 -0
  165. data/spec/test_app/db/migrate/20131221064153_mail_mgr_test_message.rb +23 -0
  166. data/spec/test_app/db/migrate/20131221064154_add_deleted_at_to_mailing_lists.rb +15 -0
  167. data/spec/test_app/db/migrate/20131221064155_contacts_deleted_at.rb +15 -0
  168. data/spec/test_app/db/migrate/20131221064156_mail_mgr_mailing_list_add_defaults_to_active.rb +15 -0
  169. data/spec/test_app/db/migrate/20131221064157_mail_mgr_message_add_from_email_address.rb +15 -0
  170. data/spec/test_app/db/migrate/20131221072600_create_delayed_jobs.rb +22 -0
  171. data/spec/test_app/db/schema.rb +131 -0
  172. data/spec/test_app/db/structure.sql +31 -0
  173. data/spec/test_app/public/404.html +26 -0
  174. data/spec/test_app/public/422.html +26 -0
  175. data/spec/test_app/public/500.html +25 -0
  176. data/spec/test_app/public/favicon.ico +0 -0
  177. data/spec/test_app/script/delayed_job +5 -0
  178. data/spec/test_app/script/lockable +36 -0
  179. data/spec/test_app/script/rails +6 -0
  180. data/spec/test_app/spec/controllers/users_controller_spec.rb +160 -0
  181. data/spec/test_app/spec/factories/mailing_lists.rb +7 -0
  182. data/spec/test_app/spec/factories/mailings.rb +6 -0
  183. data/spec/test_app/spec/factories/original_factories.rb.txt +107 -0
  184. data/spec/test_app/spec/factories/users.rb +10 -0
  185. data/spec/test_app/spec/models/mail_manager/bounce_spec.rb +17 -0
  186. data/spec/test_app/spec/models/mail_manager/mailing_list_spec.rb +15 -0
  187. data/spec/test_app/spec/models/user_spec.rb +37 -0
  188. data/spec/test_app/spec/requests/users_spec.rb +11 -0
  189. data/spec/test_app/spec/routing/users_routing_spec.rb +35 -0
  190. data/spec/test_app/spec/support/database_cleaner.rb +23 -0
  191. data/spec/test_app/spec/support/files/bad_utf8_chars.eml +32 -0
  192. data/spec/test_app/spec/views/users/edit.html.erb_spec.rb +24 -0
  193. data/spec/test_app/spec/views/users/index.html.erb_spec.rb +29 -0
  194. data/spec/test_app/spec/views/users/new.html.erb_spec.rb +24 -0
  195. data/spec/test_app/spec/views/users/show.html.erb_spec.rb +21 -0
  196. data/zeus.json +22 -0
  197. metadata +424 -0
@@ -0,0 +1,127 @@
1
+ =begin rdoc
2
+ Author:: Chris Hauboldt (mailto:biz@lnstar.com)
3
+ Copyright:: 2009 Lone Star Internet Inc.
4
+
5
+ This class supplies the method for things to be "mailable" from the mailing list manager. Mailable things
6
+ need to be able to be found, have a distinguishable name, have "parts" which contain content for the email and
7
+ register themselves in initialization code. Below is a sample model class and its initialization code.
8
+
9
+ NOTE!!! you should order the parts in the order you would like them to be placed in the email... the last one seems to be the prefered type of many email clients, although some will have preference settings. In general put the one you want them to see last.
10
+
11
+
12
+
13
+ class Mailable < ActiveRecord::Base
14
+ named_scope :active, :conditions => ['deleted_at is null and published_at<?',Time.now.utc]
15
+ def name
16
+ name + created_at
17
+ end
18
+ def email_html
19
+ "<html><body>Hello World!</body></html>"
20
+ end
21
+ def email_text
22
+ "Hello World!"
23
+ end
24
+ end
25
+
26
+ begin
27
+ require 'mailable'
28
+ MailableRegistry.register(MyMailable,{
29
+ :find_mailables => :active,
30
+ :name => :name,
31
+ :parts => [
32
+ ['text/plain' => :email_text],
33
+ ['text/html' => :email_html]
34
+ ]
35
+ })
36
+ Rails.logger.warn "Registered Newsletter Mailable"
37
+ rescue => e
38
+ Rails.logger.warn "Couldn't register Newsletter Mailable #{e.message}"
39
+ end
40
+ --
41
+ =end
42
+
43
+ module MailManager
44
+ class MailableRegistry
45
+ attr_reader :mailable_things
46
+
47
+ =begin rdoc
48
+ Registers a class as a "mailable" item.
49
+ Parameters::
50
+ klass => Class constant to be registered
51
+ methods => a hash which maps :find, :name, and mime type to methods; :parts {'mime-type' => :method}
52
+
53
+ Example Useage:
54
+ you may want to wrap your register in a rescue block if you don't know whether or not the
55
+ mailing list manager exists in your current project.
56
+
57
+ begin
58
+ require 'mail_manager/mailable_registry'
59
+ MailableRegistry.register(MyMailable,{
60
+ :find_mailables => :active,
61
+ :name => :name,
62
+ :parts => [
63
+ ['text/plain' => :email_text],
64
+ ['text/html' => :email_html]
65
+ ]
66
+ })
67
+ Rails.logger.debug "Registered Newsletter Mailable"
68
+ rescue => e
69
+ Rails.logger.debug "Couldn't register Newsletter Mailable #{e.message}"
70
+ end
71
+
72
+ =end
73
+ def self.register(klass,methods={})
74
+ Rails.logger.warn "Registered Mailable: #{klass.inspect} - #{methods.inspect}"
75
+ @@mailable_things.merge!({klass => methods})
76
+ end
77
+
78
+ =begin rdoc
79
+ Finds available mailable items by searching through all registered mailables, calling their finders and sorting by name.
80
+ =end
81
+ def self.find
82
+ mailable_items = []
83
+ @@mailable_things.each_pair do |thing,methods|
84
+ Rails.logger.debug "Gathering #{thing} mailables with #{methods[:find_mailables]}"
85
+ mailable_items += thing.constantize.send(methods[:find_mailables])
86
+ end
87
+ mailable_items.sort{|a,b| a.name <=> b.name}
88
+ end
89
+
90
+
91
+ protected
92
+ # -- holds registrations of mailables
93
+ @@mailable_things = Hash.new
94
+
95
+ def self.mailable_things
96
+ @@mailable_things
97
+ end
98
+
99
+ module Mailable
100
+ def mailable_initialize_parts
101
+ @mailable_parts = []
102
+ MailableRegistry.mailable_things[self.class.name][:parts].each{|part,method|
103
+ @mailable_parts << [part, send(method)]
104
+ }
105
+ @mailable_parts
106
+ end
107
+
108
+ def mailable_value(method)
109
+ return send(method) unless MailableRegistry.mailable_things[self.class.name] and
110
+ MailableRegistry.mailable_things[self.class.name][method]
111
+ send(MailableRegistry.mailable_things[self.class.name][method])
112
+ end
113
+
114
+ def mailable_parts
115
+ return @mailable_parts unless @mailable_parts.nil?
116
+ mailable_initialize_parts
117
+ end
118
+
119
+ def self.included(base)
120
+ base.class_eval do
121
+ has_many :mailings, :as => :mailable, :class_name => "MailManager::Mailing"
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+
@@ -0,0 +1,267 @@
1
+ =begin rdoc
2
+ Author:: Chris Hauboldt (mailto:biz@lnstar.com)
3
+ Copyright:: 2009 Lone Star Internet Inc.
4
+
5
+ This class is responsible for actually sending email messages... its mostly just an ActionMailer, but also has the added functionality to send messages with inline images.
6
+
7
+ Messages:
8
+ unsubscribed - sends an email to notify the user that they have been removed
9
+ message - sends and Message
10
+ test_mailing - sends a test message for a mailing
11
+ mail - knows how to send any message based on the different "mime" parts its given
12
+
13
+ =end
14
+
15
+ require 'net/http'
16
+ require 'uri'
17
+ require "base64"
18
+ begin
19
+ require "mini_magick"
20
+ rescue => e
21
+ require 'rmagick' rescue nil
22
+ end
23
+
24
+
25
+ module MailManager
26
+ class Mailer < ActionMailer::Base
27
+ def unsubscribed(message,subscriptions)
28
+ @contact = message.contact
29
+ @recipients = @contact.email_address
30
+ @from = message.from_email_address
31
+ @message = message
32
+ @mailing_lists = subscriptions.reject{|subscription| subscription.mailing_list.nil?}.
33
+ collect{|subscription| subscription.mailing_list.name}
34
+ @subject = "Unsubscribed from #{@mailing_lists.join(',')} at #{MailManager.site_url}"
35
+ Rails.logger.debug "Really Sending Unsubscribed from #{@mailing_lists.first} to #{@contact.email_address}"
36
+ mail(to: @recipients, from: @from, subject: @subject)
37
+ end
38
+
39
+ # we do special junk ... so lets make them class methods
40
+ class << self
41
+ def deliver_message(message)
42
+ self.send_mail(message.subject,message.email_address_with_name,message.from_email_address,
43
+ message.parts,message.guid,message.mailing.include_images?)
44
+ end
45
+
46
+ def multipart_with_inline_images(subject,to_email_address,from_email_address,the_parts,message_id=nil,include_images=true)
47
+ text_source = the_parts.first[1];nil
48
+ original_html_source = the_parts.last[1];nil
49
+ mail = Mail.new do
50
+ to to_email_address
51
+ from from_email_address
52
+ subject subject
53
+ part :content_type => "multipart/alternative", :content_disposition => "inline" do |main|
54
+ main.part :content_type => "text/plain", :body => text_source
55
+ if include_images
56
+ main.part :content_type => "multipart/related" do |related|
57
+ (html_source,images) = MailManager::Mailer::inline_html_with_images(original_html_source)
58
+ images.each_with_index do |image,index|
59
+ related.attachments.inline[image[:filename]] = {
60
+ :content_id => image[:cid],
61
+ :content => image[:content]
62
+ }
63
+ html_source.gsub!(image[:cid],related.attachments[index].cid)
64
+ end
65
+ related.part :content_type => "text/html; charset=UTF-8", :body => html_source
66
+ end
67
+ else
68
+ main.part :content_type => "text/html; charset=UTF-8", :body => original_html_source
69
+ end
70
+ end
71
+ end
72
+ mail
73
+ end
74
+
75
+ def multipart_alternative_without_images(subject,to_email_address,from_email_address,the_parts,message_id=nil,include_images=true)
76
+ text_source = the_parts.first[1];nil
77
+ original_html_source = the_parts.last[1];nil
78
+ mail = Mail.new do
79
+ to to_email_address
80
+ from from_email_address
81
+ subject subject
82
+
83
+ text_part do
84
+ body text_source
85
+ end
86
+
87
+ html_part do
88
+ content_type 'text/html; charset=UTF-8'
89
+ body original_html_source
90
+ end
91
+ end
92
+ mail
93
+ end
94
+
95
+ def send_mail(subject,to_email_address,from_email_address,the_parts,message_id=nil,include_images=true)
96
+ include_images = (include_images and !MailManager.dont_include_images_domains.detect{|domain|
97
+ to_email_address.strip =~ /#{domain}>?$/})
98
+ mail = if include_images
99
+ multipart_with_inline_images(subject,to_email_address,from_email_address,the_parts,message_id,include_images)
100
+ else
101
+ multipart_alternative_without_images(subject,to_email_address,from_email_address,the_parts,message_id,include_images)
102
+ end
103
+ mail.header['Return-Path'] = MailManager.bounce['email_address']
104
+ mail.header['X-Bounce-Guid'] = message_id if message_id
105
+ set_mail_settings(mail)
106
+ mail.deliver!
107
+ Rails.logger.info "Sent mail to: #{to_email_address}"
108
+ Rails.logger.debug mail.to_s
109
+ end
110
+
111
+ def set_mail_settings(mail)
112
+ mail.delivery_method ActionMailer::Base.delivery_method.eql?(:letter_opener) ? :test : ActionMailer::Base.delivery_method
113
+ # letter opener blows up!
114
+ # Ex set options!
115
+ # mail.delivery_method.settings.merge!( {
116
+ # user_name: 'bobo',
117
+ # password: 'Secret1!',
118
+ # address: 'mail.lnstar.com',
119
+ # domain: 'mail.lnstar.com',
120
+ # enable_starttls_auto: true,
121
+ # authentication: :plain,
122
+ # port: 587
123
+ # } )
124
+
125
+ mail.delivery_method.settings.merge!(
126
+ (case method
127
+ when :smtp then ActionMailer::Base.smtp_settings
128
+ when :sendmail then ActionMailer::Base.sendmail_settings
129
+ else
130
+ {}
131
+ end rescue {})
132
+ )
133
+ end
134
+
135
+ def inline_attachment(params, &block)
136
+ params = { :content_type => params } if String === params
137
+ params = { :disposition => "inline",
138
+ :transfer_encoding => "base64" }.merge(params)
139
+ params[:headers] ||= {}
140
+ params[:headers]['Content-ID'] = params[:cid]
141
+ params
142
+ end
143
+
144
+ def image_mime_types(extension)
145
+ case extension.downcase
146
+ when 'bmp' then 'image/bmp'
147
+ when 'cod' then 'image/cis-cod'
148
+ when 'gif' then 'image/gif'
149
+ when 'ief' then 'image/ief'
150
+ when 'jpe' then 'image/jpeg'
151
+ when 'jpeg' then 'image/jpeg'
152
+ when 'jpg' then 'image/jpeg'
153
+ when 'png' then 'image/png'
154
+ when 'jfif' then 'image/pipeg'
155
+ when 'svg' then 'image/svg+xml'
156
+ when 'tif' then 'image/tiff'
157
+ when 'tiff' then 'image/tiff'
158
+ end
159
+ end
160
+
161
+ def get_extension_from_data(image_data)
162
+ if defined?(MiniMagick)
163
+ MiniMagick::Image.read(image_data)[:format] || ''
164
+ elsif defined?(Magick)
165
+ Magick::Image.from_blob(image_data).first.format || ''
166
+ else
167
+ ''
168
+ end
169
+ rescue => e
170
+ ''
171
+ end
172
+
173
+ def inline_html_with_images(html_source)
174
+ parsed_data = html_source.split(/(<\s*img[^>]+src\s*=\s*["'])([^"']*)(["'])/i)
175
+ images = Array.new
176
+ final_html = ''
177
+ image_errors = ''
178
+ parsed_data.each_with_index do |data,index|
179
+ if(index % 4 == 2)
180
+ image = Hash.new()
181
+ image[:cid] = Base64.encode64(data).gsub(/\s*/,'').reverse[0..59]
182
+ final_html << "cid:#{image[:cid]}"
183
+ #only attach new images!
184
+ next if images.detect{|this_image| this_image[:cid].eql?(image[:cid])}
185
+ begin
186
+ image[:content] = fetch(data)
187
+ rescue => e
188
+ image_errors += "Couldn't fetch url '#{data}'<!--, #{e.message} - #{e.backtrace.join("\n")}-->\n"
189
+ end
190
+ image[:filename] = filename = File.basename(data)
191
+ extension = filename.gsub(/^.*\./,'').downcase
192
+ Rails.logger.debug "Fetching Image for: #{filename} #{image[:content].to_s[0..30]}"
193
+ extension = get_extension_from_data(image[:content]) if image_mime_types(extension).blank?
194
+ image_errors += "Couldn't find mime type for #{extension} on #{data}" if image_mime_types(extension).blank?
195
+ image[:content_type] = image_mime_types(extension)
196
+ images << image
197
+ else
198
+ final_html << data
199
+ end
200
+ end
201
+ raise image_errors unless image_errors.eql?('')
202
+ [final_html,images]
203
+ # related_part = Mail::Part.new do
204
+ # body final_html
205
+ # end
206
+ # images.each do |image|
207
+ # related_part.part inline_attachment(image)
208
+ # end
209
+ # related_part.content_type = 'multipart/related'
210
+ # related_part
211
+
212
+ # related_part = Mail::Part.new do
213
+ # content_type 'multipart/related'
214
+ # # content_type 'text/html; charset=UTF-8'
215
+ # # body final_html
216
+ # end
217
+ # related_part.parts << Mail::Part.new do
218
+ # content_type 'text/html; charset=UTF-8'
219
+ # body final_html
220
+ # end
221
+ # images.each do |image|
222
+ # related_part.attachments[image[:filename]] = image[:body]
223
+ # end
224
+ # related_part.content_type = 'multipart/related'
225
+ # related_part.parts.first.content_type = 'text/html; charset=UTF-8'
226
+ # related_part.parts.first.header['Content-Disposition'] = 'inline'
227
+
228
+ end
229
+
230
+ def local_ips
231
+ `/sbin/ifconfig`
232
+ end
233
+
234
+ def request_local?(uri_str)
235
+ uri = URI.parse(uri_str)
236
+ ip_address = `host #{uri.host}`.gsub(/.*has address ([\d\.]+)\s.*/m,"\\1")
237
+ local_ips.include?(ip_address)
238
+ rescue => e
239
+ false
240
+ end
241
+
242
+ def fetch(uri_str, limit = 10)
243
+ # You should choose better exception.
244
+ # raise ArgumentError, 'HTTP redirect too deep' if limit == 0
245
+
246
+ # response = Net::HTTP.get_response(URI.parse(uri_str))
247
+ # case response
248
+ # when Net::HTTPSuccess then response.body
249
+ # when Net::HTTPRedirection then fetch(response['location'], limit - 1)
250
+ # else
251
+ # response.error!
252
+ # end
253
+ body = ''
254
+ Curl.get(uri_str) do |http|
255
+ http.follow_location = true
256
+ http.interface = '127.0.0.1' if request_local?(uri_str)
257
+ http.on_success{|response| body = response.body}
258
+ end
259
+ raise Exception.new("Couldn't fetch URL: #{uri_str}") unless body.present?
260
+ body
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+
267
+
@@ -0,0 +1,266 @@
1
+ # encoding: utf-8
2
+ =begin rdoc
3
+ Author:: Chris Hauboldt (mailto:biz@lnstar.com)
4
+ Copyright:: 2009 Lone Star Internet Inc.
5
+
6
+ Mailing is used to send a Mailable object to an MailingList. It is 'ready' to send when its status is 'scheduled' and 'shcheduled_at' is in the past. MailingJob will poll for 'ready' Mailings and send them.
7
+
8
+ Statuses
9
+ pending - initial state - mailing is waiting to be tested and scheduled - will not send
10
+ scheduled - mailing will be sent when it is 'ready' (its status is 'scheduled' and 'shcheduled_at' is in the past)
11
+ processing - MailingJob is sending the messages for the mailing
12
+ completed - Mailing has been sent
13
+
14
+ =end
15
+
16
+ module MailManager
17
+ class Mailing < ActiveRecord::Base
18
+ self.table_name = "#{MailManager.table_prefix}mailings"
19
+ has_many :messages, :class_name => 'MailManager::Message'
20
+ has_many :test_messages, :class_name => 'MailManager::TestMessage'
21
+ has_many :bounces, :class_name => 'MailManager::Bounce'
22
+ has_and_belongs_to_many :mailing_lists, :class_name => 'MailManager::MailingList',
23
+ :join_table => "#{MailManager.table_prefix}mailing_lists_#{MailManager.table_prefix}mailings"
24
+ #FIXME why does this break?
25
+ belongs_to :mailable, :polymorphic => true
26
+
27
+ accepts_nested_attributes_for :mailable
28
+
29
+ attr_accessor :bounce_count
30
+
31
+ validates_presence_of :subject
32
+ #validates_presence_of :mailable
33
+
34
+ scope :ready, lambda {{:conditions => ["(status='scheduled' AND scheduled_at < ?) OR status='resumed'",Time.now.utc]}}
35
+ scope :by_statuses, lambda {|*statuses| {:conditions => ["status in (#{statuses.collect{|bindings,status| '?'}.join(",")})",statuses].flatten}}
36
+
37
+ def self.with_bounces(bounce_status=nil)
38
+ bounce_status_condition = bounce_status.present? ? ActiveRecord::Base.send(:sanitize_sql_array,[" WHERE status=?", bounce_status]) : ''
39
+ bounce_query = "SELECT mailing_id, COUNT(id) AS count from #{MailManager.table_prefix}bounces #{bounce_status_condition} group by mailing_id"
40
+ bounce_data = Bounce.connection.execute(bounce_query).inject({}){|hash,(mailing_id,count)| hash.merge(mailing_id => count)}
41
+ mailings = scoped
42
+ mailings = mailings.where("id in (#{bounce_data.keys.select(&:present?).join(',')})") if bounce_data.keys.select(&:present?).present?
43
+ mailings.order("created_at desc").map{|mailing| mailing.bounce_count = bounce_data[mailing.id]; mailing}
44
+ end
45
+
46
+ include StatusHistory
47
+ override_statuses(['pending','scheduled','processing','paused','resumed','cancelled','completed'],'pending')
48
+ before_create :set_default_status
49
+
50
+ attr_protected :id
51
+
52
+ def send_one_off_message(contact)
53
+ message = Message.new
54
+ message.contact_id = contact.id
55
+ message.mailing_id = self.id
56
+ message.change_status(:ready)
57
+ message.delay.deliver
58
+ end
59
+
60
+ def deliver
61
+ Rails.logger.info "Starting to Process Mailing '#{subject}' ID:#{id}"
62
+ Lock.with_lock("mail_mgr_mailing_send[#{id}]") do |lock|
63
+ unless status.to_s.eql?('scheduled')
64
+ raise Exception.new("Mailing was not scheduled when job tried to run!")
65
+ end
66
+ unless scheduled_at <= Time.now
67
+ Rails.logger.info "Mailing is not scheduled to run until #{scheduled_at} rescheduling job!"
68
+ self.delay(run_at: scheduled_at).deliver
69
+ return true
70
+ end
71
+ change_status(:processing)
72
+ initialize_messages
73
+ messages.pending.each do |message|
74
+ if reload.status.to_s != 'processing'
75
+ Rails.logger.warn "Mailing #{id} is no longer in processing status it was changed to #{status} while running"
76
+ return false
77
+ end
78
+ begin
79
+ # use the cached mailing parts, set messages mailing to self
80
+ message.mailing=self
81
+ message.change_status(:processing)
82
+ message.deliver
83
+ message.change_status(:sent)
84
+ rescue => e
85
+ message.result = "Error: #{e.message} - #{e.backtrace.join("\n")}"
86
+ message.change_status(:failed)
87
+ end
88
+ Rails.logger.debug "Sleeping #{MailManager.sleep_time_between_messages} before next message"
89
+ sleep MailManager.sleep_time_between_messages
90
+ end
91
+ change_status(:completed) if status.to_s.eql?('processing')
92
+ end
93
+ end
94
+
95
+ def mailable
96
+ return @mailable if @mailable
97
+ return self unless mailable_type and mailable_id
98
+ @mailable = mailable_type.constantize.find(mailable_id)
99
+ end
100
+
101
+ def self.cleanse_source(source)
102
+ require 'iconv' unless String.method_defined?(:encode)
103
+ if String.method_defined?(:encode)
104
+ source.encode!('UTF-16', 'UTF-8', :invalid => :replace, :replace => '')
105
+ source.encode!('UTF-8', 'UTF-16')
106
+ else
107
+ ic = Iconv.new('UTF-8', 'UTF-8//IGNORE')
108
+ source = ic.iconv(source)
109
+ end
110
+ end
111
+
112
+ def self.substitute_values(source,substitutions)
113
+ substitutions.each_pair do |substitution,value|
114
+ if value.blank?
115
+ source.gsub!(/##{substitution}#([^#]*)#/,'\1') rescue source = self.cleanse_source(source).gsub(/##{substitution}#([^#]*)#/,'\1')
116
+ else
117
+ source.gsub!(/##{substitution}#[^#]*#/,value.to_s) rescue source = self.cleanse_source(source).gsub(/##{substitution}#[^#]*#/,value.to_s)
118
+ end
119
+ end
120
+ if defined? MailManager::ContactableRegistry.respond_to?(:valid_contactable_substitutions)
121
+ MailManager::ContactableRegistry.valid_contactable_substitutions.
122
+ reject{|key| substitutions.keys.include?(key)}.each do |substitution|
123
+ source.gsub!(/##{substitution}#([^#]*)#/,'\1') rescue source = self.cleanse_source(source).gsub(/##{substitution}#([^#]*)#/,'\1')
124
+ end
125
+ end
126
+ source
127
+ end
128
+
129
+ def raw_parts
130
+ @raw_parts ||= mailable.mailable_parts
131
+ end
132
+
133
+ def parts(substitutions={})
134
+ parts = []
135
+ raw_parts.each do |type,source|
136
+ parts << [type, Mailing.substitute_values(source.dup,substitutions)]
137
+ end
138
+ parts
139
+ end
140
+
141
+ def mailable=(value)
142
+ return if value.nil?
143
+ self[:mailable_type] = value.class.name
144
+ self[:mailable_id] = value.id
145
+ end
146
+
147
+ def mailable_class_and_id=(value)
148
+ return if value.nil?
149
+ parts = value.split(/_/)
150
+ self[:mailable_id] = parts.pop
151
+ self[:mailable_type] = parts.join('_')
152
+ end
153
+
154
+ #def mailable_attributes=(mailable_attributes={})
155
+ # mailable_attributes.each_pair do |key,value|
156
+ # end
157
+ #end
158
+
159
+ # creates all of the Messages that will be sent for this mailing
160
+ def initialize_messages
161
+ unless messages.length > 0
162
+ Rails.logger.info "Building mailing messages for mailing(#{id})"
163
+ transaction do
164
+ emails_hash = messages.select{|m| m.type.eql?('MailManager::Message')}.inject(Hash.new){|emails_hash,message| emails_hash.merge(Mailing.clean_email_address(message.email_address)=>1)}
165
+ mailing_lists.each do |mailing_list|
166
+ mailing_list.subscriptions.active.each do |subscription|
167
+ contact = subscription.contact
168
+ next if contact.nil? or contact.deleted?
169
+ email_address = Mailing.clean_email_address(contact.email_address)
170
+ if emails_hash.has_key?(email_address)
171
+ Rails.logger.info "Skipping duplicate address: #{email_address}"
172
+ else
173
+ Rails.logger.info "Adding #{email_address} to mailing #{subject}"
174
+ emails_hash[email_address] = 1
175
+ message = Message.new
176
+ message.subscription = subscription
177
+ message.contact = contact
178
+ message.mailing = self
179
+ message.save
180
+ end
181
+ end
182
+ end
183
+ end
184
+ save
185
+ end
186
+ end
187
+
188
+ # clean up an email address for sending FIXME - maybe do a bit more
189
+ def self.clean_email_address(email_address)
190
+ email_address.downcase.strip
191
+ end
192
+
193
+ # sends a test message for this mailing to the given address
194
+ def send_test_message(test_email_addresses)
195
+ test_email_addresses.split(/,/).each do |test_email_address|
196
+ puts "Creating test message for #{test_email_address}"
197
+ test_message = TestMessage.new(:test_email_address => test_email_address.strip)
198
+ test_message.mailing_id = self.id
199
+ test_message.save
200
+ test_message.delay.deliver
201
+ end
202
+ end
203
+
204
+ # used in select helpers to identify this Mailing's Mailable
205
+ def mailable_thing_and_id
206
+ return '' if mailable.nil?
207
+ return "#{mailable.class.name}_#{mailable.id}"
208
+ end
209
+
210
+ def mailing_list_ids=(mailing_list_ids)
211
+ mailing_list_ids.delete('')
212
+ self.mailing_lists = mailing_list_ids.collect{|mailing_list_id| MailingList.find_by_id(mailing_list_id)}
213
+ end
214
+
215
+ def can_pause?
216
+ ['processing'].include?(status.to_s)
217
+ end
218
+
219
+ def can_edit?
220
+ ['pending','scheduled','paused'].include?(status.to_s)
221
+ end
222
+
223
+ def can_cancel?
224
+ ['pending','scheduled','processing','paused','resumed'].include?(status.to_s)
225
+ end
226
+
227
+ def can_resume?
228
+ ['paused'].include?(status.to_s)
229
+ end
230
+
231
+ def can_schedule?
232
+ ['pending'].include?(status.to_s)
233
+ end
234
+
235
+ def schedule
236
+ raise "Unable to schedule" unless can_schedule?
237
+ change_status('scheduled')
238
+ delay(run_at: scheduled_at).deliver
239
+ end
240
+
241
+ def cancel
242
+ raise "Unable to cancel" unless can_cancel?
243
+ change_status('pending')
244
+ # Delayed::Job.active.find(:all, :conditions => ["handler like ?","MailMgr::Mailing"])
245
+
246
+ #Changing this to return only the jobs that match the id so I don't have to parse with YAML ... seems logical
247
+ mailing_jobs = Delayed::Job.find(:all, :conditions => ["handler like ?","%MailMgr::Mailing%"] || ["handler like ?", "%id: {job_mailing_id.to_i}\n%"])
248
+ #mailing_jobs = Delayed::Job.active.find(:all, :conditions => ["handler like ?","%MailMgr::Mailing%"])
249
+ mailing_jobs.each do |job|
250
+ #job_mailing_id = YAML::load(job.handler).object.split(':').last
251
+ #logger.debug "Job mailing id: #{job_mailing_id} - This mailing id: #{self.id} - do they match: #{job_mailing_id.to_i == self.id.to_i}"
252
+ job.destroy #if job_mailing_id.to_i == self.id.to_i
253
+ end
254
+ end
255
+
256
+ def resume
257
+ raise "Unable to resume" unless can_resume?
258
+ change_status('resumed')
259
+ end
260
+
261
+ def pause
262
+ raise "Unable to pause" unless can_pause?
263
+ change_status('paused')
264
+ end
265
+ end
266
+ end