osm 1.2.24 → 1.2.25

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c000055060e9bf9c68a29ee0a70c9904f65a632d
4
- data.tar.gz: b3ea3125f4a8e0cd3af8cd79f7adeaa8a2c497d9
3
+ metadata.gz: 4ef940d8e32de06f687dcb5632b3a2b98389927b
4
+ data.tar.gz: 637568e6e8cb0c2cfdee813869a8b949b308f42a
5
5
  SHA512:
6
- metadata.gz: 014a4e51a7e8ab1b2d85beeeeb58bfe440bcbeabdbe489d52b020b07620da2916f55aed25b5b2342a8b4db3a9ac6614e173f78c66e98d588d2f7ad8affc8a383
7
- data.tar.gz: f559e2d25bbb91459322012301a61bdaf5e9a29d766727e94b8ee6db9343194b2312a74c83d25c6abe5607d57d61ce3ff2f3368e3f24877b708b78e18a9b79a6
6
+ metadata.gz: a9ccff69771e68971f65b5c9251c19bf6269d9fd14724e1421d417eba447a28ec3b8c5a609bb9a3793c9e0b6c4300b0579a9456a5fb468d32231584344dbdbe5
7
+ data.tar.gz: b4a25bc8837f41b43d54586318f79eb6cd32792954db3a70fcc81a0c998929cc8b0103705400ba92494896f97c961b9d0b92f6fc43341d7742545ff1c4e9092b
@@ -1,3 +1,10 @@
1
+ ## Version 1.2.25
2
+
3
+ * Add fetching My.SCOUT parent login history - Osm::Myscout::ParentLoginHistory.get_for_section
4
+ * Add fetching and updating of My.SCOUT templates
5
+ * Add fetching of email delivery reports -> Osm::Email::Delivery class and subclasses
6
+ * Send an email through OSM (use Osm::Email.get_emails_for_contacts method to get the value for Osm::Email.send_email method's send_to parameter).
7
+
1
8
  ## Version 1.2.24
2
9
 
3
10
  * Fix detection of no roles in api.get_user_roles!
data/README.md CHANGED
@@ -89,7 +89,7 @@ however it should be noted that when the OSM API adds a feature it can be diffic
89
89
 
90
90
  ## Parts of the OSM API Supported:
91
91
 
92
- ### Read
92
+ ### Retrieve
93
93
  * Activity
94
94
  * API Access
95
95
  * API Access for our app
@@ -100,6 +100,7 @@ however it should be noted that when the OSM API adds a feature it can be diffic
100
100
  * Badge stock levels
101
101
  * Budgets (Gold required)
102
102
  * Due Badges
103
+ * Email delivery reports
103
104
  * Evening
104
105
  * Event (Silver required)
105
106
  * Events (Silver required)
@@ -163,6 +164,7 @@ however it should be noted that when the OSM API adds a feature it can be diffic
163
164
  * Authorise an app to use the API on a user's behalf
164
165
  * Add activity to programme
165
166
  * Send an SMS to member(s)
167
+ * Send an Email to member(s)
166
168
 
167
169
  ## Parts not/never supported
168
170
  * Campsite Directory
data/lib/osm.rb CHANGED
@@ -3,6 +3,7 @@ require 'active_attr'
3
3
  require 'active_support'
4
4
  require 'active_model'
5
5
  require 'date'
6
+ require 'time'
6
7
  require 'httparty'
7
8
  require 'dirty_hashy'
8
9
 
@@ -88,13 +89,7 @@ module Osm
88
89
  end
89
90
 
90
91
 
91
- private
92
- def self.make_array_of_symbols(array)
93
- array.each_with_index do |item, index|
94
- array[index] = item.to_sym
95
- end
96
- end
97
-
92
+ private
98
93
  def self.make_datetime(date, time, options={})
99
94
  date = nil if date.nil? || date.empty? || (!options[:ignore_epoch] && epoch_date?(date))
100
95
  time = nil if time.nil? || time.empty?
@@ -141,7 +141,7 @@ module Osm
141
141
  attributes[:editable] = data['editable']
142
142
  attributes[:deletable] = data['deletable'] ? true : false
143
143
  attributes[:used] = data['used'].to_i
144
- attributes[:sections] = data['sections'].is_a?(Array) ? Osm::make_array_of_symbols(data['sections']) : []
144
+ attributes[:sections] = data['sections'].is_a?(Array) ? data['sections'].map(&:to_sym) : []
145
145
  attributes[:tags] = data['tags'].is_a?(Array) ? data['tags'] : []
146
146
  attributes[:versions] = []
147
147
  attributes[:files] = []
@@ -162,11 +162,11 @@ module Osm
162
162
  # @param [String] url The script on the remote server to invoke
163
163
  # @param [Hash] api_data A hash containing the values to be sent to the server in the body of the request
164
164
  # @return [Hash, Array, String] the parsed JSON returned by OSM
165
- def perform_query(url, api_data={})
165
+ def perform_query(url, api_data={}, raw=false)
166
166
  self.class.perform_query(@site, url, api_data.merge({
167
167
  'userid' => @user_id,
168
168
  'secret' => @user_secret,
169
- }))
169
+ }), raw)
170
170
  end
171
171
 
172
172
  # Get API user's roles in OSM
@@ -251,7 +251,7 @@ module Osm
251
251
  # @return [Hash, Array, String] the parsed JSON returned by OSM
252
252
  # @raise [Osm::Error] If an error was returned by OSM
253
253
  # @raise [Osm::ConnectionError] If an error occured connecting to OSM
254
- def self.perform_query(site, url, api_data={})
254
+ def self.perform_query(site, url, api_data={}, raw=false)
255
255
  raise ArgumentError, 'site is invalid, this should be set to either :osm or :ogm' unless Osm::Api::BASE_URLS.keys.include?(site)
256
256
 
257
257
  data = api_data.merge({
@@ -260,7 +260,7 @@ module Osm
260
260
  })
261
261
 
262
262
  if @@debug
263
- puts "Making :#{site} API request to #{url}"
263
+ puts "Making #{'RAW' if raw} :#{site} API request to #{url}"
264
264
  hide_values_for = ['secret', 'token']
265
265
  api_data_as_string = api_data.sort.map{ |key, value| "#{key} => #{hide_values_for.include?(key) ? 'PRESENT' : value.inspect}" }.join(', ')
266
266
  puts "{#{api_data_as_string}}"
@@ -279,6 +279,7 @@ module Osm
279
279
  puts result.response.body
280
280
  end
281
281
 
282
+ return result.response.body if raw
282
283
  return nil if result.response.body.empty?
283
284
  case result.response.content_type
284
285
  when 'application/json', 'text/html'
@@ -306,6 +307,9 @@ module Osm
306
307
  return false unless data.is_a?(Hash)
307
308
  return false if data['ok']
308
309
  to_return = data['error'] || data['err'] || false
310
+ if to_return.is_a?(Hash)
311
+ to_return = to_return['message'] unless to_return['message'].blank?
312
+ end
309
313
  to_return = false if to_return.blank?
310
314
  return to_return
311
315
  end
@@ -0,0 +1,392 @@
1
+ module Osm
2
+
3
+ class Email
4
+ TAGS = [{id: 'FIRSTNAME', description: "Member's first name"}, {id: 'LASTNAME', description: "Member's last name"}]
5
+
6
+ # Get a list of selected email address for selected members ready to pass to send_email method
7
+ # @param [Osm::Api] api The api to use to make the request
8
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to send the message to
9
+ # @param [Array<symbol>] contacts The contacts to get for members (:primary, :secondary and/or :member)
10
+ # @param [Array<Osm::Member, Fixnum, #to_i>] members The members (or their IDs) to get the email addresses for
11
+ # @return [Hash] member_id -> {firstname [String], lastname [String], emails [Array<String>]}
12
+ def self.get_emails_for_contacts(api, section, contacts, members)
13
+ # Convert contacts into OSM's format
14
+ contacts = [*contacts]
15
+ fail ArgumentError, "You must pass at least one contact" if contacts.none?
16
+ contact_group_map = {
17
+ primary: '"contact_primary_1"',
18
+ secondary: '"contact_primary_2"',
19
+ member: '"contact_primary_member"'
20
+ }
21
+ contacts.map! do |contact|
22
+ mapped = contact_group_map[contact]
23
+ fail ArgumentError, "Invalid contact - #{contact.inspect}" if mapped.nil?
24
+ mapped
25
+ end
26
+
27
+ # Convert member_ids to array of numbers
28
+ members = [*members]
29
+ fail ArgumentError, "You must pass at least one member" if members.none?
30
+ members.map!{ |member| member.to_i }
31
+
32
+ data = api.perform_query("/ext/members/email/?action=getSelectedEmailsFromContacts&sectionid=#{section.to_i}&scouts=#{members.join(',')}", {
33
+ 'contactGroups' => "[#{contacts.join(',')}]"
34
+ })
35
+ if data.is_a?(Hash)
36
+ data = data['emails']
37
+ return data if data.is_a?(Hash)
38
+ end
39
+ return false
40
+ end
41
+
42
+ # Get a list of selected email address for selected members ready to pass to send_email method
43
+ # @param [Osm::Api] api The api to use to make the request
44
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to send the message to
45
+ # @param [Hash] send_to Email addresses to send the email to: member_id -> {firstname [String], lastname [String], emails [Array<String>]}
46
+ # @param [String, nil] cc Email address (if any) to cc
47
+ # @param [String] from Email address to send the email from
48
+ # @param [String] from Email subject The subject of the email
49
+ # @param [String] from Email body The bosy of the email
50
+ # @return [Boolean] Whether OSM reported the email as sent
51
+ def self.send_email(api, section, send_to, cc='', from, subject, body)
52
+ data = api.perform_query('ext/members/email/?action=send', {
53
+ 'sectionid' => section.to_i,
54
+ 'emails' => send_to.to_json,
55
+ 'scouts' => send_to.keys.join(','),
56
+ 'cc' => cc,
57
+ 'from' => from,
58
+ 'subject' => subject,
59
+ 'body' => body,
60
+ })
61
+
62
+ return data.is_a?(Hash) && data['ok']
63
+ end
64
+
65
+
66
+ class DeliveryReport < Osm::Model
67
+ class Recipient < Osm::Model; end # Ensure class exists for definition of validations
68
+ TIME_FORMAT = '%d/%m/%Y %H:%M'
69
+ VALID_STATUSES = [:processed, :delivered, :bounced]
70
+
71
+ # @!attribute [rw] id
72
+ # @return [Fixnum] the id of the email
73
+ # @!attribute [rw] sent_at
74
+ # @return [Time] when the email was sent
75
+ # @!attribute [rw] subject
76
+ # @return [String] the subject line of the email
77
+ # @!attribute [rw] recipients
78
+ # @return [Array<Osm::DeliveryReport::Email::Recipient>]
79
+ # @!attribute [rw] section_id
80
+ # @return [Fixnum] the id of the section which sent the email
81
+
82
+ attribute :id, type: Integer
83
+ attribute :sent_at, type: DateTime
84
+ attribute :subject, type: String
85
+ attribute :recipients, type: Object, default: []
86
+ attribute :section_id, type: Integer
87
+
88
+ if ActiveModel::VERSION::MAJOR < 4
89
+ attr_accessible :id, :sent_at, :subject, :recipients, :section_id
90
+ end
91
+
92
+ validates_numericality_of :id, only_integer: true, greater_than: 0
93
+ validates_numericality_of :section_id, only_integer: true, greater_than: 0
94
+ validates_presence_of :sent_at
95
+ validates_presence_of :subject
96
+ validates :recipients, array_of: {item_type: Osm::Email::DeliveryReport::Recipient, item_valid: true}
97
+
98
+
99
+ # @!method initialize
100
+ # Initialize a new DeliveryReport
101
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
102
+
103
+
104
+ # Get delivery reports
105
+ # @param [Osm::Api] api The api to use to make the request
106
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the reports for
107
+ # @!macro options_get
108
+ # @return [Array<Osm::Email::DeliveryReport>]
109
+ def self.get_for_section(api, section, options={})
110
+ Osm::Model.require_access_to_section(api, section, options)
111
+ section_id = section.to_i
112
+ cache_key = ['email_delivery_reports', section_id]
113
+
114
+ if !options[:no_cache] && Osm::Model.cache_exist?(api, cache_key)
115
+ return cache_read(api, cache_key)
116
+ end
117
+
118
+ reports = []
119
+ recipients = {}
120
+ data = api.perform_query("ext/settings/emails/?action=getDeliveryReport&sectionid=#{section_id}")
121
+ data.each do |item|
122
+ case item['type']
123
+
124
+ when 'email'
125
+ # Create an Osm::Email::DeliveryReport in reports array
126
+ id = Osm::to_i_or_nil(item['id'])
127
+ sent_at_str, subject = item['name'].to_s.split(' - ', 2).map{ |i| i.to_s.strip }
128
+ reports.push Osm::Email::DeliveryReport.new(
129
+ id: id,
130
+ sent_at: Time.strptime(sent_at_str, TIME_FORMAT),
131
+ subject: subject,
132
+ section_id: section_id,
133
+ )
134
+ recipients[id] = []
135
+
136
+ when 'oneEmail'
137
+ # Create an Osm::Email::DeliveryReport::Email::Recipient in recipients[email_id] array
138
+ report_id, id = item['id'].to_s.strip.split('-').map{ |i| Osm::to_i_or_nil(i) }
139
+ status = item['status_raw'].to_sym
140
+ status = :bounced if status.eql?(:bounce)
141
+ member_id = Osm::to_i_or_nil(item['member_id'])
142
+ recipients[report_id].push Osm::Email::DeliveryReport::Recipient.new(
143
+ id: id,
144
+ address: item['email'],
145
+ status: status,
146
+ member_id: member_id,
147
+ )
148
+
149
+ end
150
+ end # each item in data
151
+
152
+ # Add recipients to reports
153
+ reports.each do |report|
154
+ recs = recipients[report.id]
155
+ # Set report for each recipient
156
+ recs.each do |recipient|
157
+ recipient.delivery_report = report
158
+ end
159
+ report.recipients = recs
160
+ end
161
+
162
+ cache_write(api, cache_key, reports)
163
+ return reports
164
+ end
165
+
166
+ # Get email contents for this report
167
+ # @param [Osm::Api] api The api to use to make the request
168
+ # @!macro options_get
169
+ # @return [Osm::Email::DeliveryReport::Email]
170
+ def get_email(api, options={})
171
+ Osm::Model.require_access_to_section(api, section_id, options)
172
+ cache_key = ['email_delivery_reports_email', section_id, id]
173
+
174
+ if !options[:no_cache] && Osm::Model.cache_exist?(api, cache_key)
175
+ return cache_read(api, cache_key)
176
+ end
177
+
178
+ email = Osm::Email::DeliveryReport::Email.fetch_from_osm(api, section_id, id)
179
+
180
+ cache_write(api, cache_key, email)
181
+ return email
182
+ end
183
+
184
+ # @!method processed_recipients
185
+ # The recipients of the message with a status of :processed
186
+ # @return (Array<Osm::Email::DeliveryReport::Recipient>)
187
+ # @!method processed_recipients?
188
+ # Whether there are recipients of the message with a status of :processed
189
+ # @return (Boolean)
190
+ # @!method delivered_recipients
191
+ # Count the recipients of the message with a status of :delivered
192
+ # @return (Array<Osm::Email::DeliveryReport::Recipient>)
193
+ # @!method delivered_recipients?
194
+ # Whether there are recipients of the message with a status of :delivered
195
+ # @return (Boolean)
196
+ # @!method bounced_recipients
197
+ # Count the recipients of the message with a status of :bounced
198
+ # @return (Array<Osm::Email::DeliveryReport::Recipient>)
199
+ # @!method bounced_recipients?
200
+ # Whether there are recipients of the message with a status of :bounced
201
+ # @return (Boolean)
202
+ VALID_STATUSES.each do |attribute|
203
+ define_method "#{attribute}_recipients" do
204
+ recipients.select{ |r| r.status.eql?(attribute) }
205
+ end
206
+ define_method "#{attribute}_recipients?" do
207
+ send("#{attribute}_recipients").any?
208
+ end
209
+ end
210
+
211
+
212
+ def to_s
213
+ "#{sent_at.strftime(TIME_FORMAT)} - #{subject}"
214
+ end
215
+
216
+ def <=>(another)
217
+ result = self.sent_at <=> another.try(:sent_at)
218
+ result = self.id <=> another.try(:id) if result.eql?(0)
219
+ return result
220
+ end
221
+
222
+
223
+ class Recipient < Osm::Model
224
+ SORT_BY = [:delivery_report, :id]
225
+ VALID_STATUSES = Osm::Email::DeliveryReport::VALID_STATUSES.clone
226
+
227
+ # @!attribute [rw] id
228
+ # @return [Fixnum] the id of the email recipient
229
+ # @!attribute [rw] delivery_report
230
+ # @return [Osm::Email::DeliveryReport] the report this recipient belongs to
231
+ # @!attribute [rw] address
232
+ # @return [String] the email address of the recipient
233
+ # @!attribute [rw] status
234
+ # @return [Symbol] the status of the email sent to the recipient
235
+ # @!attribute [rw] member_id
236
+ # @return [Fixnum] the id of the member the email was sent to
237
+
238
+ attribute :id, type: Integer
239
+ attribute :delivery_report, type: Object
240
+ attribute :address, type: String
241
+ attribute :status, type: Object, default: :unknown
242
+ attribute :member_id, type: Integer
243
+
244
+ if ActiveModel::VERSION::MAJOR < 4
245
+ attr_accessible :id, :delivery_report, :address, :status, :member_id
246
+ end
247
+
248
+ validates_numericality_of :id, only_integer: true, greater_than: 0
249
+ validates_numericality_of :member_id, only_integer: true, greater_than: 0
250
+ validates_presence_of :address
251
+ validates_presence_of :delivery_report
252
+ validates_inclusion_of :status, :in => VALID_STATUSES
253
+
254
+
255
+ # @!method initialize
256
+ # Initialize a new DeliveryReport::Recipient
257
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
258
+
259
+
260
+ # Get email contents for this recipient
261
+ # @param [Osm::Api] api The api to use to make the request
262
+ # @!macro options_get
263
+ # @return [Osm::Email::DeliveryReport::Email]
264
+ def get_email(api, options={})
265
+ Osm::Model.require_access_to_section(api, delivery_report.section_id, options)
266
+ cache_key = ['email_delivery_reports_email', delivery_report.section_id, delivery_report.id, id]
267
+
268
+ if !options[:no_cache] && Osm::Model.cache_exist?(api, cache_key)
269
+ return cache_read(api, cache_key)
270
+ end
271
+
272
+ email = Osm::Email::DeliveryReport::Email.fetch_from_osm(api, delivery_report.section_id, delivery_report.id, member_id, address)
273
+
274
+ cache_write(api, cache_key, email)
275
+ return email
276
+ end
277
+
278
+ # Unblock email address from being sent emails
279
+ # @param [Osm::Api] api The api to use to make the request
280
+ # @param [Boolean] whether removal was successful
281
+ def unblock_address(api)
282
+ return true unless bounced?
283
+
284
+ data = api.perform_query('ext/settings/emails/?action=unBlockEmail', {
285
+ 'section_id' => delivery_report.section_id,
286
+ 'email' => address,
287
+ 'email_id' => delivery_report.id
288
+ })
289
+
290
+ if data.is_a?(Hash)
291
+ fail Osm::Error, data['error'].to_s unless data['error'].nil?
292
+ return !!data['status']
293
+ end
294
+ return false
295
+ end
296
+
297
+ # @!method processed?
298
+ # Check if the email to this recipient has been processes
299
+ # @return (Boolean)
300
+ # @!method delivered?
301
+ # Check if the email to this recipient was delivered
302
+ # @return (Boolean)
303
+ # @!method bounced?
304
+ # Check if the email to this recipient bounced
305
+ # @return (Boolean)
306
+ VALID_STATUSES.each do |attribute|
307
+ define_method "#{attribute}?" do
308
+ status.eql?(attribute)
309
+ end
310
+ end
311
+
312
+ def to_s
313
+ "#{address} - #{status}"
314
+ end
315
+
316
+ def inspect
317
+ Osm::inspect_instance(self, {replace_with: {'delivery_report' => :id}})
318
+ end
319
+
320
+ end # class Osm::Email::DeliveryReport::Recipient
321
+
322
+
323
+ class Email < Osm::Model
324
+ SORT_BY = [:subject, :from, :to]
325
+
326
+ # @!attribute [rw] to
327
+ # @return [String] who the email was sent to (possibly nil)
328
+ # @!attribute [rw] from
329
+ # @return [String] who the email was sent from
330
+ # @!attribute [rw] subject
331
+ # @return [String] the subject of the email
332
+ # @!attribute [rw] body
333
+ # @return [String] the body of the email
334
+
335
+ attribute :to, type: String
336
+ attribute :from, type: String
337
+ attribute :subject, type: String
338
+ attribute :body, type: String
339
+
340
+ if ActiveModel::VERSION::MAJOR < 4
341
+ attr_accessible :to, :from, :subject, :body
342
+ end
343
+
344
+ validates_presence_of :to
345
+ validates_presence_of :from
346
+ validates_presence_of :subject
347
+ validates_presence_of :body
348
+
349
+
350
+ # @!method initialize
351
+ # Initialize a new DeliveryReport::Email
352
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
353
+
354
+
355
+ def to_s
356
+ "To: #{to}\nFrom: #{from}\n\n#{subject}\n\n#{body.gsub(/<\/?[^>]*>/, '')}"
357
+ end
358
+
359
+ protected
360
+ # Get email contents
361
+ # @param [Osm::Api] api The api to use to make the request
362
+ # @param [Integer] section_id
363
+ # @param [Integer] email_id
364
+ # @param [String] email_address
365
+ # @return [Osm::Email::DeliveryReport::Email]
366
+ def self.fetch_from_osm(api, section_id, email_id, member_id=nil, email_address='')
367
+ Osm::Model.require_access_to_section(api, section_id)
368
+
369
+ data = api.perform_query("ext/settings/emails/?action=getSentEmail&section_id=#{section_id}&email_id=#{email_id}&email=#{email_address}&member_id=#{member_id}")
370
+ fail Osm::Error, "Unexpected format for response - got a #{data.class}" unless data.is_a?(Hash)
371
+ fail Osm::Error, data['error'].to_s unless data['status']
372
+ fail Osm::Error, "Unexpected format for meta data - got a #{data.class}" unless data['data'].is_a?(Hash)
373
+
374
+ body = api.perform_query("ext/settings/emails/?action=getSentEmailContent&section_id=#{section_id}&email_id=#{email_id}&email=#{email_address}&member_id=#{member_id}", {}, true)
375
+ fail Osm::Error, data if data.eql?('Email not found')
376
+
377
+ email_data = data['data']
378
+ new(
379
+ to: email_data['to'],
380
+ from: email_data['from'],
381
+ subject: email_data['subject'],
382
+ body: body,
383
+ )
384
+ end
385
+
386
+ end # class Osm::Email::DeliveryReport::Email
387
+
388
+ end # class Osm::Email::DeliveryReport
389
+
390
+ end # class Osm::Email
391
+
392
+ end