This repository has been archived by the owner on Mar 27, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 283
/
group.rb
306 lines (267 loc) · 12.1 KB
/
group.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# == Schema Information
#
# Table name: groups
#
# id :integer not null, primary key
# name :string(100)
# description :string(500)
# meets :string(100)
# location :string(100)
# directions :string(500)
# other_notes :string(500)
# creator_id :integer
# address :string(255)
# members_send :boolean default(TRUE)
# private :boolean
# category :string(50)
# leader_id :integer
# updated_at :datetime
# hidden :boolean
# approved :boolean
# link_code :string(255)
# parents_of :integer
# site_id :integer
# blog :boolean default(TRUE)
# email :boolean default(TRUE)
# prayer :boolean default(TRUE)
# attendance :boolean default(TRUE)
# legacy_id :integer
# gcal_private_link :string(255)
# approval_required_to_join :boolean default(TRUE)
# pictures :boolean default(TRUE)
# cm_api_list_id :string(50)
#
class Group < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :membership_requests, :dependent => :destroy
has_many :people, :through => :memberships, :order => 'last_name, first_name'
has_many :admins, :through => :memberships, :source => :person, :order => 'last_name, first_name', :conditions => ['memberships.admin = ?', true]
has_many :messages, :conditions => 'parent_id is null', :order => 'updated_at desc', :dependent => :destroy
has_many :notes, :order => 'created_at desc'
has_many :prayer_requests, :order => 'created_at desc'
has_many :attendance_records
has_many :albums
has_many :stream_items, :dependent => :destroy
has_many :attachments, :dependent => :delete_all
belongs_to :creator, :class_name => 'Person', :foreign_key => 'creator_id'
belongs_to :leader, :class_name => 'Person', :foreign_key => 'leader_id'
belongs_to :parents_of_group, :class_name => 'Group', :foreign_key => 'parents_of'
belongs_to :site
scope :active, :conditions => {:hidden => false}
scope_by_site_id
attr_accessible :name, :description, :meets, :location, :directions, :other_notes, :address, :members_send, :private, :category, :leader_id, :blog, :email, :prayer, :attendance, :gcal_private_link, :approval_required_to_join, :pictures, :cm_api_list_id
attr_accessible :approved, :link_code, :parents_of, :hidden, :if => Proc.new { Person.logged_in && Person.logged_in.admin?(:manage_groups) }
validates_presence_of :name
validates_presence_of :category
validates_uniqueness_of :name
validates_format_of :address, :with => /^[a-zA-Z0-9]+$/, :allow_nil => true
validates_uniqueness_of :address, :allow_nil => true
validates_length_of :address, :in => 2..30, :allow_nil => true
validates_uniqueness_of :cm_api_list_id, :allow_nil => true, :allow_blank => true
serialize :cached_parents
def validate
begin
errors.add('parents_of', :points_to_self) if not new_record? and parents_of == id
rescue
puts 'error checking for self-referencing parents_of (OK if you are migrating)'
end
end
has_one_photo :path => "#{DB_PHOTO_PATH}/groups", :sizes => PHOTO_SIZES
acts_as_logger LogItem
alias_method 'photo_without_logging=', 'photo='
def photo=(p)
LogItem.create :loggable_type => 'Group', :loggable_id => id, :object_changes => {'photo' => (p ? 'changed' : 'removed')}, :person => Person.logged_in
self.photo_without_logging = p
end
scope :checkin_destinations, :include => :group_times, :conditions => ['group_times.checkin_time_id is not null'], :order => 'group_times.ordering'
def name_group # returns something like "Morgan group"
"#{name}#{name =~ /group$/i ? '' : ' group'}"
end
def inspect
"<#{name}>"
end
def self.can_create?
Site.current.max_groups.nil? or Group.active.count < Site.current.max_groups
end
def admin?(person, exclude_global_admins=false)
if person
if exclude_global_admins
admins.include? person
else
person.admin?(:manage_groups) or admins.include? person
end
end
end
def last_admin?(person)
person and admin?(person, :exclude_global_admins) and admins.length == 1
end
def linked?
link_code and link_code.any?
end
def mapable?
# must look like ", OK 74137"
# TODO: this needs some work to be usable in other countries
location.to_s.any? && location =~ /,\s[A-Z]{2}\s+\d{5}/ ? true : false
end
def address=(a)
write_attribute(:address, a == '' ? nil : a)
end
def get_options_for(person)
memberships.find_by_person_id(person.id)
end
def set_options_for(person, options)
memberships.find_by_person_id(person.id).update_attributes!(options)
end
after_save :update_memberships
def update_memberships
if parents_of
parents = Group.find(parents_of).people.map { |p| p.parents }.flatten.uniq
update_membership_associations(parents)
elsif linked?
conditions = []
link_code.downcase.split.each do |code|
conditions.add_condition ["#{sql_lcase('classes')} = ? or classes like ? or classes like ? or classes like ? or classes like ? or classes like ?", code, "#{code},%", "%,#{code}", "%,#{code},%", "#{code}[%", "%,#{code}[%"], 'or'
end
update_membership_associations(Person.find(:all, :conditions => conditions))
elsif Membership.column_names.include?('auto')
memberships.find_all_by_auto(true).each { |m| m.destroy }
end
if respond_to?(:cm_api_list_id) and cm_api_list_id.to_s.any? and Setting.get(:services, :campaign_monitor_api_key).to_s.any?
sync_with_campaign_monitor
end
end
def update_membership_associations(new_people)
new_people.reject! { |p| p.deleted? }
self.people.reload
(new_people - self.people).each { |p| memberships.create!(:person => p, :auto => true) }
ids_to_delete = (self.people - new_people).each { |p| p.id }
Membership.delete_all(["person_id in (?) and auto = ?", ids_to_delete, true])
end
def can_send?(person)
(members_send and person.member_of?(self) and person.messages_enabled?) or admin?(person)
end
alias_method 'can_post?', 'can_send?'
def can_share?(person)
person.member_of?(self) and \
(
(email? and can_post?(person)) or \
blog? or \
pictures? or \
prayer?
)
end
def full_address
address.to_s.any? ? (address + '@' + Site.current.host) : nil
end
def get_people_attendance_records_for_date(date)
records = {}
people.each { |p| records[p.id] = [p, false] }
date = Date.parse(date) if(date.is_a?(String))
attendance_records.all(:conditions => ['attended_at >= ? and attended_at <= ?', date.strftime('%Y-%m-%d 0:00'), date.strftime('%Y-%m-%d 23:59:59')]).each do |record|
records[record.person.id] = [record.person, record]
end
records.values.sort_by { |r| [r[0].last_name, r[0].first_name] }
end
def attendance_dates
attendance_records.find_by_sql("select distinct attended_at from attendance_records where group_id = #{id} order by attended_at desc").map { |r| r.attended_at }
end
def gcal_url
if gcal_private_link.to_s.any?
if token = gcal_token
"https://www.google.com/calendar/embed?pvttk=#{token}&showTitle=0&showCalendars=0&showTz=1&height=600&wkst=1&bgcolor=%23FFFFFF&src=#{gcal_account}&color=%23A32929&ctz=#{Time.zone.tzinfo.name}"
end
end
end
def gcal_account
gcal_private_link.to_s.match(/[a-z0-9\._]+(@|%40)[a-z\.]+/).to_s.sub(/@/, '%40')
end
def gcal_token
gcal_private_link.to_s.match(/private\-([a-z0-9]+)/)[1] rescue ''
end
def shared_stream_items(count)
items = stream_items.all(
:order => 'stream_items.created_at desc',
:limit => count,
:include => :person
)
# do our own eager loading here...
comment_people_ids = items.map { |s| s.context['comments'].to_a.map { |c| c['person_id'] } }.flatten
comment_people = Person.all(
:conditions => ["id in (?)", comment_people_ids],
:select => 'first_name, last_name, suffix, gender, id, family_id, updated_at' # only what's needed
).inject({}) { |h, p| h[p.id] = p; h } # as a hash with id as the key
items.each do |stream_item|
stream_item.context['comments'].to_a.each do |comment|
comment['person'] = comment_people[comment['person_id']]
end
stream_item.readonly!
end
items
end
before_destroy :remove_parent_of_links
def remove_parent_of_links
Group.find_all_by_parents_of(id).each { |g| g.update_attribute(:parents_of, nil) }
end
def sync_with_campaign_monitor
return unless (api_key = Setting.get(:services, :campaign_monitor_api_key)).to_s.any?
return unless cm_api_list_id.to_s.any?
cm = CampaignMonitorParty.new(api_key)
in_group = self.people.all(:conditions => ['memberships.get_email = ?', true]).select { |p| p.email.to_s.any? }
unsubscribed = cm.Subscribers.GetUnsubscribed('ListID' => cm_api_list_id, 'Date' => '2000-01-01 00:00:00')['anyType']['Subscriber'].to_a.map { |s| [s['Name'], s['EmailAddress']] }
# ensure we don't resubscribe someone who has already unsubscribed
# (and also set their get_email attribute to false in the group)
upload_to_cm = []
in_group.each do |person|
if unsubscribed.map { |p| p[1].downcase }.include?(person.email.downcase)
person.memberships.find_by_group_id(self.id).update_attribute(:get_email, false)
else
upload_to_cm << [person.name, person.email]
end
end
# unsubscribe addresses in the subscriber list but not found in the group
subscribed = cm.Subscribers.GetActive('ListID' => cm_api_list_id, 'Date' => '2000-01-01 00:00:00')['anyType']['Subscriber'].to_a.map { |s| [s['Name'], s['EmailAddress']] }
subscribed.each do |name, email|
if not upload_to_cm.any? { |n, e| e.downcase == email.downcase }
cm.Subscriber.Unsubscribe('ListID' => cm_api_list_id, 'Email' => email)
end
end
# subscribe addresses in the group but not in the subscriber list
upload_to_cm.each do |name, email|
if not subscribed.any? { |n, e| e.downcase == email.downcase }
cm.Subscriber.Add('ListID' => cm_api_list_id, 'Email' => email, 'Name' => name)
end
end
end
class << self
def update_memberships
all(:order => 'parents_of').each { |group| group.update_memberships }
end
def categories
returning({}) do |cats|
if Person.logged_in.admin?(:manage_groups)
results = Group.find_by_sql("select category, count(*) as group_count from groups where category is not null and category != '' and category != 'Subscription' group by category").map { |g| [g.category, g.group_count] }
else
results = Group.find_by_sql(["select category, count(*) as group_count from groups where category is not null and category != '' and category != 'Subscription' and hidden = ? and approved = ? group by category", false, true]).map { |g| [g.category, g.group_count] }
end
results.each do |cat, count|
cats[cat] = count.to_i
end
end
end
def count_by_type
{
:normal => Group.count('id', :conditions => {:hidden => false, :private => false}),
:hidden => Group.count('id', :conditions => {:hidden => true, :private => false}),
:private => Group.count('id', :conditions => {:private => true, :hidden => false}),
:private_and_hidden => Group.count('id', :conditions => {:private => true, :hidden => true })
}.reject { |k, v| v == 0 }
end
def count_by_linked
{
:unlinked => Group.count('id', :conditions => "parents_of is null and (link_code is null or link_code = '')"),
:linked => Group.count('id', :conditions => "link_code is not null and link_code != ''"),
:parents => Group.count('id', :conditions => "parents_of is not null")
}.reject { |k, v| v == 0 }
end
end
end