fat_free_crm 0.18.2 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (251) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +61 -160
  3. data/.travis.yml +27 -11
  4. data/CHANGELOG.md +40 -24
  5. data/CONTRIBUTORS.md +1 -0
  6. data/Dockerfile +45 -14
  7. data/Gemfile +16 -10
  8. data/Gemfile.lock +230 -222
  9. data/Procfile +1 -1
  10. data/README.md +2 -2
  11. data/Rakefile +1 -1
  12. data/app/assets/stylesheets/common.scss +1 -1
  13. data/app/controllers/admin/application_controller.rb +1 -1
  14. data/app/controllers/admin/field_groups_controller.rb +1 -3
  15. data/app/controllers/admin/tags_controller.rb +1 -3
  16. data/app/controllers/admin/users_controller.rb +5 -8
  17. data/app/controllers/application_controller.rb +11 -45
  18. data/app/controllers/comments_controller.rb +2 -5
  19. data/{config/initializers/authlogic.rb → app/controllers/confirmations_controller.rb} +4 -2
  20. data/app/controllers/emails_controller.rb +0 -2
  21. data/app/controllers/entities/accounts_controller.rb +1 -3
  22. data/app/controllers/entities/campaigns_controller.rb +1 -3
  23. data/app/controllers/entities/contacts_controller.rb +4 -24
  24. data/app/controllers/entities/leads_controller.rb +7 -10
  25. data/app/controllers/entities/opportunities_controller.rb +4 -14
  26. data/app/controllers/entities_controller.rb +21 -7
  27. data/app/controllers/home_controller.rb +2 -4
  28. data/app/controllers/passwords_controller.rb +3 -59
  29. data/{spec/features/support/maintain_sessions.rb → app/controllers/registrations_controller.rb} +12 -5
  30. data/{lib/development_tasks/gem.rake → app/controllers/sessions_controller.rb} +6 -6
  31. data/app/controllers/tasks_controller.rb +8 -17
  32. data/app/controllers/users_controller.rb +8 -29
  33. data/app/helpers/admin/users_helper.rb +1 -1
  34. data/app/helpers/application_helper.rb +27 -32
  35. data/app/helpers/campaigns_helper.rb +1 -1
  36. data/app/helpers/contacts_helper.rb +1 -3
  37. data/app/helpers/opportunities_helper.rb +4 -12
  38. data/app/helpers/tasks_helper.rb +1 -1
  39. data/app/helpers/users_helper.rb +1 -3
  40. data/{config/initializers/paper_trail.rb → app/mailers/devise_mailer.rb} +5 -1
  41. data/app/mailers/user_mailer.rb +0 -9
  42. data/app/models/entities/account.rb +10 -10
  43. data/app/models/entities/campaign.rb +4 -6
  44. data/app/models/entities/contact.rb +24 -12
  45. data/app/models/entities/lead.rb +7 -7
  46. data/app/models/entities/opportunity.rb +7 -9
  47. data/app/models/fields/custom_field.rb +1 -0
  48. data/app/models/fields/custom_field_date_pair.rb +2 -0
  49. data/app/models/fields/field.rb +1 -3
  50. data/app/models/list.rb +1 -1
  51. data/app/models/observers/entity_observer.rb +3 -7
  52. data/app/models/observers/lead_observer.rb +2 -4
  53. data/app/models/observers/opportunity_observer.rb +2 -4
  54. data/app/models/observers/task_observer.rb +1 -1
  55. data/app/models/polymorphic/email.rb +2 -2
  56. data/app/models/polymorphic/task.rb +13 -9
  57. data/app/models/polymorphic/version.rb +3 -2
  58. data/app/models/setting.rb +2 -0
  59. data/app/models/users/permission.rb +3 -3
  60. data/app/models/users/preference.rb +2 -1
  61. data/app/models/users/user.rb +67 -42
  62. data/app/views/accounts/_top_section.html.haml +1 -1
  63. data/app/views/accounts/edit.js.haml +1 -1
  64. data/app/views/accounts/update.js.haml +2 -2
  65. data/app/views/admin/users/_user.html.haml +4 -4
  66. data/app/views/contacts/_index_brief.html.haml +1 -1
  67. data/app/views/contacts/_index_full.html.haml +1 -1
  68. data/app/views/contacts/_index_long.html.haml +1 -1
  69. data/app/views/devise/confirmations/new.html.haml +9 -0
  70. data/app/views/devise/mailer/confirmation_instructions.html.haml +4 -0
  71. data/app/views/devise/mailer/password_change.html.haml +3 -0
  72. data/app/views/devise/mailer/reset_password_instructions.html.haml +6 -0
  73. data/app/views/devise/passwords/edit.html.haml +18 -0
  74. data/app/views/devise/passwords/new.html.haml +10 -0
  75. data/app/views/devise/registrations/new.html.haml +21 -0
  76. data/app/views/devise/sessions/new.html.haml +32 -0
  77. data/app/views/layouts/_about.html.haml +5 -5
  78. data/app/views/layouts/_header.html.haml +3 -3
  79. data/app/views/layouts/admin/_header.html.haml +1 -1
  80. data/app/views/shared/_address.html.haml +5 -5
  81. data/app/views/shared/_paginate_with_per_page.html.haml +1 -0
  82. data/app/views/users/_avatar.html.haml +1 -1
  83. data/bin/bundle +1 -1
  84. data/bin/rails +1 -1
  85. data/bin/setup +38 -0
  86. data/bin/update +33 -0
  87. data/bin/yarn +13 -0
  88. data/config/application.rb +8 -6
  89. data/config/boot.rb +1 -1
  90. data/config/brakeman.ignore +2 -2
  91. data/config/database.postgres.docker.yml +5 -5
  92. data/config/environment.rb +1 -1
  93. data/config/environments/development.rb +1 -0
  94. data/config/environments/test.rb +7 -0
  95. data/config/initializers/action_mailer.rb +1 -3
  96. data/config/initializers/application_controller_renderer.rb +9 -0
  97. data/config/initializers/assets.rb +6 -11
  98. data/config/initializers/backtrace_silencers.rb +0 -6
  99. data/config/initializers/content_security_policy.rb +26 -0
  100. data/config/initializers/cookies_serializer.rb +3 -6
  101. data/config/initializers/devise.rb +289 -0
  102. data/config/initializers/filter_parameter_logging.rb +0 -5
  103. data/config/initializers/gravatar.rb +0 -1
  104. data/config/initializers/inflections.rb +0 -6
  105. data/config/initializers/mime_types.rb +1 -9
  106. data/config/initializers/new_framework_defaults_5_2.rb +40 -0
  107. data/config/initializers/relative_url_root.rb +1 -3
  108. data/config/initializers/session_store.rb +1 -3
  109. data/config/initializers/wrap_parameters.rb +4 -9
  110. data/config/locales/fat_free_crm.en-GB.yml +5 -5
  111. data/config/locales/fat_free_crm.en-US.yml +5 -5
  112. data/config/locales/fat_free_crm.fr.yml +1 -1
  113. data/config/locales/fat_free_crm.ru.yml +1 -0
  114. data/config/routes.rb +20 -9
  115. data/db/demo/users.yml +62 -81
  116. data/db/migrate/20100928030620_remove_uuid.rb +1 -2
  117. data/db/migrate/20120316045804_activities_to_versions.rb +1 -0
  118. data/db/migrate/20120510025219_add_not_null_constraints_for_timestamp_columns.rb +1 -0
  119. data/db/migrate/20180107082701_authlogic_to_devise.rb +58 -0
  120. data/db/schema.rb +48 -43
  121. data/docker-compose.yml +10 -0
  122. data/fat_free_crm.gemspec +11 -13
  123. data/lib/development_tasks/license.rake +2 -2
  124. data/lib/fat_free_crm/callback.rb +2 -2
  125. data/lib/fat_free_crm/comment_extensions.rb +2 -4
  126. data/lib/fat_free_crm/core_ext/string.rb +1 -1
  127. data/lib/fat_free_crm/engine.rb +1 -1
  128. data/lib/fat_free_crm/errors.rb +1 -1
  129. data/lib/fat_free_crm/export_csv.rb +1 -0
  130. data/lib/fat_free_crm/exportable.rb +1 -1
  131. data/lib/fat_free_crm/fields.rb +1 -1
  132. data/lib/fat_free_crm/gem_dependencies.rb +1 -1
  133. data/lib/fat_free_crm/gem_ext/simple_form/action_view_extensions/form_helper.rb +1 -3
  134. data/lib/fat_free_crm/i18n.rb +2 -2
  135. data/lib/fat_free_crm/mail_processor/base.rb +4 -10
  136. data/lib/fat_free_crm/mail_processor/dropbox.rb +5 -15
  137. data/lib/fat_free_crm/permissions.rb +7 -4
  138. data/lib/fat_free_crm/sortable.rb +1 -1
  139. data/lib/fat_free_crm/tabs.rb +2 -2
  140. data/lib/fat_free_crm/version.rb +2 -2
  141. data/lib/gravatar_image_tag.rb +7 -8
  142. data/lib/missing_translation_detector.rb +1 -0
  143. data/lib/tasks/ffcrm/missing_translations.rake +1 -0
  144. data/lib/tasks/ffcrm/setup.rake +10 -1
  145. data/lib/tasks/ffcrm/update_data.rake +2 -2
  146. data/script/rails +2 -2
  147. data/spec/controllers/admin/users_controller_spec.rb +0 -56
  148. data/spec/controllers/comments_controller_spec.rb +6 -6
  149. data/spec/controllers/entities/campaigns_controller_spec.rb +1 -1
  150. data/spec/controllers/entities/contacts_controller_spec.rb +2 -1
  151. data/spec/controllers/entities/leads_controller_spec.rb +2 -2
  152. data/spec/controllers/entities/opportunities_controller_spec.rb +1 -1
  153. data/spec/controllers/entities_controller_spec.rb +5 -0
  154. data/spec/controllers/home_controller_spec.rb +5 -5
  155. data/spec/controllers/tasks_controller_spec.rb +6 -4
  156. data/spec/controllers/users_controller_spec.rb +28 -98
  157. data/spec/factories/account_factories.rb +5 -5
  158. data/spec/factories/campaign_factories.rb +3 -3
  159. data/spec/factories/contact_factories.rb +8 -8
  160. data/spec/factories/field_factories.rb +4 -3
  161. data/spec/factories/lead_factories.rb +5 -5
  162. data/spec/factories/list_factories.rb +2 -2
  163. data/spec/factories/opportunity_factories.rb +3 -3
  164. data/spec/factories/setting_factories.rb +2 -2
  165. data/spec/factories/shared_factories.rb +11 -9
  166. data/spec/factories/task_factories.rb +7 -7
  167. data/spec/factories/user_factories.rb +16 -19
  168. data/spec/features/admin/groups_spec.rb +1 -1
  169. data/spec/features/admin/users_spec.rb +3 -1
  170. data/spec/features/campaigns_spec.rb +1 -1
  171. data/spec/features/contacts_spec.rb +1 -1
  172. data/spec/features/dashboard_spec.rb +1 -1
  173. data/spec/features/devise/sign_in_spec.rb +58 -0
  174. data/spec/features/devise/sign_up_spec.rb +36 -0
  175. data/spec/features/leads_spec.rb +1 -1
  176. data/spec/features/opportunities_overview_spec.rb +1 -1
  177. data/spec/features/opportunities_spec.rb +3 -3
  178. data/spec/features/support/browser.rb +2 -1
  179. data/spec/features/tasks_spec.rb +1 -1
  180. data/spec/helpers/admin/field_groups_helper_spec.rb +1 -1
  181. data/spec/helpers/users_helper_spec.rb +4 -4
  182. data/spec/lib/comment_extensions_spec.rb +10 -4
  183. data/spec/lib/errors_spec.rb +2 -2
  184. data/spec/lib/mail_processor/dropbox_spec.rb +1 -1
  185. data/spec/lib/mail_processor/sample_emails/dropbox.rb +8 -8
  186. data/spec/lib/permissions_spec.rb +8 -3
  187. data/spec/mailers/devise_mailer_spec.rb +35 -0
  188. data/spec/mailers/user_mailer_spec.rb +0 -26
  189. data/spec/models/entities/account_spec.rb +27 -0
  190. data/spec/models/entities/contact_spec.rb +96 -1
  191. data/spec/models/fields/custom_field_date_pair_spec.rb +4 -2
  192. data/spec/models/fields/custom_field_spec.rb +4 -2
  193. data/spec/models/observers/entity_observer_spec.rb +1 -1
  194. data/spec/models/polymorphic/version_spec.rb +7 -7
  195. data/spec/models/users/user_spec.rb +22 -26
  196. data/spec/routing/users_routing_spec.rb +30 -8
  197. data/spec/shared/controllers.rb +3 -9
  198. data/spec/spec_helper.rb +10 -2
  199. data/spec/support/assert_select.rb +1 -0
  200. data/spec/support/devise_helpers.rb +28 -0
  201. data/spec/{features/support/helpers.rb → support/feature_helpers.rb} +10 -10
  202. data/spec/support/macros.rb +4 -1
  203. data/spec/views/contacts/update.js.haml_spec.rb +1 -1
  204. data/spec/views/opportunities/update.js.haml_spec.rb +1 -1
  205. data/vendor/gems/ransack_ui-1.3.4/.gitignore +17 -0
  206. data/vendor/gems/ransack_ui-1.3.4/Gemfile +7 -0
  207. data/vendor/gems/ransack_ui-1.3.4/LICENSE.txt +22 -0
  208. data/vendor/gems/ransack_ui-1.3.4/README.md +57 -0
  209. data/vendor/gems/ransack_ui-1.3.4/Rakefile +1 -0
  210. data/vendor/gems/ransack_ui-1.3.4/app/assets/images/ransack_ui/calendar.png +0 -0
  211. data/vendor/gems/ransack_ui-1.3.4/app/assets/images/ransack_ui/delete.png +0 -0
  212. data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack/predicates.js.coffee +41 -0
  213. data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_bootstrap/button_group_select.js.coffee +26 -0
  214. data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_bootstrap/index.js.coffee +2 -0
  215. data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_jquery/index.js +2 -0
  216. data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_jquery/search_form.js.coffee.erb +499 -0
  217. data/vendor/gems/ransack_ui-1.3.4/app/assets/stylesheets/ransack_ui_bootstrap/index.css +3 -0
  218. data/vendor/gems/ransack_ui-1.3.4/app/assets/stylesheets/ransack_ui_bootstrap/search.css.scss +41 -0
  219. data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_condition_fields.html.erb +15 -0
  220. data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_grouping_fields.html.erb +16 -0
  221. data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_search.html.erb +29 -0
  222. data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_sort_fields.html.erb +4 -0
  223. data/vendor/gems/ransack_ui-1.3.4/config/locales/en.yml +24 -0
  224. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui.rb +9 -0
  225. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/adapters/active_record.rb +6 -0
  226. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/adapters/active_record/base.rb +46 -0
  227. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/controller_helpers.rb +18 -0
  228. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/rails/engine.rb +21 -0
  229. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/adapters/active_record/base.rb +47 -0
  230. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/configuration.rb +15 -0
  231. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/context.rb +9 -0
  232. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/helpers/form_builder.rb +262 -0
  233. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/nodes/attribute.rb +13 -0
  234. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/nodes/condition.rb +13 -0
  235. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/nodes/grouping.rb +20 -0
  236. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/version.rb +3 -0
  237. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/view_helpers.rb +30 -0
  238. data/vendor/gems/ransack_ui-1.3.4/ransack_ui.gemspec +23 -0
  239. metadata +79 -67
  240. data/app/controllers/authentications_controller.rb +0 -53
  241. data/app/models/users/authentication.rb +0 -56
  242. data/app/views/authentications/new.html.haml +0 -19
  243. data/app/views/passwords/edit.html.haml +0 -15
  244. data/app/views/passwords/new.html.haml +0 -10
  245. data/app/views/user_mailer/password_reset_instructions.html.haml +0 -6
  246. data/app/views/users/new.html.haml +0 -19
  247. data/spec/controllers/authentications_controller_spec.rb +0 -150
  248. data/spec/controllers/passwords_controller_spec.rb +0 -32
  249. data/spec/models/users/authentication_spec.rb +0 -19
  250. data/spec/support/auth_macros.rb +0 -49
  251. data/spec/views/authentications/new.haml_spec.rb +0 -31
@@ -16,15 +16,13 @@ class LeadObserver < ActiveRecord::Observer
16
16
 
17
17
  def after_update(item)
18
18
  original = @@leads.delete(item.id)
19
- if original&.status != "rejected" && item.status == "rejected"
20
- return log_activity(item, :reject)
21
- end
19
+ return log_activity(item, :reject) if original&.status != "rejected" && item.status == "rejected"
22
20
  end
23
21
 
24
22
  private
25
23
 
26
24
  def log_activity(item, event)
27
- item.send(item.class.versions_association_name).create(event: event, whodunnit: PaperTrail.whodunnit)
25
+ item.send(item.class.versions_association_name).create(event: event, whodunnit: PaperTrail.request.whodunnit)
28
26
  end
29
27
 
30
28
  ActiveSupport.run_load_hooks(:fat_free_crm_lead_observer, self)
@@ -11,9 +11,7 @@ class OpportunityObserver < ActiveRecord::Observer
11
11
  @@opportunities = {}
12
12
 
13
13
  def after_create(item)
14
- if item.campaign && item.stage == "won"
15
- update_campaign_revenue(item.campaign, item.amount.to_f - item.discount.to_f)
16
- end
14
+ update_campaign_revenue(item.campaign, item.amount.to_f - item.discount.to_f) if item.campaign && item.stage == "won"
17
15
  end
18
16
 
19
17
  def before_update(item)
@@ -38,7 +36,7 @@ class OpportunityObserver < ActiveRecord::Observer
38
36
  private
39
37
 
40
38
  def log_activity(item, event)
41
- item.send(item.class.versions_association_name).create(event: event, whodunnit: PaperTrail.whodunnit)
39
+ item.send(item.class.versions_association_name).create(event: event, whodunnit: PaperTrail.request.whodunnit)
42
40
  end
43
41
 
44
42
  def update_campaign_revenue(campaign, revenue)
@@ -26,7 +26,7 @@ class TaskObserver < ActiveRecord::Observer
26
26
  private
27
27
 
28
28
  def log_activity(item, event)
29
- item.send(item.class.versions_association_name).create(event: event, whodunnit: PaperTrail.whodunnit)
29
+ item.send(item.class.versions_association_name).create(event: event, whodunnit: PaperTrail.request.whodunnit)
30
30
  end
31
31
 
32
32
  ActiveSupport.run_load_hooks(:fat_free_crm_task_observer, self)
@@ -30,8 +30,8 @@
30
30
  #
31
31
 
32
32
  class Email < ActiveRecord::Base
33
- belongs_to :mediator, polymorphic: true
34
- belongs_to :user
33
+ belongs_to :mediator, polymorphic: true, optional: true # TODO: Is this really optional?
34
+ belongs_to :user, optional: true # TODO: Is this really optional?
35
35
 
36
36
  has_paper_trail class_name: 'Version', meta: { related: :mediator },
37
37
  ignore: [:state]
@@ -34,15 +34,15 @@ class Task < ActiveRecord::Base
34
34
  ALLOWED_VIEWS = %w[pending assigned completed]
35
35
 
36
36
  belongs_to :user
37
- belongs_to :assignee, class_name: "User", foreign_key: :assigned_to
38
- belongs_to :completor, class_name: "User", foreign_key: :completed_by
39
- belongs_to :asset, polymorphic: true
37
+ belongs_to :assignee, class_name: "User", foreign_key: :assigned_to, optional: true # TODO: Is this really optional?
38
+ belongs_to :completor, class_name: "User", foreign_key: :completed_by, optional: true # TODO: Is this really optional?
39
+ belongs_to :asset, polymorphic: true, optional: true # TODO: Is this really optional?
40
40
 
41
41
  serialize :subscribed_users, Array
42
42
 
43
43
  # Tasks created by the user for herself, or assigned to her by others. That's
44
44
  # what gets shown on Tasks/Pending and Tasks/Completed pages.
45
- scope :my, ->(*args) {
45
+ scope :my, lambda { |*args|
46
46
  options = args[0] || {}
47
47
  user_option = (options.is_a?(Hash) ? options[:user] : options) || User.current_user
48
48
  includes(:assignee)
@@ -55,24 +55,24 @@ class Task < ActiveRecord::Base
55
55
  scope :assigned_to, ->(user) { where(assigned_to: user.id) }
56
56
 
57
57
  # Tasks assigned by the user to others. That's what we see on Tasks/Assigned.
58
- scope :assigned_by, ->(user) {
58
+ scope :assigned_by, lambda { |user|
59
59
  includes(:assignee)
60
60
  .where('user_id = ? AND assigned_to IS NOT NULL AND assigned_to != ?', user.id, user.id)
61
61
  }
62
62
 
63
63
  # Tasks created by the user or assigned to the user, i.e. the union of the two
64
64
  # scopes above. That's the tasks the user is allowed to see and track.
65
- scope :tracked_by, ->(user) {
65
+ scope :tracked_by, lambda { |user|
66
66
  includes(:assignee)
67
67
  .where('user_id = ? OR assigned_to = ?', user.id, user.id)
68
68
  }
69
69
 
70
70
  # Show tasks which either belong to the user and are unassigned, or are assigned to the user
71
- scope :visible_on_dashboard, ->(user) {
71
+ scope :visible_on_dashboard, lambda { |user|
72
72
  where('(user_id = :user_id AND assigned_to IS NULL) OR assigned_to = :user_id', user_id: user.id).where('completed_at IS NULL')
73
73
  }
74
74
 
75
- scope :by_due_at, -> {
75
+ scope :by_due_at, lambda {
76
76
  order({
77
77
  "MySQL" => "due_at NOT NULL, due_at ASC",
78
78
  "PostgreSQL" => "due_at ASC NULLS FIRST"
@@ -101,7 +101,7 @@ class Task < ActiveRecord::Base
101
101
  scope :completed_this_month, -> { where('completed_at >= ? AND completed_at < ?', Time.zone.now.beginning_of_month.utc, Time.zone.now.beginning_of_week.utc - 7.days) }
102
102
  scope :completed_last_month, -> { where('completed_at >= ? AND completed_at < ?', (Time.zone.now.beginning_of_month.utc - 1.day).beginning_of_month.utc, Time.zone.now.beginning_of_month.utc) }
103
103
 
104
- scope :text_search, ->(query) {
104
+ scope :text_search, lambda { |query|
105
105
  query = query.gsub(/[^\w\s\-\.'\p{L}]/u, '').strip
106
106
  where('upper(name) LIKE upper(?)', "%#{query}%")
107
107
  }
@@ -155,6 +155,7 @@ class Task < ActiveRecord::Base
155
155
  #----------------------------------------------------------------------------
156
156
  def computed_bucket
157
157
  return bucket if bucket != "specific_time"
158
+
158
159
  if overdue?
159
160
  "overdue"
160
161
  elsif due_today?
@@ -174,6 +175,7 @@ class Task < ActiveRecord::Base
174
175
  #----------------------------------------------------------------------------
175
176
  def self.find_all_grouped(user, view)
176
177
  return {} unless ALLOWED_VIEWS.include?(view)
178
+
177
179
  settings = (view == "completed" ? Setting.task_completed : Setting.task_bucket)
178
180
  Hash[
179
181
  settings.map do |key, _value|
@@ -186,6 +188,7 @@ class Task < ActiveRecord::Base
186
188
  #----------------------------------------------------------------------------
187
189
  def self.bucket_empty?(bucket, user, view = "pending")
188
190
  return false if bucket.blank? || !ALLOWED_VIEWS.include?(view)
191
+
189
192
  if view == "assigned"
190
193
  assigned_by(user).send(bucket).pending.count
191
194
  else
@@ -197,6 +200,7 @@ class Task < ActiveRecord::Base
197
200
  #----------------------------------------------------------------------------
198
201
  def self.totals(user, view = "pending")
199
202
  return {} unless ALLOWED_VIEWS.include?(view)
203
+
200
204
  settings = (view == "completed" ? Setting.task_completed : Setting.task_bucket)
201
205
  settings.each_with_object(HashWithIndifferentAccess[all: 0]) do |key, hash|
202
206
  hash[key] = (view == "assigned" ? assigned_by(user).send(key).pending.count : my(user).send(key).send(view).count)
@@ -12,8 +12,8 @@ class Version < PaperTrail::Version
12
12
  EVENTS = %w[all_events create view update destroy]
13
13
  DURATION = %w[one_hour one_day two_days one_week two_weeks one_month]
14
14
 
15
- belongs_to :related, polymorphic: true
16
- belongs_to :user, foreign_key: :whodunnit
15
+ belongs_to :related, polymorphic: true, optional: true # TODO: Is this really optional?
16
+ belongs_to :user, foreign_key: :whodunnit, optional: true # TODO: Is this really optional?
17
17
 
18
18
  scope :default_order, -> { order('created_at DESC') }
19
19
  scope :include_events, ->(*events) { where(event: events) }
@@ -35,6 +35,7 @@ class Version < PaperTrail::Version
35
35
  .default_order
36
36
 
37
37
  break if query.empty?
38
+
38
39
  versions += query.select { |v| v.item.present? }
39
40
  versions.uniq! { |v| [v.item_id, v.item_type] }
40
41
  offset += limit * 2
@@ -55,6 +55,7 @@ class Setting < ActiveRecord::Base
55
55
  def [](name)
56
56
  # Return value if cached
57
57
  return cache[name] if cache.key?(name)
58
+
58
59
  # Check database
59
60
  if database_and_table_exists?
60
61
  if setting = find_by_name(name.to_s)
@@ -69,6 +70,7 @@ class Setting < ActiveRecord::Base
69
70
  #-------------------------------------------------------------------
70
71
  def []=(name, value)
71
72
  return nil unless database_and_table_exists?
73
+
72
74
  setting = find_by_name(name.to_s) || new(name: name)
73
75
  setting.value = value
74
76
  setting.save
@@ -18,9 +18,9 @@
18
18
  #
19
19
 
20
20
  class Permission < ActiveRecord::Base
21
- belongs_to :user
22
- belongs_to :group
23
- belongs_to :asset, polymorphic: true
21
+ belongs_to :user, optional: true
22
+ belongs_to :group, optional: true
23
+ belongs_to :asset, polymorphic: true, optional: true
24
24
 
25
25
  validates_presence_of :user_id, unless: :group_id?
26
26
  validates_presence_of :group_id, unless: :user_id?
@@ -18,7 +18,7 @@
18
18
  #
19
19
 
20
20
  class Preference < ActiveRecord::Base
21
- belongs_to :user
21
+ belongs_to :user, optional: true
22
22
 
23
23
  #-------------------------------------------------------------------
24
24
  def [](name)
@@ -26,6 +26,7 @@ class Preference < ActiveRecord::Base
26
26
  return super(name) if name.to_s == "user_id" # get the value of belongs_to
27
27
 
28
28
  return cached_prefs[name.to_s] if cached_prefs.key?(name.to_s)
29
+
29
30
  cached_prefs[name.to_s] = if user.present? && pref = Preference.find_by_name_and_user_id(name.to_s, user.id)
30
31
  Marshal.load(Base64.decode64(pref.value))
31
32
  end
@@ -11,7 +11,7 @@
11
11
  #
12
12
  # id :integer not null, primary key
13
13
  # username :string(32) default(""), not null
14
- # email :string(64) default(""), not null
14
+ # email :string(254) default(""), not null
15
15
  # first_name :string(32)
16
16
  # last_name :string(32)
17
17
  # title :string(64)
@@ -23,24 +23,34 @@
23
23
  # yahoo :string(32)
24
24
  # google :string(32)
25
25
  # skype :string(32)
26
- # password_hash :string(255) default(""), not null
26
+ # encrypted_password :string(255) default(""), not null
27
27
  # password_salt :string(255) default(""), not null
28
- # persistence_token :string(255) default(""), not null
29
- # perishable_token :string(255) default(""), not null
30
- # last_login_at :datetime
31
- # current_login_at :datetime
32
- # last_login_ip :string(255)
33
- # current_login_ip :string(255)
34
- # login_count :integer default(0), not null
28
+ # last_sign_in_at :datetime
29
+ # current_sign_in_at :datetime
30
+ # last_sign_in_ip :string(255)
31
+ # current_sign_in_ip :string(255)
32
+ # sign_in_count :integer default(0), not null
35
33
  # deleted_at :datetime
36
34
  # created_at :datetime
37
35
  # updated_at :datetime
38
36
  # admin :boolean default(FALSE), not null
39
37
  # suspended_at :datetime
40
- # single_access_token :string(255)
38
+ # unconfirmed_email :string(254) default(""), not null
39
+ # reset_password_token :string(255)
40
+ # reset_password_sent_at :datetime
41
+ # remember_token :string(255)
42
+ # remember_created_at :datetime
43
+ # authentication_token :string(255)
44
+ # confirmation_token :string(255)
45
+ # confirmed_at :datetime
46
+ # confirmation_sent_at :datetime
41
47
  #
42
48
 
43
49
  class User < ActiveRecord::Base
50
+ devise :database_authenticatable, :registerable, :confirmable,
51
+ :encryptable, :recoverable, :rememberable, :trackable
52
+ before_create :suspend_if_needs_approval
53
+
44
54
  has_one :avatar, as: :entity, dependent: :destroy # Personal avatar.
45
55
  has_many :avatars # As owner who uploaded it, ex. Contact avatar.
46
56
  has_many :comments, as: :commentable # As owner who created a comment.
@@ -55,40 +65,38 @@ class User < ActiveRecord::Base
55
65
  has_many :lists
56
66
  has_and_belongs_to_many :groups
57
67
 
58
- has_paper_trail class_name: 'Version', ignore: [:perishable_token]
68
+ has_paper_trail class_name: 'Version', ignore: [:last_sign_in_at]
59
69
 
60
70
  scope :by_id, -> { order('id DESC') }
61
- scope :without, ->(user) { where('id != ?', user.id).by_name }
71
+ # TODO: /home/clockwerx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activerecord-5.2.3/lib/active_record/scoping/named.rb:175:in `scope': You tried to define a scope named "without" on the model "User", but ActiveRecord::Relation already defined an instance method with the same name. (ArgumentError)
72
+ scope :without_user, ->(user) { where('id != ?', user.id).by_name }
62
73
  scope :by_name, -> { order('first_name, last_name, email') }
63
74
 
64
- scope :text_search, ->(query) {
75
+ scope :text_search, lambda { |query|
65
76
  query = query.gsub(/[^\w\s\-\.'\p{L}]/u, '').strip
66
77
  where('upper(username) LIKE upper(:s) OR upper(email) LIKE upper(:s) OR upper(first_name) LIKE upper(:s) OR upper(last_name) LIKE upper(:s)', s: "%#{query}%")
67
78
  }
68
79
 
69
80
  scope :my, ->(current_user) { accessible_by(current_user.ability) }
70
81
 
71
- scope :have_assigned_opportunities, -> {
82
+ scope :have_assigned_opportunities, lambda {
72
83
  joins("INNER JOIN opportunities ON users.id = opportunities.assigned_to")
73
84
  .where("opportunities.stage <> 'lost' AND opportunities.stage <> 'won'")
74
85
  .select('DISTINCT(users.id), users.*')
75
86
  }
76
87
 
77
- acts_as_authentic do |c|
78
- c.session_class = Authentication
79
- c.validates_uniqueness_of_login_field_options = { case_sensitive: false, message: :username_taken }
80
- c.validates_length_of_login_field_options = { minimum: 1, message: :missing_username }
81
- c.validates_uniqueness_of_email_field_options = { message: :email_in_use }
82
- c.validates_length_of_password_field_options = { minimum: 0, allow_blank: true, if: :require_password? }
83
- c.ignore_blank_passwords = true
84
- c.crypto_provider = Authlogic::CryptoProviders::Sha512
85
- end
86
-
87
- # Store current user in the class so we could access it from the activity
88
- # observer without extra authentication query.
89
- cattr_accessor :current_user
90
-
91
- validates_presence_of :email, message: :missing_email
88
+ validates :email,
89
+ presence: { message: :missing_email },
90
+ length: { minimum: 3, maximum: 254 },
91
+ uniqueness: { message: :email_in_use, case_sensitive: false },
92
+ format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }
93
+ validates :username,
94
+ uniqueness: { message: :username_taken, case_sensitive: false },
95
+ presence: { message: :missing_username },
96
+ format: { with: /[a-z0-9_-]+/i }
97
+ validates :password,
98
+ presence: { if: :password_required? },
99
+ confirmation: true
92
100
 
93
101
  #----------------------------------------------------------------------------
94
102
  def name
@@ -107,7 +115,23 @@ class User < ActiveRecord::Base
107
115
 
108
116
  #----------------------------------------------------------------------------
109
117
  def awaits_approval?
110
- suspended? && login_count == 0 && Setting.user_signup == :needs_approval
118
+ suspended? && sign_in_count == 0 && Setting.user_signup == :needs_approval
119
+ end
120
+
121
+ def active_for_authentication?
122
+ super && confirmed? && !awaits_approval? && !suspended?
123
+ end
124
+
125
+ def inactive_message
126
+ if !confirmed?
127
+ super
128
+ elsif awaits_approval?
129
+ I18n.t(:msg_account_not_approved)
130
+ elsif suspended?
131
+ I18n.t(:msg_invalig_login)
132
+ else
133
+ super
134
+ end
111
135
  end
112
136
 
113
137
  #----------------------------------------------------------------------------
@@ -116,12 +140,6 @@ class User < ActiveRecord::Base
116
140
  end
117
141
  alias pref preference
118
142
 
119
- #----------------------------------------------------------------------------
120
- def deliver_password_reset_instructions!
121
- reset_perishable_token!
122
- UserMailer.password_reset_instructions(self).deliver_now
123
- end
124
-
125
143
  # Override global I18n.locale if the user has individual local preference.
126
144
  #----------------------------------------------------------------------------
127
145
  def set_individual_locale
@@ -130,10 +148,6 @@ class User < ActiveRecord::Base
130
148
 
131
149
  # Generate the value of single access token if it hasn't been set already.
132
150
  #----------------------------------------------------------------------------
133
- def set_single_access_token
134
- self.single_access_token ||= update_attribute(:single_access_token, Authlogic::Random.friendly_token)
135
- end
136
-
137
151
  def to_json(_options = nil)
138
152
  [name].to_json
139
153
  end
@@ -142,6 +156,10 @@ class User < ActiveRecord::Base
142
156
  [name].to_xml
143
157
  end
144
158
 
159
+ def password_required?
160
+ !persisted? || !password.nil? || !password_confirmation.nil?
161
+ end
162
+
145
163
  # Returns permissions ability object.
146
164
  #----------------------------------------------------------------------------
147
165
  def ability
@@ -171,14 +189,21 @@ class User < ActiveRecord::Base
171
189
  !sum.nil?
172
190
  end
173
191
 
174
- private
175
-
176
192
  # Define class methods
177
193
  #----------------------------------------------------------------------------
178
194
  class << self
179
195
  def can_signup?
180
196
  %i[allowed needs_approval].include? Setting.user_signup
181
197
  end
198
+
199
+ # Overrides Devise sign-in to use either username or email (case-insensitive)
200
+ #----------------------------------------------------------------------------
201
+ def find_for_database_authentication(warden_conditions)
202
+ conditions = warden_conditions.dup
203
+ if login = conditions.delete(:email)
204
+ where(conditions.to_h).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first
205
+ end
206
+ end
182
207
  end
183
208
 
184
209
  ActiveSupport.run_load_hooks(:fat_free_crm_user, self)
@@ -8,7 +8,7 @@
8
8
  %tr
9
9
  %td
10
10
  .label #{t :assigned_to}:
11
- = user_select(:account, User.without(current_user), current_user)
11
+ = user_select(:account, User.without_user(current_user), current_user)
12
12
  %td= spacer
13
13
  %td
14
14
  .label #{t :category}:
@@ -28,4 +28,4 @@
28
28
  - elsif params[:cancel].false? # Called from title of the landing page...
29
29
  $('#edit_#{entity_name}').html('#{ j render(partial: "edit") }');
30
30
  crm.flip_form('edit_#{entity_name}');
31
- crm.set_title('edit_#{entity_name}', "#{t :edit} #{h @entity.name}");
31
+ crm.set_title('edit_#{entity_name}', "#{t :edit} #{j @entity.name}");
@@ -5,7 +5,7 @@
5
5
  - if @entity.errors.empty?
6
6
  - if called_from_landing_page?
7
7
  crm.flip_form('edit_#{entity_name}');
8
- crm.set_title('edit_#{entity_name}', '#{h @entity.name}');
8
+ crm.set_title('edit_#{entity_name}', '#{j @entity.name}');
9
9
  = refresh_sidebar(:show, :summary)
10
10
  - else
11
11
  $('##{id}').replaceWith('#{ j render(partial: entity_name, collection: [ @entity ]) }');
@@ -16,4 +16,4 @@
16
16
  $('##{id}').effect("shake", { duration:250, distance: 6 });
17
17
  $('##{dom_id(@entity, :edit)} input[type!=hidden]').first().focus();
18
18
 
19
- = hook(:entity_update, self, {entity: @entity})
19
+ = hook(:entity_update, self, {entity: @entity})
@@ -34,14 +34,14 @@
34
34
  = " " + t(:at) + " " + user.company unless user.company.blank?
35
35
 
36
36
  %span.black= "|"
37
- - if user.current_login_at && user.login_count > 0
38
- = t('pluralize.login', user.login_count)
37
+ - if user.last_sign_in_at && user.sign_in_count > 0
38
+ = t('pluralize.login', user.sign_in_count)
39
39
  %span.black= "|"
40
40
 
41
41
  - if user.awaits_approval?
42
42
  %b.cool= t(:user_awaits_approval)
43
- - elsif user.current_login_at
44
- %span.cool= t(:last_logged_in, timeago(user.current_login_at)).html_safe
43
+ - elsif user.last_sign_in_at
44
+ %span.cool= t(:last_logged_in, timeago(user.current_sign_in_at)).html_safe
45
45
  - else
46
46
  %span.warn= t(:user_never_logged_in)
47
47
  %dt{ style: "padding: 2px 0px 0px 0px" }