fat_free_crm 0.11.1 → 0.11.2

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.

Potentially problematic release.


This version of fat_free_crm might be problematic. Click here for more details.

Files changed (179) hide show
  1. data/Gemfile +30 -12
  2. data/Gemfile.lock +131 -119
  3. data/Procfile +1 -1
  4. data/README.md +1 -1
  5. data/app/assets/images/notifications.png +0 -0
  6. data/app/assets/javascripts/application.js.erb +3 -0
  7. data/app/assets/javascripts/crm_textarea_autocomplete.js +44 -0
  8. data/app/assets/stylesheets/application.css.erb +2 -0
  9. data/app/assets/stylesheets/common.scss +7 -11
  10. data/app/assets/stylesheets/textarea_autocomplete.scss +42 -0
  11. data/app/controllers/admin/application_controller.rb +5 -5
  12. data/app/controllers/admin/field_groups_controller.rb +11 -51
  13. data/app/controllers/admin/fields_controller.rb +13 -59
  14. data/app/controllers/admin/plugins_controller.rb +1 -4
  15. data/app/controllers/admin/settings_controller.rb +0 -4
  16. data/app/controllers/admin/tags_controller.rb +11 -66
  17. data/app/controllers/admin/users_controller.rb +20 -83
  18. data/app/controllers/application_controller.rb +83 -69
  19. data/app/controllers/comments_controller.rb +12 -29
  20. data/app/controllers/emails_controller.rb +1 -5
  21. data/app/controllers/entities/accounts_controller.rb +13 -32
  22. data/app/controllers/entities/campaigns_controller.rb +17 -32
  23. data/app/controllers/entities/contacts_controller.rb +20 -38
  24. data/app/controllers/entities/leads_controller.rb +33 -55
  25. data/app/controllers/entities/opportunities_controller.rb +26 -42
  26. data/app/controllers/entities_controller.rb +92 -83
  27. data/app/controllers/home_controller.rb +1 -10
  28. data/app/controllers/lists_controller.rb +1 -4
  29. data/app/controllers/{entities/tasks_controller.rb → tasks_controller.rb} +21 -32
  30. data/app/controllers/users_controller.rb +6 -5
  31. data/app/helpers/accounts_helper.rb +32 -9
  32. data/app/helpers/application_helper.rb +15 -1
  33. data/app/helpers/campaigns_helper.rb +1 -1
  34. data/app/helpers/comments_helper.rb +11 -1
  35. data/app/helpers/leads_helper.rb +1 -1
  36. data/app/helpers/opportunities_helper.rb +1 -1
  37. data/app/{models/mailers/notifier.rb → mailers/dropbox_mailer.rb} +5 -16
  38. data/app/mailers/subscription_mailer.rb +37 -0
  39. data/{lib/tasks/dropbox.rake → app/mailers/user_mailer.rb} +11 -13
  40. data/app/models/entities/account.rb +3 -1
  41. data/app/models/entities/campaign.rb +3 -1
  42. data/app/models/entities/contact.rb +3 -1
  43. data/app/models/entities/lead.rb +6 -5
  44. data/app/models/entities/opportunity.rb +3 -1
  45. data/app/models/fields/field.rb +1 -1
  46. data/app/models/polymorphic/comment.rb +34 -0
  47. data/app/models/{entities → polymorphic}/task.rb +16 -3
  48. data/app/models/setting.rb +15 -15
  49. data/app/models/users/ability.rb +12 -5
  50. data/app/models/users/user.rb +7 -2
  51. data/app/views/accounts/index.html.haml +1 -1
  52. data/app/views/accounts/index.js.rjs +1 -1
  53. data/app/views/admin/plugins/index.html.haml +1 -7
  54. data/app/views/{shared/auto_complete.html.haml → application/_auto_complete.html.haml} +0 -0
  55. data/app/views/{shared → application}/index.atom.builder +1 -1
  56. data/app/views/{shared → application}/index.rss.builder +1 -1
  57. data/app/views/campaigns/index.html.haml +1 -1
  58. data/app/views/campaigns/index.js.rjs +1 -1
  59. data/app/views/comments/_new.html.haml +6 -0
  60. data/app/views/comments/_subscription_links.html.haml +13 -0
  61. data/app/views/comments/new.js.rjs +2 -0
  62. data/app/views/contacts/_top_section.html.haml +3 -13
  63. data/app/views/contacts/index.html.haml +1 -1
  64. data/app/views/contacts/index.js.rjs +1 -1
  65. data/app/views/{notifier/dropbox_ack_notification.html.haml → dropbox_mailer/dropbox_notification.html.haml} +2 -2
  66. data/app/views/{shared → entities}/attach.js.rjs +1 -1
  67. data/app/views/entities/contacts.js.rjs +1 -1
  68. data/app/views/{shared/discard.rjs → entities/discard.js.rjs} +0 -0
  69. data/app/views/entities/leads.js.rjs +1 -1
  70. data/app/views/entities/opportunities.js.rjs +1 -1
  71. data/app/views/entities/subscription_update.js.rjs +4 -0
  72. data/app/views/entities/versions.js.rjs +1 -1
  73. data/app/views/layouts/_footer.html.haml +1 -1
  74. data/app/views/layouts/application.html.haml +3 -0
  75. data/app/views/leads/_contact.html.haml +1 -0
  76. data/app/views/leads/index.html.haml +1 -1
  77. data/app/views/leads/index.js.rjs +1 -1
  78. data/app/views/opportunities/_top_section.html.haml +4 -14
  79. data/app/views/opportunities/index.html.haml +1 -1
  80. data/app/views/opportunities/index.js.rjs +1 -1
  81. data/app/views/subscription_mailer/comment_notification.text.erb +7 -0
  82. data/app/views/{notifier → user_mailer}/password_reset_instructions.html.haml +0 -0
  83. data/config/application.rb +3 -1
  84. data/config/environments/development.rb +1 -1
  85. data/config/environments/test.rb +3 -0
  86. data/config/initializers/action_mailer.rb +8 -5
  87. data/config/initializers/cancan.rb +151 -0
  88. data/config/initializers/constants.rb +1 -0
  89. data/config/initializers/locale.rb +20 -0
  90. data/config/initializers/paper_trail.rb +4 -5
  91. data/config/initializers/relative_url_root.rb +0 -1
  92. data/config/initializers/squeel.rb +5 -0
  93. data/config/locales/cz_fat_free_crm.yml +3 -3
  94. data/config/locales/de.yml +2 -2
  95. data/config/locales/de_fat_free_crm.yml +651 -596
  96. data/config/locales/en-GB_fat_free_crm.yml +3 -3
  97. data/config/locales/en-US_fat_free_crm.yml +13 -3
  98. data/config/locales/es_fat_free_crm.yml +3 -3
  99. data/config/locales/fr-CA_fat_free_crm.yml +3 -3
  100. data/config/locales/fr_fat_free_crm.yml +3 -3
  101. data/config/locales/it_fat_free_crm.yml +3 -3
  102. data/config/locales/pl_fat_free_crm.yml +3 -3
  103. data/config/locales/pt-BR_fat_free_crm.yml +3 -3
  104. data/config/locales/ru_fat_free_crm.yml +3 -3
  105. data/config/locales/sv-SE_fat_free_crm.yml +3 -3
  106. data/config/locales/th_fat_free_crm.yml +3 -3
  107. data/config/routes.rb +10 -0
  108. data/config/settings.default.yml +29 -10
  109. data/config/unicorn.rb +4 -0
  110. data/db/migrate/20111201030535_add_field_groups_klass_name.rb +3 -1
  111. data/db/migrate/20120314080441_add_subscribed_users_to_entities.rb +23 -0
  112. data/db/migrate/20120405080727_change_subscribed_users_to_set.rb +24 -0
  113. data/db/migrate/20120405080742_change_further_subscribed_users_to_set.rb +27 -0
  114. data/db/migrate/20120413034923_add_index_on_versions_item_type.rb +5 -0
  115. data/db/schema.rb +109 -126
  116. data/fat_free_crm.gemspec +12 -18
  117. data/lib/fat_free_crm.rb +0 -1
  118. data/lib/fat_free_crm/core_ext/array.rb +1 -0
  119. data/lib/fat_free_crm/gem_dependencies.rb +1 -0
  120. data/lib/fat_free_crm/mail_processor/base.rb +226 -0
  121. data/lib/fat_free_crm/mail_processor/comment_replies.rb +86 -0
  122. data/lib/fat_free_crm/mail_processor/dropbox.rb +288 -0
  123. data/lib/fat_free_crm/permissions.rb +6 -19
  124. data/lib/fat_free_crm/renderers.rb +0 -8
  125. data/lib/fat_free_crm/tabs.rb +1 -1
  126. data/lib/fat_free_crm/version.rb +1 -1
  127. data/lib/plugins/country_select/lib/country_select.rb +2 -2
  128. data/lib/tasks/mail_processing.rake +60 -0
  129. data/spec/controllers/admin/users_controller_spec.rb +0 -2
  130. data/spec/controllers/{accounts_controller_spec.rb → entities/accounts_controller_spec.rb} +7 -9
  131. data/spec/controllers/{campaigns_controller_spec.rb → entities/campaigns_controller_spec.rb} +7 -7
  132. data/spec/controllers/{contacts_controller_spec.rb → entities/contacts_controller_spec.rb} +5 -9
  133. data/spec/controllers/{leads_controller_spec.rb → entities/leads_controller_spec.rb} +7 -9
  134. data/spec/controllers/{opportunities_controller_spec.rb → entities/opportunities_controller_spec.rb} +8 -15
  135. data/spec/controllers/tasks_controller_spec.rb +1 -5
  136. data/spec/controllers/users_controller_spec.rb +5 -9
  137. data/spec/factories/subscription_factories.rb +6 -0
  138. data/spec/lib/mail_processor/base_spec.rb +164 -0
  139. data/spec/lib/mail_processor/comment_replies_spec.rb +63 -0
  140. data/spec/lib/{dropbox_spec.rb → mail_processor/dropbox_spec.rb} +73 -181
  141. data/spec/lib/mail_processor/sample_emails/dropbox.rb +167 -0
  142. data/spec/mailers/subscription_mailer_spec.rb +17 -0
  143. data/spec/models/{base → entities}/account_contact_spec.rb +0 -0
  144. data/spec/models/{base → entities}/account_opportunity_spec.rb +0 -0
  145. data/spec/models/{base → entities}/account_spec.rb +4 -0
  146. data/spec/models/{base → entities}/campaign_spec.rb +4 -0
  147. data/spec/models/{base → entities}/contact_opportunity_spec.rb +0 -0
  148. data/spec/models/{base → entities}/contact_spec.rb +4 -0
  149. data/spec/models/{base → entities}/lead_spec.rb +4 -0
  150. data/spec/models/{base → entities}/opportunity_spec.rb +4 -0
  151. data/spec/models/polymorphic/comment_spec.rb +15 -0
  152. data/spec/models/{base → polymorphic}/task_spec.rb +124 -30
  153. data/spec/models/polymorphic/version_spec.rb +1 -1
  154. data/spec/shared/controllers.rb +5 -7
  155. data/spec/shared/models.rb +46 -0
  156. data/spec/spec_helper.rb +3 -4
  157. data/spec/support/mail_processor_mocks.rb +30 -0
  158. data/spec/support/uploaded_file.rb +3 -0
  159. data/spec/views/{common → application}/auto_complete.haml_spec.rb +1 -1
  160. data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  161. data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  162. data/vendor/assets/images/jquery-ui/ui-bg_flat_10_000000_40x100.png +0 -0
  163. data/vendor/assets/images/jquery-ui/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  164. data/vendor/assets/images/jquery-ui/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  165. data/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
  166. data/vendor/assets/images/jquery-ui/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  167. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  168. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  169. data/vendor/assets/images/jquery-ui/ui-icons_222222_256x240.png +0 -0
  170. data/vendor/assets/images/jquery-ui/ui-icons_228ef1_256x240.png +0 -0
  171. data/vendor/assets/images/jquery-ui/ui-icons_ef8c08_256x240.png +0 -0
  172. data/vendor/assets/images/jquery-ui/ui-icons_ffd27a_256x240.png +0 -0
  173. data/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png +0 -0
  174. data/vendor/assets/javascripts/textarea_autocomplete.js +605 -0
  175. data/vendor/assets/stylesheets/jquery-ui.custom.css.erb +565 -0
  176. metadata +234 -154
  177. data/config/locales/simple_form.en.yml +0 -24
  178. data/lib/fat_free_crm/dropbox.rb +0 -439
  179. data/spec/lib/dropbox/email_samples.rb +0 -77
@@ -49,6 +49,8 @@ class Account < ActiveRecord::Base
49
49
  has_one :shipping_address, :dependent => :destroy, :as => :addressable, :class_name => "Address", :conditions => "address_type = 'Shipping'"
50
50
  has_many :emails, :as => :mediator
51
51
 
52
+ serialize :subscribed_users, Set
53
+
52
54
  accepts_nested_attributes_for :billing_address, :allow_destroy => true
53
55
  accepts_nested_attributes_for :shipping_address, :allow_destroy => true
54
56
 
@@ -66,7 +68,7 @@ class Account < ActiveRecord::Base
66
68
  uses_user_permissions
67
69
  acts_as_commentable
68
70
  acts_as_taggable_on :tags
69
- has_paper_trail
71
+ has_paper_trail :ignore => [ :subscribed_users ]
70
72
  has_fields
71
73
  exportable
72
74
  sortable :by => [ "name ASC", "rating DESC", "created_at DESC", "updated_at DESC" ], :default => "created_at DESC"
@@ -49,6 +49,8 @@ class Campaign < ActiveRecord::Base
49
49
  has_many :opportunities, :dependent => :destroy, :order => "id DESC"
50
50
  has_many :emails, :as => :mediator
51
51
 
52
+ serialize :subscribed_users, Set
53
+
52
54
  scope :state, lambda { |filters|
53
55
  where('status IN (?)' + (filters.delete('other') ? ' OR status IS NULL' : ''), filters)
54
56
  }
@@ -63,7 +65,7 @@ class Campaign < ActiveRecord::Base
63
65
  uses_user_permissions
64
66
  acts_as_commentable
65
67
  acts_as_taggable_on :tags
66
- has_paper_trail
68
+ has_paper_trail :ignore => [ :subscribed_users ]
67
69
  has_fields
68
70
  exportable
69
71
  sortable :by => [ "name ASC", "target_leads DESC", "target_revenue DESC", "leads_count DESC", "revenue DESC", "starts_on DESC", "ends_on DESC", "created_at DESC", "updated_at DESC" ], :default => "created_at DESC"
@@ -60,6 +60,8 @@ class Contact < ActiveRecord::Base
60
60
  has_one :business_address, :dependent => :destroy, :as => :addressable, :class_name => "Address", :conditions => "address_type = 'Business'"
61
61
  has_many :emails, :as => :mediator
62
62
 
63
+ serialize :subscribed_users, Set
64
+
63
65
  accepts_nested_attributes_for :business_address, :allow_destroy => true
64
66
 
65
67
  scope :created_by, lambda { |user| { :conditions => [ "user_id = ?", user.id ] } }
@@ -82,7 +84,7 @@ class Contact < ActiveRecord::Base
82
84
  uses_user_permissions
83
85
  acts_as_commentable
84
86
  acts_as_taggable_on :tags
85
- has_paper_trail
87
+ has_paper_trail :ignore => [ :subscribed_users ]
86
88
  has_fields
87
89
  exportable
88
90
  sortable :by => [ "first_name ASC", "last_name ASC", "created_at DESC", "updated_at DESC" ], :default => "created_at DESC"
@@ -57,6 +57,8 @@ class Lead < ActiveRecord::Base
57
57
  has_one :business_address, :dependent => :destroy, :as => :addressable, :class_name => "Address", :conditions => "address_type='Business'"
58
58
  has_many :emails, :as => :mediator
59
59
 
60
+ serialize :subscribed_users, Set
61
+
60
62
  accepts_nested_attributes_for :business_address, :allow_destroy => true
61
63
 
62
64
  scope :state, lambda { |filters|
@@ -75,7 +77,7 @@ class Lead < ActiveRecord::Base
75
77
  uses_user_permissions
76
78
  acts_as_commentable
77
79
  acts_as_taggable_on :tags
78
- has_paper_trail
80
+ has_paper_trail :ignore => [ :subscribed_users ]
79
81
  has_fields
80
82
  exportable
81
83
  sortable :by => [ "first_name ASC", "last_name ASC", "company ASC", "rating DESC", "created_at DESC", "updated_at DESC" ], :default => "created_at DESC"
@@ -125,7 +127,7 @@ class Lead < ActiveRecord::Base
125
127
  opportunity = Opportunity.create_for(self, account, params[:opportunity], params[:users])
126
128
  contact = Contact.create_for(self, account, opportunity, params)
127
129
 
128
- return account, opportunity, contact
130
+ [account, opportunity, contact]
129
131
  end
130
132
 
131
133
  #----------------------------------------------------------------------------
@@ -162,7 +164,8 @@ class Lead < ActiveRecord::Base
162
164
  end
163
165
  alias :name :full_name
164
166
 
165
- private
167
+ private
168
+
166
169
  #----------------------------------------------------------------------------
167
170
  def increment_leads_count
168
171
  if self.campaign_id
@@ -182,6 +185,4 @@ class Lead < ActiveRecord::Base
182
185
  def users_for_shared_access
183
186
  errors.add(:access, :share_lead) if self[:access] == "Shared" && !self.permissions.any?
184
187
  end
185
-
186
188
  end
187
-
@@ -48,6 +48,8 @@ class Opportunity < ActiveRecord::Base
48
48
  has_many :tasks, :as => :asset, :dependent => :destroy#, :order => 'created_at DESC'
49
49
  has_many :emails, :as => :mediator
50
50
 
51
+ serialize :subscribed_users, Set
52
+
51
53
  scope :state, lambda { |filters|
52
54
  where('stage IN (?)' + (filters.delete('other') ? ' OR stage IS NULL' : ''), filters)
53
55
  }
@@ -71,7 +73,7 @@ class Opportunity < ActiveRecord::Base
71
73
  uses_user_permissions
72
74
  acts_as_commentable
73
75
  acts_as_taggable_on :tags
74
- has_paper_trail
76
+ has_paper_trail :ignore => [ :subscribed_users ]
75
77
  has_fields
76
78
  exportable
77
79
  sortable :by => [ "name ASC", "amount DESC", "amount*probability DESC", "probability DESC", "closes_on ASC", "created_at DESC", "updated_at DESC" ], :default => "created_at DESC"
@@ -112,7 +112,7 @@ class Field < ActiveRecord::Base
112
112
  when 'date'
113
113
  value && value.strftime(I18n.t("date.formats.default"))
114
114
  when 'datetime'
115
- value && value.strftime(I18n.t("time.formats.short"))
115
+ value && value.strftime(I18n.t("time.formats.long"))
116
116
  when 'check_boxes'
117
117
  value = YAML.load(value) if value.is_a?(String)
118
118
  value.select(&:present?).in_groups_of(2, false).map {|g| g.join(', ')}.join("<br />".html_safe) if Array === value
@@ -41,6 +41,40 @@ class Comment < ActiveRecord::Base
41
41
  has_paper_trail :meta => { :related => :commentable },
42
42
  :ignore => [:state]
43
43
 
44
+ before_create :subscribe_mentioned_users
45
+ after_create :subscribe_user_to_entity, :notify_subscribers
46
+
44
47
  def expanded?; self.state == "Expanded"; end
45
48
  def collapsed?; self.state == "Collapsed"; end
49
+
50
+ private
51
+ # Add user to subscribed_users field on entity
52
+ def subscribe_user_to_entity(u = user)
53
+ commentable.subscribed_users << u.id
54
+ commentable.save
55
+ end
56
+
57
+ # Notify subscribed users when a comment is added, unless user created this comment
58
+ def notify_subscribers
59
+ commentable.subscribed_users.reject{|user_id| user_id == user.id}.each do |subscriber_id|
60
+ if subscriber = User.find_by_id(subscriber_id)
61
+ # Only send email if SMTP settings are configured
62
+ if Rails.application.config.action_mailer.smtp_settings.present?
63
+ SubscriptionMailer.comment_notification(subscriber, self).deliver
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # If a user is mentioned in the comment body, subscribe them to the entity
70
+ # before creation, so that they are sent an email notification
71
+ def subscribe_mentioned_users
72
+ # Scan for usernames mentioned in the comment,
73
+ # e.g. "Hi @example_user, take a look at this lead. Please show @another_user"
74
+ comment.scan(/@([a-zA-Z0-9_-]+)/).map(&:first).each do |username|
75
+ if (mentioned_user = User.find_by_username(username))
76
+ subscribe_user_to_entity(mentioned_user)
77
+ end
78
+ end
79
+ end
46
80
  end
@@ -45,6 +45,8 @@ class Task < ActiveRecord::Base
45
45
  belongs_to :completor, :class_name => "User", :foreign_key => :completed_by
46
46
  belongs_to :asset, :polymorphic => true
47
47
 
48
+ serialize :subscribed_users, Set
49
+
48
50
  # Tasks created by the user for herself, or assigned to her by others. That's
49
51
  # what gets shown on Tasks/Pending and Tasks/Completed pages.
50
52
  scope :my, lambda { |*args|
@@ -97,7 +99,7 @@ class Task < ActiveRecord::Base
97
99
  }
98
100
 
99
101
  acts_as_commentable
100
- has_paper_trail :meta => { :related => :asset }
102
+ has_paper_trail :meta => { :related => :asset }, :ignore => [ :subscribed_users ]
101
103
  has_fields
102
104
  exportable
103
105
 
@@ -231,10 +233,21 @@ class Task < ActiveRecord::Base
231
233
  rescue ArgumentError
232
234
  errors.add(:calendar, :invalid_date)
233
235
  end
234
-
236
+
235
237
  #----------------------------------------------------------------------------
236
238
  def parse_calendar_date
237
- DateTime.strptime(self.calendar, I18n.t(Setting.task_calendar_with_time ? 'time.formats.mmddyyyy_hhmm' : 'date.formats.mmddyyyy')).utc
239
+ translate_month_and_day_names!(self.calendar) unless I18n.locale == :"en-US"
240
+
241
+ DateTime.strptime(self.calendar,
242
+ I18n.t(Setting.task_calendar_with_time ? 'time.formats.mmddyyyy_hhmm' : 'date.formats.mmddyyyy')).utc
243
+ end
244
+
245
+ # Translates month and day names of a given datetime string.
246
+ #----------------------------------------------------------------------------
247
+ def translate_month_and_day_names!(date_string)
248
+ translated = I18n.t([:month_names, :abbr_month_names, :day_names, :abbr_day_names], :scope => :date).flatten.compact
249
+ original = (Date::MONTHNAMES + Date::ABBR_MONTHNAMES + Date::DAYNAMES + Date::ABBR_DAYNAMES).compact
250
+ translated.each_with_index { |name, i| date_string.gsub!(name, original[i]) }
238
251
  end
239
252
  end
240
253
 
@@ -27,7 +27,7 @@
27
27
  #
28
28
 
29
29
  # Fat Free CRM settings are stored in three places, and are loaded in the following order:
30
- #
30
+ #
31
31
  # 1) config/settings.default.yml
32
32
  # 2) config/settings.yml (if exists)
33
33
  # 3) 'settings' table in database (if exists)
@@ -44,16 +44,16 @@ class Setting < ActiveRecord::Base
44
44
  @@cache = @@yaml_settings = {}.with_indifferent_access
45
45
 
46
46
  class << self
47
-
47
+
48
48
  # Cache should be cleared before each request.
49
49
  def clear_cache!
50
50
  @@cache = {}.with_indifferent_access
51
51
  end
52
-
52
+
53
53
  #-------------------------------------------------------------------
54
54
  def method_missing(method, *args)
55
55
  begin
56
- super(method, *args)
56
+ super
57
57
  rescue NoMethodError
58
58
  method_name = method.to_s
59
59
  if method_name.last == "="
@@ -70,30 +70,30 @@ class Setting < ActiveRecord::Base
70
70
  # Return value if cached
71
71
  return cache[name] if cache.has_key?(name)
72
72
  # Check database
73
- if database_and_table_exists?
74
- if setting = self.find_by_name(name)
73
+ if database_and_table_exists?
74
+ if setting = self.find_by_name(name.to_s)
75
75
  unless setting.value.nil?
76
76
  return cache[name] = setting.value
77
77
  end
78
78
  end
79
79
  end
80
- # Check YAML settings
80
+ # Check YAML settings
81
81
  if yaml_settings.has_key?(name)
82
82
  return cache[name] = yaml_settings[name]
83
83
  end
84
84
  end
85
-
85
+
86
86
 
87
87
  # Set setting value
88
88
  #-------------------------------------------------------------------
89
89
  def []=(name, value)
90
90
  return nil unless database_and_table_exists?
91
- setting = self.find_by_name(name) || self.new(:name => name)
91
+ setting = self.find_by_name(name.to_s) || self.new(:name => name)
92
92
  setting.value = value
93
93
  setting.save
94
94
  cache[name] = value
95
95
  end
96
-
96
+
97
97
 
98
98
  # Unrolls [ :one, :two ] settings array into [[ "One", :one ], [ "Two", :two ]]
99
99
  # picking symbol translations from locale. If setting is not a symbol but
@@ -109,12 +109,12 @@ class Setting < ActiveRecord::Base
109
109
  # instead of crashing the entire application.
110
110
  table_exists? rescue false
111
111
  end
112
-
113
-
112
+
113
+
114
114
  # Loads settings from YAML files
115
115
  def load_settings_from_yaml
116
116
  @@yaml_settings = {}.with_indifferent_access
117
-
117
+
118
118
  setting_files = [
119
119
  FatFreeCRM.root.join("config", "settings.default.yml"),
120
120
  Rails.root.join("config", "settings.yml")
@@ -125,8 +125,8 @@ class Setting < ActiveRecord::Base
125
125
  if File.exist?(file)
126
126
  begin
127
127
  settings = YAML.load_file(file)
128
- # Merge settings into current settings hash
129
- @@yaml_settings.merge!(settings)
128
+ # Merge settings into current settings hash (recursively)
129
+ @@yaml_settings.deep_merge!(settings)
130
130
  rescue Exception => ex
131
131
  puts "Settings couldn't be loaded from #{file}: #{ex.message}"
132
132
  end
@@ -1,13 +1,20 @@
1
+ # See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities
2
+
1
3
  class Ability
2
4
  include CanCan::Ability
3
5
 
4
6
  def initialize(user)
5
- # See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities
6
- can :create, :all
7
- can [:read, :update, :destroy], :all, :access => 'Public'
8
7
  if user.present?
9
- can [:read, :update, :destroy], :all, :user_id => user.id
10
- can [:read, :update, :destroy], :all, :permissions => {:user_id => user.id}
8
+ entities = [Account, Campaign, Contact, Lead, Opportunity]
9
+
10
+ can :create, :all
11
+ can :manage, entities, :access => 'Public'
12
+ can :manage, entities + [Task], :user_id => user.id
13
+
14
+ entities.each do |klass|
15
+ permissions = user.permissions.where(:asset_type => klass.name)
16
+ can :manage, klass, :id => permissions.map(&:asset_id)
17
+ end
11
18
  end
12
19
  end
13
20
  end
@@ -80,11 +80,16 @@ class User < ActiveRecord::Base
80
80
  where('upper(username) LIKE upper(:s) OR upper(first_name) LIKE upper(:s) OR upper(last_name) LIKE upper(:s)', :s => "#{query}%")
81
81
  }
82
82
 
83
+ scope :my, lambda {
84
+ current_ability = Ability.new(User.current_user)
85
+ accessible_by(current_ability)
86
+ }
87
+
83
88
  acts_as_authentic do |c|
84
89
  c.session_class = Authentication
85
90
  c.validates_uniqueness_of_login_field_options = { :message => :username_taken }
86
91
  c.validates_length_of_login_field_options = { :minimum => 1, :message => :missing_username }
87
- c.merge_validates_format_of_login_field_options(:with => /.*/)
92
+ c.merge_validates_format_of_login_field_options(:with => /[a-zA-Z0-9_-]+/)
88
93
 
89
94
  c.validates_uniqueness_of_email_field_options = { :message => :email_in_use }
90
95
  c.validates_length_of_password_field_options = { :minimum => 0, :allow_blank => true, :if => :require_password? }
@@ -126,7 +131,7 @@ class User < ActiveRecord::Base
126
131
  #----------------------------------------------------------------------------
127
132
  def deliver_password_reset_instructions!
128
133
  reset_perishable_token!
129
- Notifier.password_reset_instructions(self).deliver
134
+ UserMailer.password_reset_instructions(self).deliver
130
135
  end
131
136
 
132
137
  # Override global I18n.locale if the user has individual local preference.
@@ -11,7 +11,7 @@
11
11
  = image_tag("loading.gif", :size => :thumb, :id => "loading", :style => "display: none;")
12
12
  .remote#options{ hidden }
13
13
  .remote#advanced_search{ hidden_if(!params[:q]) }
14
- - if @search
14
+ - if params[:q]
15
15
  = render :partial => "advanced_search"
16
16
  .remote#create_account{ hidden }
17
17
 
@@ -1,4 +1,4 @@
1
- unless @accounts.blank?
1
+ if @accounts.any?
2
2
  page[:accounts].replace_html render @accounts
3
3
  else
4
4
  page[:accounts].replace_html :partial => "shared/empty"
@@ -1,9 +1,3 @@
1
- = styles_for :plugin
2
-
3
1
  .title Plugins
4
2
 
5
- .list#plugins
6
- - if @plugins.any?
7
- = render :partial => "admin/plugins/plugin", :collection => @plugins
8
- - else
9
- = render "shared/empty"
3
+ %p #{t :not_implemented}
@@ -11,7 +11,7 @@ end
11
11
  atom_feed do |feed|
12
12
  feed.title title || t(items.to_sym)
13
13
  feed.updated assets.any? ? assets.max { |a, b| a.updated_at <=> b.updated_at }.updated_at : Time.now
14
- feed.generator "Fat Free CRM v#{FatFreeCRM::Version}"
14
+ feed.generator "Fat Free CRM v#{FatFreeCRM::VERSION::STRING}"
15
15
  feed.author do |author|
16
16
  author.name @current_user.full_name
17
17
  author.email @current_user.email
@@ -11,7 +11,7 @@ end
11
11
  xml.instruct! :xml, :version => "1.0"
12
12
  xml.rss :version => "2.0" do
13
13
  xml.channel do
14
- xml.generator "Fat Free CRM v#{FatFreeCRM::Version}"
14
+ xml.generator "Fat Free CRM v#{FatFreeCRM::VERSION::STRING}"
15
15
  xml.link send(:"#{items}_url")
16
16
  xml.pubDate Time.now.to_s(:rfc822)
17
17
  xml.title title || t(items.to_sym)
@@ -11,7 +11,7 @@
11
11
  = image_tag("loading.gif", :size => :thumb, :id => "loading", :style => "display: none;")
12
12
  .remote#options{ hidden }
13
13
  .remote#advanced_search{ hidden_if(!params[:q]) }
14
- - if @search
14
+ - if params[:q]
15
15
  = render :partial => "advanced_search"
16
16
  .remote#create_campaign{ hidden }
17
17
 
@@ -1,4 +1,4 @@
1
- unless @campaigns.blank?
1
+ if @campaigns.any?
2
2
  page[:campaigns].replace_html render @campaigns
3
3
  else
4
4
  page[:campaigns].replace_html :partial => "shared/empty"
@@ -1,6 +1,10 @@
1
1
  - class_name = commentable.class.name.downcase
2
2
  - id_prefix = "#{class_name}_#{commentable.id}"
3
3
  .comment.highlight.new_comment{ :id => "#{id_prefix}_comment_new" }
4
+ - subscribed_users = commentable.subscribed_users.map{|uid| User.find_by_id(uid) unless uid == @current_user.id }.compact
5
+ - if notification_emails_configured? && subscribed_users.any?
6
+ = t(:following_users_will_be_notified) << ":"
7
+ = subscribed_user_links(subscribed_users)
4
8
 
5
9
  -# Two hidden fields store the IDs of notes and emails shown for the asset. These IDs are used
6
10
  -# by [Expand/Collapse All]. The contents gets updated by actions such as [Add] or [Delete].
@@ -25,3 +29,5 @@
25
29
  %div{ {:id => "#{id_prefix}_ask"}.merge(hidden_if(false))}
26
30
  = text_field_tag :post_new_note, t(:add_note_help), :onclick => remote_function(:url => new_comment_path("#{class_name}_id" => commentable), :method => :get), :id => "#{id_prefix}_post_new_note"
27
31
 
32
+ - if notification_emails_configured?
33
+ = render :partial => "comments/subscription_links", :locals => {:entity => commentable}
@@ -0,0 +1,13 @@
1
+ - class_name = entity.class.name.downcase
2
+ - id_prefix = "#{class_name}_#{entity.id}"
3
+
4
+ - subscribed = entity.subscribed_users.include?(current_user.id)
5
+
6
+ - if subscribed
7
+ - link_text, action = [t(:disable_email_subscriptions), "unsubscribe"]
8
+ - else
9
+ - link_text, action = [t(:subscribe_via_email), "subscribe"]
10
+
11
+ %div{:id => "#{id_prefix}_subscribe", :class => "comment_subscriptions"}
12
+ = image_tag "notifications.png", :title => t(:notifications_tooltip)
13
+ = link_to link_text, url_for(:controller => class_name.pluralize, :action => action, :id => entity.id), :remote => true, :method => :post