osm 1.2.24 → 1.2.25

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.
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