alchemy_crm 2.0.0

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 (240) hide show
  1. data/.gitignore +22 -0
  2. data/.travis.yml +12 -0
  3. data/Gemfile +21 -0
  4. data/Guardfile +13 -0
  5. data/LICENSE +24 -0
  6. data/README.md +82 -0
  7. data/Rakefile +27 -0
  8. data/alchemy_crm.gemspec +31 -0
  9. data/app/assets/images/.DS_Store +0 -0
  10. data/app/assets/images/alchemy_crm/.DS_Store +0 -0
  11. data/app/assets/images/alchemy_crm/contact-icons.png +0 -0
  12. data/app/assets/images/alchemy_crm/fill_content.gif +0 -0
  13. data/app/assets/images/alchemy_crm/group-icons.png +0 -0
  14. data/app/assets/images/alchemy_crm/mailing-icons.png +0 -0
  15. data/app/assets/images/alchemy_crm/module_icon.png +0 -0
  16. data/app/assets/images/alchemy_crm/newsletter-icons.png +0 -0
  17. data/app/assets/images/alchemy_crm/statistic.png +0 -0
  18. data/app/assets/images/alchemy_crm/tag-icons.png +0 -0
  19. data/app/assets/javascripts/alchemy_crm/alchemy_crm.base.js +65 -0
  20. data/app/assets/javascripts/alchemy_crm/scripts.js +2 -0
  21. data/app/assets/stylesheets/alchemy_crm/base.css.scss +473 -0
  22. data/app/assets/stylesheets/alchemy_crm/styles.css +3 -0
  23. data/app/controllers/alchemy_crm/admin/base_controller.rb +7 -0
  24. data/app/controllers/alchemy_crm/admin/contact_groups_controller.rb +23 -0
  25. data/app/controllers/alchemy_crm/admin/contacts_controller.rb +112 -0
  26. data/app/controllers/alchemy_crm/admin/deliveries_controller.rb +80 -0
  27. data/app/controllers/alchemy_crm/admin/elements_controller_extension.rb +32 -0
  28. data/app/controllers/alchemy_crm/admin/mailings_controller.rb +47 -0
  29. data/app/controllers/alchemy_crm/admin/newsletters_controller.rb +24 -0
  30. data/app/controllers/alchemy_crm/admin/tags_controller.rb +62 -0
  31. data/app/controllers/alchemy_crm/base_controller.rb +5 -0
  32. data/app/controllers/alchemy_crm/contacts_controller.rb +103 -0
  33. data/app/controllers/alchemy_crm/mailings_controller.rb +20 -0
  34. data/app/controllers/alchemy_crm/recipients_controller.rb +35 -0
  35. data/app/controllers/alchemy_crm/subscriptions_controller.rb +57 -0
  36. data/app/helpers/alchemy/pages_helper_extension.rb +18 -0
  37. data/app/helpers/alchemy_crm/admin/base_helper.rb +12 -0
  38. data/app/helpers/alchemy_crm/mailings_helper.rb +190 -0
  39. data/app/mailers/alchemy_crm/bounce_receiver.rb +13 -0
  40. data/app/mailers/alchemy_crm/contacts_mailer.rb +26 -0
  41. data/app/mailers/alchemy_crm/mailings_mailer.rb +48 -0
  42. data/app/mailers/alchemy_crm/subscriptions_mailer.rb +17 -0
  43. data/app/models/alchemy/essence_element_teaser.rb +19 -0
  44. data/app/models/alchemy_crm/contact.rb +264 -0
  45. data/app/models/alchemy_crm/contact_group.rb +26 -0
  46. data/app/models/alchemy_crm/contact_group_filter.rb +26 -0
  47. data/app/models/alchemy_crm/delivery.rb +57 -0
  48. data/app/models/alchemy_crm/element_extension.rb +28 -0
  49. data/app/models/alchemy_crm/mailing.rb +93 -0
  50. data/app/models/alchemy_crm/newsletter.rb +48 -0
  51. data/app/models/alchemy_crm/reaction.rb +9 -0
  52. data/app/models/alchemy_crm/recipient.rb +48 -0
  53. data/app/models/alchemy_crm/subscription.rb +8 -0
  54. data/app/views/alchemy/admin/elements/_view_not_found.text.erb +0 -0
  55. data/app/views/alchemy/admin/elements/fill.js.erb +13 -0
  56. data/app/views/alchemy/admin/elements/link.js.erb +1 -0
  57. data/app/views/alchemy/elements/_newsletter_article_editor.html.erb +4 -0
  58. data/app/views/alchemy/elements/_newsletter_article_view.html.erb +16 -0
  59. data/app/views/alchemy/elements/_newsletter_article_view.text.erb +5 -0
  60. data/app/views/alchemy/elements/_newsletter_greeting_editor.html.erb +3 -0
  61. data/app/views/alchemy/elements/_newsletter_greeting_view.html.erb +11 -0
  62. data/app/views/alchemy/elements/_newsletter_greeting_view.text.erb +6 -0
  63. data/app/views/alchemy/elements/_newsletter_signout_form_editor.html.erb +3 -0
  64. data/app/views/alchemy/elements/_newsletter_signout_form_view.html.erb +32 -0
  65. data/app/views/alchemy/elements/_newsletter_signout_mail_editor.html.erb +4 -0
  66. data/app/views/alchemy/elements/_newsletter_signup_form_editor.html.erb +6 -0
  67. data/app/views/alchemy/elements/_newsletter_signup_form_view.html.erb +78 -0
  68. data/app/views/alchemy/elements/_newsletter_signup_mail_editor.html.erb +4 -0
  69. data/app/views/alchemy/essences/_essence_date_view.text.erb +3 -0
  70. data/app/views/alchemy/essences/_essence_element_teaser_editor.html.erb +31 -0
  71. data/app/views/alchemy/essences/_essence_element_teaser_view.html.erb +1 -0
  72. data/app/views/alchemy/essences/_essence_element_teaser_view.text.erb +3 -0
  73. data/app/views/alchemy/essences/_essence_richtext_view.text.erb +1 -0
  74. data/app/views/alchemy/essences/_essence_text_view.text.erb +1 -0
  75. data/app/views/alchemy/newsletter_layouts/_standard.html.erb +39 -0
  76. data/app/views/alchemy/newsletter_layouts/_standard.text.erb +5 -0
  77. data/app/views/alchemy_crm/admin/contact_groups/_contact_group.html.erb +32 -0
  78. data/app/views/alchemy_crm/admin/contact_groups/_filter.html.erb +30 -0
  79. data/app/views/alchemy_crm/admin/contact_groups/_form.html.erb +47 -0
  80. data/app/views/alchemy_crm/admin/contact_groups/_tag.html.erb +6 -0
  81. data/app/views/alchemy_crm/admin/contact_groups/add_filter.js.erb +9 -0
  82. data/app/views/alchemy_crm/admin/contact_groups/destroy.rjs +1 -0
  83. data/app/views/alchemy_crm/admin/contact_groups/edit.html.erb +1 -0
  84. data/app/views/alchemy_crm/admin/contact_groups/index.html.erb +41 -0
  85. data/app/views/alchemy_crm/admin/contact_groups/new.html.erb +1 -0
  86. data/app/views/alchemy_crm/admin/contacts/_contact.html.erb +44 -0
  87. data/app/views/alchemy_crm/admin/contacts/_form.html.erb +86 -0
  88. data/app/views/alchemy_crm/admin/contacts/destroy.rjs +1 -0
  89. data/app/views/alchemy_crm/admin/contacts/edit.html.erb +1 -0
  90. data/app/views/alchemy_crm/admin/contacts/import.html.erb +33 -0
  91. data/app/views/alchemy_crm/admin/contacts/import.js.erb +2 -0
  92. data/app/views/alchemy_crm/admin/contacts/index.html.erb +51 -0
  93. data/app/views/alchemy_crm/admin/contacts/new.html.erb +1 -0
  94. data/app/views/alchemy_crm/admin/contacts/vcf_import_result.html.erb +49 -0
  95. data/app/views/alchemy_crm/admin/deliveries/_delivery.html.erb +41 -0
  96. data/app/views/alchemy_crm/admin/deliveries/_form.html.erb +65 -0
  97. data/app/views/alchemy_crm/admin/deliveries/_recipient.html.erb +40 -0
  98. data/app/views/alchemy_crm/admin/deliveries/edit.html.erb +1 -0
  99. data/app/views/alchemy_crm/admin/deliveries/index.html.erb +29 -0
  100. data/app/views/alchemy_crm/admin/deliveries/new.html.erb +1 -0
  101. data/app/views/alchemy_crm/admin/deliveries/show.html.erb +66 -0
  102. data/app/views/alchemy_crm/admin/elements/link.rjs +4 -0
  103. data/app/views/alchemy_crm/admin/elements/teasables.html.erb +57 -0
  104. data/app/views/alchemy_crm/admin/mailings/_form.html.erb +43 -0
  105. data/app/views/alchemy_crm/admin/mailings/_mailing.html.erb +75 -0
  106. data/app/views/alchemy_crm/admin/mailings/edit.html.erb +1 -0
  107. data/app/views/alchemy_crm/admin/mailings/edit_content.html.erb +194 -0
  108. data/app/views/alchemy_crm/admin/mailings/import_result.html.erb +37 -0
  109. data/app/views/alchemy_crm/admin/mailings/index.html.erb +39 -0
  110. data/app/views/alchemy_crm/admin/mailings/new.html.erb +1 -0
  111. data/app/views/alchemy_crm/admin/mailings/show.html.erb +1 -0
  112. data/app/views/alchemy_crm/admin/mailings/show.text.erb +1 -0
  113. data/app/views/alchemy_crm/admin/newsletters/_form.html.erb +54 -0
  114. data/app/views/alchemy_crm/admin/newsletters/_newsletter.html.erb +42 -0
  115. data/app/views/alchemy_crm/admin/newsletters/_tag_with_radiobutton.html.erb +7 -0
  116. data/app/views/alchemy_crm/admin/newsletters/destroy.rjs +1 -0
  117. data/app/views/alchemy_crm/admin/newsletters/edit.html.erb +1 -0
  118. data/app/views/alchemy_crm/admin/newsletters/index.html.erb +42 -0
  119. data/app/views/alchemy_crm/admin/newsletters/new.html.erb +10 -0
  120. data/app/views/alchemy_crm/admin/tags/_radio_tag.html.erb +6 -0
  121. data/app/views/alchemy_crm/admin/tags/_tag.html.erb +32 -0
  122. data/app/views/alchemy_crm/admin/tags/edit.html.erb +40 -0
  123. data/app/views/alchemy_crm/admin/tags/index.html.erb +35 -0
  124. data/app/views/alchemy_crm/admin/tags/new.html.erb +16 -0
  125. data/app/views/alchemy_crm/contacts_mailer/signout_mail.html.erb +8 -0
  126. data/app/views/alchemy_crm/contacts_mailer/signout_mail.text.erb +4 -0
  127. data/app/views/alchemy_crm/contacts_mailer/signup_mail.html.erb +11 -0
  128. data/app/views/alchemy_crm/contacts_mailer/signup_mail.text.erb +4 -0
  129. data/app/views/alchemy_crm/mailings/show.html.erb +1 -0
  130. data/app/views/alchemy_crm/mailings_mailer/verification_mail.html.erb +11 -0
  131. data/app/views/alchemy_crm/subscriptions/index.html.erb +23 -0
  132. data/app/views/alchemy_crm/subscriptions_mailer/overview_mail.html.erb +27 -0
  133. data/app/views/alchemy_crm/subscriptions_mailer/overview_mail.text.erb +19 -0
  134. data/app/views/csv_magic/import_errors.html.erb +44 -0
  135. data/app/views/csv_magic/mapper.html.erb +62 -0
  136. data/app/views/layouts/alchemy_crm/mailings.html.erb +212 -0
  137. data/app/views/layouts/alchemy_crm/mailings.text.erb +3 -0
  138. data/config/alchemy_crm.config.yml +21 -0
  139. data/config/authorization_rules.rb +37 -0
  140. data/config/initializers/assets.rb +4 -0
  141. data/config/initializers/delayed_job.rb +6 -0
  142. data/config/locales/alchemy.de.yml +55 -0
  143. data/config/locales/alchemy.en.yml +52 -0
  144. data/config/locales/alchemy_crm.de.yml +196 -0
  145. data/config/locales/alchemy_crm.en.yml +53 -0
  146. data/config/locales/localized_country_select.de.rb +268 -0
  147. data/config/locales/localized_country_select.en.rb +282 -0
  148. data/config/module_definition.yml +30 -0
  149. data/config/routes.rb +71 -0
  150. data/db/migrate/20100708220434_create_mailings.rb +19 -0
  151. data/db/migrate/20100708220534_create_contacts.rb +27 -0
  152. data/db/migrate/20100708220812_create_contact_groups.rb +13 -0
  153. data/db/migrate/20100708221009_create_newsletters.rb +15 -0
  154. data/db/migrate/20100708221319_create_sent_mailings.rb +15 -0
  155. data/db/migrate/20100708221529_create_recipients.rb +21 -0
  156. data/db/migrate/20100708222620_acts_as_taggable_migration.rb +28 -0
  157. data/db/migrate/20100708223324_create_contact_group_filters.rb +17 -0
  158. data/db/migrate/20100708223727_create_newsletter_subscriptions.rb +14 -0
  159. data/db/migrate/20100708223922_create_essence_element_teasers.rb +15 -0
  160. data/db/migrate/20100714172207_create_contact_groups_newsletters.rb +12 -0
  161. data/db/migrate/20100927152601_create_reactions.rb +14 -0
  162. data/db/migrate/20111028091722_acts_as_taggable_on_migration.rb +15 -0
  163. data/db/migrate/20111028200710_add_counter_cache_columns_for_tags.rb +11 -0
  164. data/db/migrate/20111030204931_create_delayed_jobs.rb +21 -0
  165. data/db/migrate/20111104105854_add_deliver_at_to_sent_mailings.rb +9 -0
  166. data/db/migrate/20111104125306_remove_sent_mailing_pdf_id_from_sent_mailings.rb +9 -0
  167. data/db/migrate/20111104131912_add_delivered_at_to_sent_mailings.rb +9 -0
  168. data/db/migrate/20111124145923_namespace_alchemy_crm_models.rb +17 -0
  169. data/db/migrate/20120306115555_add_disabled_to_alchemy_crm_contacts.rb +5 -0
  170. data/db/migrate/20120307160721_rename_alchemy_crm_recipients_sent_mailing_id.rb +5 -0
  171. data/db/migrate/20120315113522_add_url_to_alchemy_crm_reactions.rb +5 -0
  172. data/db/migrate/20120316161333_add_sha1_and_salt_to_alchemy_crm_recipients.rb +6 -0
  173. data/db/migrate/20120320103412_add_indexes_to_alchemy_crm_sha1_columns.rb +7 -0
  174. data/db/migrate/20120321105455_add_queue_to_delayed_jobs.rb +9 -0
  175. data/db/migrate/20120327174301_add_more_indexes_to_alchemy_crm_tables.rb +12 -0
  176. data/lib/alchemy_crm.rb +18 -0
  177. data/lib/alchemy_crm/bounced_delivery.rb +46 -0
  178. data/lib/alchemy_crm/config.rb +28 -0
  179. data/lib/alchemy_crm/engine.rb +40 -0
  180. data/lib/alchemy_crm/i18n_helpers.rb +35 -0
  181. data/lib/alchemy_crm/newsletter_layout.rb +40 -0
  182. data/lib/alchemy_crm/seeder.rb +30 -0
  183. data/lib/alchemy_crm/version.rb +3 -0
  184. data/lib/generators/alchemy_crm/scaffold/files/_standard.html.erb +1 -0
  185. data/lib/generators/alchemy_crm/scaffold/files/_standard.text.erb +1 -0
  186. data/lib/generators/alchemy_crm/scaffold/files/elements.yml +51 -0
  187. data/lib/generators/alchemy_crm/scaffold/files/newsletter_layouts.yml +3 -0
  188. data/lib/generators/alchemy_crm/scaffold/files/newsletters.html.erb +22 -0
  189. data/lib/generators/alchemy_crm/scaffold/files/newsletters.text.erb +4 -0
  190. data/lib/generators/alchemy_crm/scaffold/files/page_layouts.yml +19 -0
  191. data/lib/generators/alchemy_crm/scaffold/scaffold_generator.rb +26 -0
  192. data/lib/tasks/alchemy_crm.rake +28 -0
  193. data/lib/tasks/routes.rake +29 -0
  194. data/rails/init.rb +1 -0
  195. data/recipes/alchemy-mailings-capistrano-tasks.rb +12 -0
  196. data/spec/alchemy_mailings_spec.rb +7 -0
  197. data/spec/config_spec.rb +28 -0
  198. data/spec/controllers/alchemy_crm/contacts_controller_spec.rb +210 -0
  199. data/spec/controllers/alchemy_crm/mailings_controller_spec.rb +89 -0
  200. data/spec/controllers/alchemy_crm/recipients_controller_spec.rb +155 -0
  201. data/spec/dummy/Rakefile +7 -0
  202. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  203. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  204. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  205. data/spec/dummy/config.ru +4 -0
  206. data/spec/dummy/config/alchemy/config.yml +142 -0
  207. data/spec/dummy/config/alchemy/elements.yml +79 -0
  208. data/spec/dummy/config/alchemy/newsletter_layouts.yml +4 -0
  209. data/spec/dummy/config/alchemy/page_layouts.yml +55 -0
  210. data/spec/dummy/config/application.rb +43 -0
  211. data/spec/dummy/config/boot.rb +10 -0
  212. data/spec/dummy/config/database.yml +22 -0
  213. data/spec/dummy/config/environment.rb +5 -0
  214. data/spec/dummy/config/environments/development.rb +30 -0
  215. data/spec/dummy/config/environments/production.rb +60 -0
  216. data/spec/dummy/config/environments/test.rb +39 -0
  217. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  218. data/spec/dummy/config/initializers/inflections.rb +10 -0
  219. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  220. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  221. data/spec/dummy/config/initializers/session_store.rb +8 -0
  222. data/spec/dummy/config/locales/en.yml +5 -0
  223. data/spec/dummy/config/routes.rb +4 -0
  224. data/spec/dummy/db/schema.rb +452 -0
  225. data/spec/dummy/public/404.html +26 -0
  226. data/spec/dummy/public/422.html +26 -0
  227. data/spec/dummy/public/500.html +26 -0
  228. data/spec/dummy/public/favicon.ico +0 -0
  229. data/spec/dummy/script/rails +6 -0
  230. data/spec/helpers/alchemy_crm/mailings_helper_spec.rb +169 -0
  231. data/spec/mailers/alchemy_crm/contacts_mailer_spec.rb +88 -0
  232. data/spec/mailers/alchemy_crm/mailings_mailer_spec.rb +42 -0
  233. data/spec/models/alchemy_crm/contact_group_spec.rb +41 -0
  234. data/spec/models/alchemy_crm/contact_spec.rb +108 -0
  235. data/spec/models/alchemy_crm/delivery_spec.rb +55 -0
  236. data/spec/models/alchemy_crm/mailing_spec.rb +63 -0
  237. data/spec/models/alchemy_crm/newsletter_spec.rb +45 -0
  238. data/spec/spec_helper.rb +58 -0
  239. data/vendor/assets/javascripts/autocomplete-rails.js +16 -0
  240. metadata +438 -0
@@ -0,0 +1,19 @@
1
+ module Alchemy
2
+ class EssenceElementTeaser < ActiveRecord::Base
3
+
4
+ acts_as_essence(
5
+ :ingredient_column => :url,
6
+ :preview_text_method => :url
7
+ )
8
+
9
+ # Saves the content from params
10
+ def save_ingredient(params, options = {})
11
+ return true if params.blank?
12
+ self.url = params["url"]
13
+ self.title = params["title"]
14
+ self.text = params["text"]
15
+ self.save!
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,264 @@
1
+ # encoding: UTF-8
2
+
3
+ module AlchemyCrm
4
+ class Contact < ActiveRecord::Base
5
+
6
+ acts_as_taggable
7
+
8
+ attr_accessible(
9
+ :salutation,
10
+ :title,
11
+ :firstname,
12
+ :lastname,
13
+ :email,
14
+ :phone,
15
+ :mobile,
16
+ :address,
17
+ :zip,
18
+ :city,
19
+ :organisation,
20
+ :country,
21
+ :subscriptions_attributes
22
+ )
23
+
24
+ attr_accessible(
25
+ :salutation,
26
+ :title,
27
+ :firstname,
28
+ :lastname,
29
+ :email,
30
+ :phone,
31
+ :mobile,
32
+ :address,
33
+ :zip,
34
+ :city,
35
+ :organisation,
36
+ :country,
37
+ :subscriptions_attributes,
38
+ :verified,
39
+ :disabled,
40
+ :tag_list,
41
+ :as => :admin
42
+ )
43
+
44
+ has_many :subscriptions, :dependent => :destroy
45
+ has_many :newsletters, :through => :subscriptions, :uniq => true
46
+ has_many :recipients
47
+
48
+ attr_accessor :skip_validation
49
+
50
+ accepts_nested_attributes_for :subscriptions, :allow_destroy => true
51
+
52
+ validates_presence_of :salutation, :unless => proc { skip_validation }
53
+ validates_presence_of :firstname, :unless => proc { skip_validation }
54
+ validates_presence_of :lastname
55
+ validates_presence_of :email
56
+ validates_uniqueness_of :email
57
+ validates_format_of :email, :with => ::Authlogic::Regex.email, :if => proc { errors[:email].blank? }
58
+
59
+ before_save :update_sha1, :if => proc { email_sha1.blank? || email_changed? }
60
+
61
+ scope :verified, where(:verified => true)
62
+ scope :disabled, where(:disabled => true)
63
+ scope :enabled, where(:disabled => false)
64
+ scope :available, verified.enabled
65
+
66
+ COLUMN_NAMES = [
67
+ [::I18n.t(:title, :scope => 'activerecord.attributes.alchemy_crm/contact', :default => 'Title'), "title"],
68
+ [::I18n.t(:salutation, :scope => 'activerecord.attributes.alchemy_crm/contact', :default => 'Salutation'), "salutation"],
69
+ [::I18n.t(:firstname, :scope => 'activerecord.attributes.alchemy_crm/contact', :default => 'Firstname'), "firstname"],
70
+ [::I18n.t(:lastname, :scope => 'activerecord.attributes.alchemy_crm/contact', :default => 'Lastname'), "lastname"],
71
+ [::I18n.t(:address, :scope => 'activerecord.attributes.alchemy_crm/contact', :default => 'Address'), "address"],
72
+ [::I18n.t(:zip, :scope => 'activerecord.attributes.alchemy_crm/contact', :default => 'Zipcode'), "zip"],
73
+ [::I18n.t(:city, :scope => 'activerecord.attributes.alchemy_crm/contact', :default => 'City'), "city"],
74
+ [::I18n.t(:country, :scope => 'activerecord.attributes.alchemy_crm/contact', :default => 'Country'), "country"],
75
+ [::I18n.t(:organisation, :scope => 'activerecord.attributes.alchemy_crm/contact', :default => 'Organisation'), "organisation"]
76
+ ]
77
+
78
+ INTERPOLATION_NAME_METHODS = %w(fullname name_with_title firstname lastname name email)
79
+
80
+ def disable!
81
+ update_attributes({
82
+ :verified => false,
83
+ :disabled => true
84
+ }, :as => :admin)
85
+ subscriptions.destroy_all
86
+ end
87
+
88
+ # Returns a full name
89
+ # Salutation + Title + Firstname + Lastname
90
+ def fullname
91
+ if lastname.present? || firstname.present?
92
+ "#{translated_salutation} #{name_with_title}".squeeze(" ")
93
+ else
94
+ name
95
+ end
96
+ end
97
+
98
+ # Translated salutation
99
+ #
100
+ # Translate the saluations in your +config/LOCALE.yml+
101
+ #
102
+ # alchemy_crm:
103
+ # salutations:
104
+ # mr:
105
+ # ms:
106
+ #
107
+ def translated_salutation
108
+ ::I18n.t(salutation, :scope => [:alchemy_crm, :salutations], :default => salutation.to_s.capitalize)
109
+ end
110
+
111
+ # Returns the name and title
112
+ # Title + Firstname + Lastname
113
+ def name_with_title
114
+ "#{title} #{name}".squeeze(" ")
115
+ end
116
+
117
+ # Returns the name ot email, if no name is present
118
+ # Firstname + Lastname, or email
119
+ def name
120
+ if lastname.present? || firstname.present?
121
+ "#{firstname} #{lastname}".squeeze(" ")
122
+ else
123
+ email
124
+ end
125
+ end
126
+
127
+ # Uses the +config['name_interpolation_method']+ to return the value for emails and forms %{name} interpolations.
128
+ def interpolation_name_value
129
+ name_interpolation_method = Config.get(:name_interpolation_method)
130
+ if name_interpolation_method.present? && INTERPOLATION_NAME_METHODS.include?(name_interpolation_method.to_s) && self.respond_to?(name_interpolation_method.to_sym)
131
+ self.send(name_interpolation_method.to_sym)
132
+ else
133
+ fullname
134
+ end
135
+ end
136
+
137
+ def self.fake
138
+ fake = new(
139
+ :salutation => nil,
140
+ :title => ::I18n.t(:title, :scope => 'alchemy_crm.fake_contact_attributes', :default => 'Dr.'),
141
+ :firstname => ::I18n.t(:firstname, :scope => 'alchemy_crm.fake_contact_attributes', :default => 'Jon'),
142
+ :lastname => ::I18n.t(:lastname, :scope => 'alchemy_crm.fake_contact_attributes', :default => 'Doe'),
143
+ :email => ::I18n.t(:email, :scope => 'alchemy_crm.fake_contact_attributes', :default => 'jon@doe.com'),
144
+ :phone => ::I18n.t(:phone, :scope => 'alchemy_crm.fake_contact_attributes', :default => '1-1234567'),
145
+ :mobile => ::I18n.t(:mobile, :scope => 'alchemy_crm.fake_contact_attributes', :default => '123-456789'),
146
+ :address => ::I18n.t(:address, :scope => 'alchemy_crm.fake_contact_attributes', :default => 'Street 1'),
147
+ :zip => ::I18n.t(:zip, :scope => 'alchemy_crm.fake_contact_attributes', :default => '10000'),
148
+ :city => ::I18n.t(:city, :scope => 'alchemy_crm.fake_contact_attributes', :default => 'City'),
149
+ :organisation => ::I18n.t(:organisation, :scope => 'alchemy_crm.fake_contact_attributes', :default => 'Company inc.'),
150
+ :country => ::I18n.t(:country, :scope => 'alchemy_crm.fake_contact_attributes', :default => 'US')
151
+ )
152
+ fake.readonly!
153
+ fake
154
+ end
155
+
156
+ def self.replace_tag(old_tag, new_tag)
157
+ self.tagged_with(old_tag).each do |contact|
158
+ contact.tags.delete(old_tag)
159
+ contact.tags << new_tag
160
+ contact.save
161
+ end
162
+ end
163
+
164
+ def self.find_by_query(query, options, paginate)
165
+ column_names = Contact.public_column_names
166
+ search_string = (column_names.join(" LIKE '%#{query}%' OR ") + " LIKE '%#{query}%'")
167
+ contacts = where(search_string)
168
+ if paginate
169
+ contacts.paginate(options)
170
+ else
171
+ contacts
172
+ end
173
+ end
174
+
175
+ #TODO: Zu den selbstgewählten Mailingformaten müssten noch alle durch den Backend User gewählten Formate mit angezeigt werden, oder?
176
+ def all_newsletters
177
+ self.newsletters
178
+ end
179
+
180
+ def self.public_column_names
181
+ Contact.column_names - ["created_at", "updated_at", "email_salt", "verified", "email_sha1", "id"]
182
+ end
183
+
184
+ def self.filterable_column_names
185
+ COLUMN_NAMES
186
+ end
187
+
188
+ def self.find_or_create(data)
189
+ if (contact = Contact.find_by_email(data[:email])).nil?
190
+ contact = Contact.create(data)
191
+ end
192
+ contact
193
+ end
194
+
195
+ def all_subscriptions_verified?
196
+ self.subscriptions.inject(true){|acc, s| acc = s.verified? && acc; acc}
197
+ end
198
+
199
+ def self.new_from_recipient(recipient)
200
+ raise "No recipient found!" if recipient.nil?
201
+ contact = new(:email => recipient.email, :lastname => recipient.email)
202
+ contact.readonly!
203
+ contact
204
+ end
205
+
206
+ def self.new_from_vcard(vcard, verified)
207
+ raise "No vcard found!" if vcard.nil?
208
+ raise ::I18n.t(:imported_contacts_not_verified, :scope => :alchemy_crm) if !verified
209
+ contacts = []
210
+ ::Vpim::Vcard.decode(vcard).each do |card|
211
+ remapped_attributes = {
212
+ :organisation => card.org.blank? ? nil : card.org.first,
213
+ :lastname => card.name.blank? ? nil : card.name.family,
214
+ :firstname => card.name.blank? ? nil : card.name.given,
215
+ :title => card.name.blank? ? nil : card.name.prefix,
216
+ :address => card.address.blank? ? nil : card.address.street,
217
+ :city => card.address.blank? ? nil : card.address.locality,
218
+ :country => card.address.blank? ? nil : card.address.country,
219
+ :zip => card.address.blank? ? nil : card.address.postalcode,
220
+ :email => card.email.to_s,
221
+ :phone => card.telephone ? card.telephone.location.include?("cell") ? nil : card.telephone.to_s : nil,
222
+ :mobile => card.telephones.detect { |t| t.location.include?("cell") }.to_s,
223
+ :verified => true
224
+ }
225
+ contact = Contact.new(remapped_attributes, :as => :admin)
226
+ contact.skip_validation = true
227
+ contact.save
228
+ contacts << contact
229
+ end
230
+ contacts
231
+ end
232
+
233
+ def to_vcard
234
+ card = Vpim::Vcard::Maker.make2 do |maker|
235
+ maker.add_name do |name|
236
+ name.prefix = title unless title.blank?
237
+ name.given = firstname unless firstname.blank?
238
+ name.family = lastname unless lastname.blank?
239
+ end
240
+ maker.add_tel(phone) { |t| t.location = 'work' } unless phone.blank?
241
+ maker.add_tel(mobile) { |t| t.location = 'cell' } unless mobile.blank?
242
+ maker.add_email(email) { |e| e.location = 'work' } unless email.blank?
243
+ maker.add_addr do |addr|
244
+ addr.street = address
245
+ addr.postalcode = zip
246
+ addr.locality = city
247
+ addr.country = country
248
+ end
249
+ maker.org = organisation
250
+ end
251
+ vcf = File.new(Rails.root.to_s + "/tmp/#{fullname}.vcf", "w")
252
+ vcf.write(card.encode)
253
+ vcf.close
254
+ end
255
+
256
+ private
257
+
258
+ def update_sha1
259
+ salt = email_salt || [Array.new(6){rand(256).chr}.join].pack("m")[0..7]
260
+ self.email_salt, self.email_sha1 = salt, Digest::SHA1.hexdigest(email + salt)
261
+ end
262
+
263
+ end
264
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+ module AlchemyCrm
3
+ class ContactGroup < ActiveRecord::Base
4
+
5
+ acts_as_taggable_on :contact_tags
6
+
7
+ has_many :filters, :dependent => :destroy, :class_name => "AlchemyCrm::ContactGroupFilter"
8
+
9
+ validates_presence_of :name
10
+
11
+ accepts_nested_attributes_for :filters, :allow_destroy => true
12
+
13
+ def contacts
14
+ Contact.tagged_with(self.contact_tags, :any => true).where(filters_sql_string)
15
+ end
16
+
17
+ def filters_sql_string
18
+ filters.map(&:sql_string).join(' AND ')
19
+ end
20
+
21
+ def humanized_name
22
+ "#{self.name} (#{self.contacts.length})"
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+ module AlchemyCrm
3
+ class ContactGroupFilter < ActiveRecord::Base
4
+
5
+ belongs_to :contact_group
6
+
7
+ OPERATORS = [
8
+ [::I18n.t(:like, :scope => 'alchemy_crm.contact_group_filter_operators', :default => 'Contains'), "LIKE"],
9
+ [::I18n.t(:not_like, :scope => 'alchemy_crm.contact_group_filter_operators', :default => 'Contains not'), "NOT LIKE"],
10
+ [::I18n.t(:equals, :scope => 'alchemy_crm.contact_group_filter_operators', :default => 'Equals'), "="],
11
+ [::I18n.t(:equals_not, :scope => 'alchemy_crm.contact_group_filter_operators', :default => 'Equals not'), "!="]
12
+ ]
13
+
14
+ def sql_string
15
+ return "" if column.blank? || operator.blank? || prepared_value.blank?
16
+ "`#{Contact.table_name}`.`#{column}` #{operator} '#{prepared_value}'"
17
+ end
18
+
19
+ private
20
+
21
+ def prepared_value
22
+ operator =~ /LIKE/ ? "%#{value}%" : "#{value}"
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: UTF-8
2
+ module AlchemyCrm
3
+ class Delivery < ActiveRecord::Base
4
+
5
+ attr_accessor :chunk_delay
6
+
7
+ belongs_to :mailing
8
+ has_many :recipients, :dependent => :destroy
9
+
10
+ after_create :recipients_from_mailing_contacts
11
+
12
+ scope :delivered, where("`alchemy_crm_deliveries`.`delivered_at` IS NOT NULL")
13
+ scope :pending, where("`alchemy_crm_deliveries`.`delivered_at` IS NULL")
14
+
15
+ def delivered?
16
+ !self.delivered_at.nil?
17
+ end
18
+
19
+ # Sending mails in chunks.
20
+ def send_chunks(options={})
21
+ raise "No Mailing Given" if mailing.blank?
22
+ @chunk_delay = 0
23
+ recipients.each_slice(self.class.settings(:send_mails_in_chunks_of)) do |recipients_chunk|
24
+ send_mail_chunk(recipients_chunk, options)
25
+ @chunk_delay += 1
26
+ end
27
+ update_attribute(:delivered_at, Time.now)
28
+ end
29
+ handle_asynchronously :send_chunks, :run_at => proc { |m| m.deliver_at || Time.now }
30
+
31
+ # Send the mail chunk via delayed_job and waiting some time before next is enqueued
32
+ def send_mail_chunk(recipients_chunk, options)
33
+ recipients_chunk.each do |recipient|
34
+ mail = MailingsMailer.build(mailing, recipient, options).deliver
35
+ recipient.update_attribute(:message_id, mail.message_id)
36
+ end
37
+ end
38
+ handle_asynchronously :send_mail_chunk, :run_at => proc { |m| (m.chunk_delay * m.class.settings(:send_mail_chunks_every)).minutes.from_now }
39
+
40
+ def self.settings(name)
41
+ AlchemyCrm::Config.get(name)
42
+ end
43
+
44
+ private
45
+
46
+ def recipients_from_mailing_contacts
47
+ raise "No Mailing Given" if mailing.blank?
48
+ mailing.contacts_not_having_email_yet.each do |contact|
49
+ recipients << Recipient.create!(
50
+ :email => contact.email,
51
+ :contact => contact
52
+ )
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,28 @@
1
+ # Extending Alchemys Element model
2
+ Alchemy::Element.class_eval do
3
+
4
+ def update_from_element(source, url)
5
+ self.contents.each do |content|
6
+ source_content = source.contents.find_by_name(content.name)
7
+ if !source_content.blank? && source_content.essence_type == content.essence_type
8
+ case content.essence_type
9
+ when "Alchemy::EssenceText"
10
+ content.essence.body = source_content.essence.body
11
+ when "Alchemy::EssenceRichtext"
12
+ content.essence.body = source_content.essence.body
13
+ when "Alchemy::EssencePicture"
14
+ content.essence.picture_id = source_content.essence.picture_id
15
+ when "Alchemy::EssenceAttachment"
16
+ content.essence.attachment_id = source_content.essence.attachment_id
17
+ end
18
+ content.essence.save
19
+ end
20
+ end
21
+ teaser = self.contents.where(:essence_type => "Alchemy::EssenceElementTeaser").first
22
+ if !teaser.essence.blank?
23
+ teaser.essence.url = url
24
+ teaser.essence.save
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,93 @@
1
+ # encoding: UTF-8
2
+ module AlchemyCrm
3
+ class Mailing < ActiveRecord::Base
4
+
5
+ MAILING_PAGE_LAYOUT_PREFIX = "newsletter_layout_"
6
+
7
+ belongs_to :page, :dependent => :destroy, :class_name => 'Alchemy::Page'
8
+ has_many :deliveries, :dependent => :destroy
9
+ has_many :recipients, :through => :deliveries
10
+ belongs_to :newsletter
11
+
12
+ validates_presence_of :name
13
+ validates_presence_of :newsletter_id, :on => :create
14
+
15
+ before_create :set_sha1
16
+ after_create :create_page
17
+
18
+ # Returns all contacts found via newsletter.
19
+ def newsletter_contacts
20
+ return [] if newsletter.blank?
21
+ newsletter.contacts
22
+ end
23
+
24
+ # Returns all contacts found via newsletter contacts and additional email addresses.
25
+ def contacts
26
+ (newsletter_contacts + contacts_from_additional_email_addresses).uniq
27
+ end
28
+
29
+ def contacts_count
30
+ return 0 if contacts.blank?
31
+ contacts.count
32
+ end
33
+
34
+ def contacts_not_having_email_yet
35
+ return contacts if recipients.empty?
36
+ contacts.select { |c| !recipients.collect(&:email).include?(c.email) }
37
+ end
38
+
39
+ # Returns a list of all email addresses for contacts that have not got any email yet.
40
+ def emails
41
+ contacts_not_having_email_yet.collect(&:email)
42
+ end
43
+
44
+ # Return a list of email addresses from additional_email_addresses field.
45
+ def additional_emails
46
+ return [] if additional_email_addresses.blank?
47
+ additional_email_addresses.gsub(/\s/,'').split(',').uniq
48
+ end
49
+
50
+ # Returns a list of contacts found or initialized by email address from additional_email_addresses field.
51
+ def contacts_from_additional_email_addresses
52
+ additional_emails.collect{ |email| Contact.find_or_initialize_by_email(:email => email) }
53
+ end
54
+
55
+ # Makes a copy of another mailing.
56
+ def self.copy(id)
57
+ source = self.find(id)
58
+ new(source.attributes.merge(
59
+ "name" => "#{source.name} (#{::I18n.t(:copy, :scope => :alchemy_crm, :default => 'Copy')})"
60
+ ).except("id", "updated_at", "created_at", "sha1", "salt", "page_id"))
61
+ end
62
+
63
+ def next_pending_delivery
64
+ deliveries.pending.first
65
+ end
66
+
67
+ private
68
+
69
+ def create_page
70
+ mailing_root = Alchemy::Page.find_by_name('Alchemy CRM Rootpage')
71
+ raise "Alchemy CRM Rootpage not found. Did you seed the database?" if mailing_root.blank?
72
+ mailing_page = Alchemy::Page.new(
73
+ :name => "Mailing #{self.name}",
74
+ :sitemap => false,
75
+ :page_layout => newsletter.layout,
76
+ :language => Alchemy::Language.get_default,
77
+ :parent_id => mailing_root.id
78
+ )
79
+ if mailing_page.save
80
+ self.page = mailing_page
81
+ save
82
+ else
83
+ raise "Error while creating Mailingpage: #{mailing_page.errors.full_messages}"
84
+ end
85
+ end
86
+
87
+ def set_sha1
88
+ self.salt = [Array.new(6){rand(256).chr}.join].pack("m")[0..7]
89
+ self.sha1 = Digest::SHA1.hexdigest(Time.now.to_i.to_s + salt)
90
+ end
91
+
92
+ end
93
+ end