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,35 @@
1
+ module AlchemyCrm
2
+ module I18nHelpers
3
+
4
+ def self.included(controller)
5
+ controller.send(:helper_method, [:i18n_t, :translate_model_attribute, :alchemy_crm_t])
6
+ end
7
+
8
+ # This is a proxy to the ::I18n.t method with an alchem_crm scope.
9
+ #
10
+ # We can't use +t+ in views, because Alchemy overrides it and scopes everything into the +alchemy+ namespace.
11
+ #
12
+ # === NOTE:
13
+ #
14
+ # The keys are scoped into +alchemy_crm+ namespace.
15
+ # Even if you pass a scope this is scoped under +alchemy_crm+.
16
+ #
17
+ def alchemy_crm_t(key, options={})
18
+ scope = options[:scope].blank? ? 'alchemy_crm' : "alchemy_crm.#{options.delete(:scope)}"
19
+ ::I18n.t(key, {:scope => scope, :default => key.to_s.humanize}.merge(options))
20
+ end
21
+
22
+ # This is a proxy to the ::I18n.t method without any scope.
23
+ #
24
+ # We can't use +t+ in views, because Alchemy overrides it and scopes everything into the +alchemy+ namespace.
25
+ #
26
+ def i18n_t(key, options={})
27
+ ::I18n.t(key, options.merge(:default => key.to_s.humanize))
28
+ end
29
+
30
+ def translate_model_attribute(model, key)
31
+ "alchemy_crm/#{model.to_s.underscore}".classify.constantize.human_attribute_name(key)
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,40 @@
1
+ module AlchemyCrm
2
+ class NewsletterLayout
3
+
4
+ def self.get_layouts_for_select()
5
+ all
6
+ @@newsletter_layouts.map do |l|
7
+ [display_name_for(l["name"]), l["name"]]
8
+ end
9
+ end
10
+
11
+ def self.all
12
+ @@newsletter_layouts ||= Alchemy::PageLayout.get_all_by_attributes(:newsletter => true)
13
+ end
14
+
15
+ def self.get(name)
16
+ all
17
+ @@newsletter_layouts.detect { |l| l['name'] == name.to_s }
18
+ end
19
+
20
+ def self.display_name_for(name)
21
+ Alchemy::I18n.t(name, :scope => :page_layout_names, :default => name.to_s.camelcase)
22
+ end
23
+
24
+ end
25
+ end
26
+
27
+ Alchemy::PageLayout.class_eval do
28
+
29
+ def self.selectable_layouts(language_id, layoutpage = false)
30
+ class_variable_get('@@definitions').select do |layout|
31
+ used = layout["unique"] && has_a_page_this_layout?(layout["name"], language_id)
32
+ if layoutpage
33
+ layout["layoutpage"] == true && !used && layout["newsletter"] != true
34
+ else
35
+ layout["layoutpage"] != true && !used && layout["newsletter"] != true
36
+ end
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,30 @@
1
+ module AlchemyCrm
2
+ class Seeder
3
+
4
+ def self.seed!
5
+ puts "Seeding database for Alchemy Mailing Module"
6
+ root = Alchemy::Page.root
7
+ lang = Alchemy::Language.get_default
8
+ if root.nil? || lang.nil?
9
+ puts "== Aborting! Not all dependencies found. Did you ran the Alchemy seeder?"
10
+ else
11
+ mailing_root = Alchemy::Page.find_or_create_by_name(
12
+ :name => "Alchemy CRM Rootpage",
13
+ :page_layout => 'alchemy_crm_rootpage',
14
+ :do_not_autogenerate => true,
15
+ :do_not_sweep => true,
16
+ :language => lang,
17
+ :parent_id => root.id
18
+ )
19
+ if mailing_root.new_record?
20
+ mailing_root.save!
21
+ mailing_root.move_to_child_of root
22
+ puts "== Success! Mailing root page created"
23
+ else
24
+ puts "== Skipping! Already created mailing root page."
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module AlchemyCrm
2
+ VERSION = "2.0.0"
3
+ end
@@ -0,0 +1 @@
1
+ <%= render_elements %>
@@ -0,0 +1 @@
1
+ <%= render_elements(:render_format => 'text') %>
@@ -0,0 +1,51 @@
1
+ # These elements are added by the Alchemy CRM module.
2
+
3
+ - name: newsletter_signup_form
4
+ unique: true
5
+ contents:
6
+ - name: choose_newsletter_text
7
+ type: EssenceRichtext
8
+ - name: signed_up_text
9
+ type: EssenceRichtext
10
+ validate: [presence]
11
+ - name: verified_text
12
+ type: EssenceRichtext
13
+ validate: [presence]
14
+
15
+ - name: newsletter_signup_mail
16
+ unique: true
17
+ contents:
18
+ - name: mail_from
19
+ type: EssenceText
20
+ validate: [presence]
21
+ - name: subject
22
+ type: EssenceText
23
+ validate: [presence]
24
+ take_me_for_preview: true
25
+ - name: text
26
+ type: EssenceRichtext
27
+ validate: [presence]
28
+
29
+ - name: newsletter_signout_form
30
+ unique: true
31
+ contents:
32
+ - name: signed_out_text
33
+ type: EssenceRichtext
34
+ validate: [presence]
35
+ - name: disabled_text
36
+ type: EssenceRichtext
37
+ validate: [presence]
38
+
39
+ - name: newsletter_signout_mail
40
+ unique: true
41
+ contents:
42
+ - name: mail_from
43
+ type: EssenceText
44
+ validate: [presence]
45
+ - name: subject
46
+ type: EssenceText
47
+ validate: [presence]
48
+ take_me_for_preview: true
49
+ - name: text
50
+ type: EssenceRichtext
51
+ validate: [presence]
@@ -0,0 +1,3 @@
1
+ # Newsletter page layouts
2
+ - name: standard
3
+ elements: []
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6
+ <title>Newsletter</title>
7
+ <style type="text/css" media="screen">
8
+ /* Put styles styles here */
9
+ </style>
10
+ </head>
11
+ <body alink="#000" vlink="#000" link="#000" bgcolor="#fff">
12
+ <%# This image is used for tracking %>
13
+ <%- unless @preview_mode -%>
14
+ <img src="<%= recipient_reads_url(:id => @recipient.id || 1, :host => @server) %>" style="width: 0; height: 0; display: none" width="0" height="0"/>
15
+ <%- end -%>
16
+ <%# end tracking image %>
17
+
18
+ <%= render_newsletter_layout %>
19
+
20
+ <%= alchemy_preview_mode_code %>
21
+ </body>
22
+ </html>
@@ -0,0 +1,4 @@
1
+ Newsletter
2
+ ==========
3
+
4
+ <%= render_newsletter_layout(:render_format => 'text') %>
@@ -0,0 +1,19 @@
1
+ # These page layouts are added by the Alchemy CRM module.
2
+
3
+ - name: newsletter_signup
4
+ unique: true
5
+ cache: false
6
+ elements: [newsletter_signup_form]
7
+ autogenerate: [newsletter_signup_form]
8
+
9
+ - name: newsletter_signout
10
+ unique: true
11
+ cache: false
12
+ elements: [newsletter_signout_form]
13
+ autogenerate: [newsletter_signout_form]
14
+
15
+ - name: newsletter_mails
16
+ unique: true
17
+ layoutpage: true
18
+ elements: [newsletter_signup_mail, newsletter_signout_mail]
19
+ autogenerate: [newsletter_signup_mail, newsletter_signout_mail]
@@ -0,0 +1,26 @@
1
+ require 'rails'
2
+
3
+ module AlchemyCrm
4
+ module Generators
5
+ class ScaffoldGenerator < ::Rails::Generators::Base
6
+ desc "This generator generates the Alchemy CRM scaffold."
7
+ source_root File.expand_path('files', File.dirname(__FILE__))
8
+
9
+ def create_newsletter_scaffold
10
+
11
+ empty_directory "#{Rails.root}/app/views/alchemy/newsletter_layouts"
12
+
13
+ copy_file "_standard.html.erb", "#{Rails.root}/app/views/alchemy/newsletter_layouts/_standard.html.erb"
14
+ copy_file "_standard.text.erb", "#{Rails.root}/app/views/alchemy/newsletter_layouts/_standard.text.erb"
15
+ copy_file "newsletters.html.erb", "#{Rails.root}/app/views/layouts/alchemy/newsletters.html.erb"
16
+ copy_file "newsletters.text.erb", "#{Rails.root}/app/views/layouts/alchemy/newsletters.text.erb"
17
+ copy_file "newsletter_layouts.yml", "#{Rails.root}/config/alchemy/newsletter_layouts.yml"
18
+
19
+ append_file "#{Rails.root}/config/alchemy/elements.yml", YAML.load_file(File.expand_path('files/elements.yml', File.dirname(__FILE__))).to_yaml.gsub(/^-{3}\s{2}/, "\n# These elements are added by the Alchemy CRM module.\n\n")
20
+ append_file "#{Rails.root}/config/alchemy/page_layouts.yml", YAML.load_file(File.expand_path('files/page_layouts.yml', File.dirname(__FILE__))).to_yaml.gsub(/^^-{3}\s{2}/, "\n# These page layouts are added by the Alchemy CRM module.\n\n")
21
+
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ namespace 'alchemy_crm' do
2
+
3
+ desc "Migrates the database, inserts essential data into the database and copies all assets."
4
+ task :prepare do
5
+ Rake::Task['alchemy_crm:install:migrations'].invoke
6
+ Rake::Task['alchemy_crm:seeder:copy'].invoke
7
+ end
8
+
9
+ namespace 'seeder' do
10
+ desc "Copy a line of code into the seeds.rb file"
11
+ task :copy do
12
+ File.open("./db/seeds.rb", "a") do |f|
13
+ f.puts "\n# Seeding Alchemy CRM data"
14
+ f.puts "AlchemyCrm::Seeder.seed!\n"
15
+ end
16
+ end
17
+ end
18
+
19
+ namespace 'elements' do
20
+ desc "Copy all crm elements into app"
21
+ task :copy do
22
+ Dir.glob(File.expand_path('../../app/views/alchemy/elements/*', File.dirname(__FILE__))).each do |file|
23
+ FileUtils.cp(file, Rails.root.join('app/views/alchemy/elements/'))
24
+ end
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,29 @@
1
+ desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
2
+ task :routes => :environment do
3
+ Rails.application.reload_routes!
4
+ all_routes = AlchemyCrm::Engine.routes.routes
5
+
6
+ if ENV['CONTROLLER']
7
+ all_routes = all_routes.select{ |route| route.defaults[:controller] == ENV['CONTROLLER'] }
8
+ end
9
+
10
+ routes = all_routes.collect do |route|
11
+
12
+ reqs = route.requirements.dup
13
+ reqs[:to] = route.app unless route.app.class.name.to_s =~ /^ActionDispatch::Routing/
14
+ reqs = reqs.empty? ? "" : reqs.inspect
15
+
16
+ {:name => route.name.to_s, :verb => route.verb.to_s, :path => route.path, :reqs => reqs}
17
+ end
18
+
19
+ # Skip the route if it's internal info route
20
+ routes.reject! { |r| r[:path] =~ %r{/rails/info/properties|^/assets} }
21
+
22
+ name_width = routes.map{ |r| r[:name].length }.max
23
+ verb_width = routes.map{ |r| r[:verb].length }.max
24
+ path_width = routes.map{ |r| r[:path].length }.max
25
+
26
+ routes.each do |r|
27
+ puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
28
+ end
29
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib/alchemy_crm')
@@ -0,0 +1,12 @@
1
+ namespace 'alchemy_crm' do
2
+
3
+ namespace :assets do
4
+
5
+ desc "Copies all assets from Alchemy-Mailings plugin folder to public folder"
6
+ task :copy do
7
+ run "cd #{current_path} && RAILS_ENV=production #{rake} alchemy_crm:assets:copy:all"
8
+ end
9
+
10
+ end
11
+
12
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe AlchemyCrm do
4
+ it "should be valid" do
5
+ AlchemyCrm.should be_a(Module)
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ module AlchemyCrm
4
+ describe 'Configuration' do
5
+ describe 'file' do
6
+ context 'is present in host application' do
7
+ before(:each) do
8
+ @config_file = Rails.root.join('config', 'alchemy_crm.config.yml')
9
+ File.open(@config_file, 'w') do |file|
10
+ file.puts({'mail_from' => 'jon@doe.com'}.to_yaml)
11
+ end
12
+ end
13
+ it "should load the app config" do
14
+ Config.show.should == {'mail_from' => 'jon@doe.com'}
15
+ end
16
+ after(:each) do
17
+ FileUtils.rm_f @config_file
18
+ end
19
+ end
20
+ context 'is not present in application' do
21
+ it 'should load the default config' do
22
+ default_config = File.join(File.dirname(__FILE__), '..', 'config/alchemy_crm.config.yml')
23
+ Config.show.should == YAML.load_file(default_config)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,210 @@
1
+ require 'spec_helper'
2
+
3
+ module AlchemyCrm
4
+ describe ContactsController do
5
+
6
+ before(:all) do
7
+ Alchemy::PageLayout.add([
8
+ {"name" => 'newsletter_signup', "elements" => ['newsletter_signup_form'], "autogenerate" => ["newsletter_signup_form"]},
9
+ {"name" => 'newsletter_signout', "elements" => ['newsletter_signout_form'], "autogenerate" => ["newsletter_signout_form"]},
10
+ {"name" => 'newsletter_mails', "elements" => ['newsletter_signup_mail', 'newsletter_signout_mail'], "autogenerate" => ["newsletter_signup_mail", "newsletter_signout_mail"]}
11
+ ])
12
+ @language_root = Alchemy::Page.create!(:name => 'Language Root', :page_layout => 'standard', :parent_id => Alchemy::Page.root.id, :language => Alchemy::Language.get_default)
13
+ @signup_page = Alchemy::Page.create!(:name => 'Newsletter Signup', :page_layout => 'newsletter_signup', :parent_id => @language_root.id, :language => @language_root.language, :public => true)
14
+ @signout_page = Alchemy::Page.create!(:name => 'Newsletter Signout', :page_layout => 'newsletter_signout', :parent_id => @language_root.id, :language => @language_root.language, :public => true)
15
+ @mail_page = Alchemy::Page.create!(:name => 'Newsletter Mails', :page_layout => 'newsletter_mails', :parent_id => @language_root.id, :language => @language_root.language)
16
+ @contact_params = {:email => 'jon@doe.com', :firstname => 'Jon', :lastname => 'Doe', :salutation => 'mr'}
17
+ @newsletter = Newsletter.create!(:name => 'Newsletter', :layout => 'standard')
18
+ end
19
+
20
+ describe '#signup' do
21
+
22
+ before(:each) do
23
+ post :signup, {:contact => @contact_params, :use_route => :alchemy_crm}
24
+ end
25
+
26
+ it "should create the contact from params." do
27
+ Contact.find_by_email('jon@doe.com').should_not be_nil
28
+ end
29
+
30
+ it "should deliver the signup mail." do
31
+ ActionMailer::Base.deliveries.collect(&:subject).should include("Signup mail")
32
+ end
33
+
34
+ end
35
+
36
+ describe '#verify' do
37
+
38
+ before(:all) do
39
+ @contact = Contact.create!({:email => 'jon@doe.com', :firstname => 'Jon', :lastname => 'Doe', :salutation => 'mr'})
40
+ end
41
+
42
+ context "receiving hash from existing contact" do
43
+
44
+ render_views
45
+
46
+ before(:each) do
47
+ @element = @signup_page.elements.find_by_name(:newsletter_signup_form)
48
+ @element.content_by_name('verified_text').essence.update_attribute(:body, 'you are now verified')
49
+ post :verify, {:token => @contact.email_sha1, :use_route => :alchemy_crm}
50
+ end
51
+
52
+ it "should verify the contact." do
53
+ @contact.reload
54
+ @contact.verified.should be(true)
55
+ end
56
+
57
+ it "should display custom 'verified' text." do
58
+ verified_text = Regexp.new(Regexp.escape(@element.ingredient('verified_text')))
59
+ response.body.should match(verified_text)
60
+ end
61
+
62
+ end
63
+
64
+ context "receiving hash from existing contact and newsletter ids" do
65
+
66
+ before(:each) do
67
+ @subscription = Subscription.create!(:contact => @contact, :newsletter => @newsletter)
68
+ @newsletter_ids = @contact.subscriptions.collect(&:newsletter_id)
69
+ post :verify, {:token => @contact.email_sha1, :newsletter_ids => @newsletter_ids, :use_route => :alchemy_crm}
70
+ end
71
+
72
+ it "should verify all subscriptions from contact." do
73
+ @contact.subscriptions.where(:verified => true).collect(&:newsletter_id).should == @newsletter_ids
74
+ end
75
+
76
+ end
77
+
78
+ context "receiving hash from not existing contact" do
79
+
80
+ it "should raise routing error." do
81
+ lambda {
82
+ post :verify, {:token => 'f4k3h4ckh4sh', :use_route => :alchemy_crm}
83
+ }.should raise_error(ActionController::RoutingError)
84
+ end
85
+
86
+ end
87
+
88
+ context "not receiving any newsletter_ids" do
89
+
90
+ before(:each) do
91
+ @subscription = Subscription.create!(:contact => @contact, :newsletter => @newsletter)
92
+ post :verify, {:token => @contact.email_sha1, :use_route => :alchemy_crm}
93
+ end
94
+
95
+ it "should verify all subscriptions from contact." do
96
+ @contact.subscriptions.where(:verified => true).count.should == @contact.subscriptions.count
97
+ end
98
+
99
+ end
100
+
101
+ after(:all) do
102
+ @contact.destroy
103
+ end
104
+
105
+ end
106
+
107
+ describe '#signout' do
108
+
109
+ context "receiving an unknown email" do
110
+
111
+ it "should display error notice." do
112
+ post :signout, {:email => 'not@know.com', :use_route => :alchemy_crm}
113
+ flash[:notice].should match(/contact could not be found/)
114
+ end
115
+
116
+ end
117
+
118
+ context "receiving an email for verified contact" do
119
+
120
+ before(:each) do
121
+ @contact = Contact.create!({:email => 'jon@doe.com', :firstname => 'Jon', :lastname => 'Doe', :salutation => 'mr', :verified => true}, :as => :admin)
122
+ end
123
+
124
+ it "should deliver signout mail." do
125
+ post :signout, {:email => 'jon@doe.com', :use_route => :alchemy_crm}
126
+ ActionMailer::Base.deliveries.collect(&:subject).should include("Signout mail")
127
+ end
128
+
129
+ end
130
+
131
+ context "receiving an email for known, but not verified contact" do
132
+
133
+ before(:each) do
134
+ @contact = Contact.create!({:email => 'jon@doe.com', :firstname => 'Jon', :lastname => 'Doe', :salutation => 'mr'})
135
+ end
136
+
137
+ it "should display error notice." do
138
+ post :signout, {:email => @contact.email, :use_route => :alchemy_crm}
139
+ flash[:notice].should match(/contact could not be found/)
140
+ end
141
+
142
+ end
143
+
144
+ end
145
+
146
+ describe '#disable' do
147
+
148
+ before(:all) do
149
+ @contact = Contact.create!({:email => 'jon@doe.com', :firstname => 'Jon', :lastname => 'Doe', :salutation => 'mr', :verified => true}, :as => :admin)
150
+ end
151
+
152
+ context "receiving hash from existing contact" do
153
+
154
+ render_views
155
+
156
+ before(:each) do
157
+ @element = @signout_page.elements.find_by_name(:newsletter_signout_form)
158
+ @element.content_by_name('disabled_text').essence.update_attribute(:body, 'you are now disabled')
159
+ post :disable, {:token => @contact.email_sha1, :use_route => :alchemy_crm}
160
+ end
161
+
162
+ it "should disable the contact." do
163
+ @contact.reload
164
+ @contact.disabled.should be(true)
165
+ @contact.verified.should be(false)
166
+ end
167
+
168
+ it "should display custom 'disabled' text." do
169
+ disabled_text = Regexp.new(Regexp.escape(@element.ingredient('disabled_text')))
170
+ response.body.should match(disabled_text)
171
+ end
172
+
173
+ end
174
+
175
+ context "receiving hash from existing contact" do
176
+
177
+ before(:each) do
178
+ @subscription = Subscription.create!(:contact => @contact, :newsletter => @newsletter)
179
+ post :disable, {:token => @contact.email_sha1, :use_route => :alchemy_crm}
180
+ end
181
+
182
+ it "should delete all subscriptions from contact." do
183
+ @contact.subscriptions.should be_empty
184
+ end
185
+
186
+ end
187
+
188
+ context "receiving hash from not existing contact" do
189
+
190
+ it "should raise routing error." do
191
+ lambda {
192
+ post :disable, {:token => 'f4k3h4ckh4sh', :use_route => :alchemy_crm}
193
+ }.should raise_error(ActionController::RoutingError)
194
+ end
195
+
196
+ end
197
+
198
+ after(:all) do
199
+ @contact.destroy
200
+ end
201
+
202
+ end
203
+
204
+ after(:all) do
205
+ @language_root.destroy
206
+ Contact.destroy_all
207
+ end
208
+
209
+ end
210
+ end