MattHall-campaign_monitor 1.3.2.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.
@@ -0,0 +1,27 @@
1
+ class CampaignMonitor
2
+ module Helpers
3
+
4
+ def handle_response(response)
5
+ return [] if response.empty?
6
+
7
+ if response["Code"].to_i == 0
8
+ # success!
9
+ yield(response)
10
+ elsif response["Code"].to_i == 100
11
+ raise InvalidAPIKey
12
+ else
13
+ # error!
14
+ raise ApiError, response["Code"] + ": " + response["Message"]
15
+ end
16
+ end
17
+
18
+ def timestamp_format
19
+ '%Y-%m-%d %H:%M:%S'
20
+ end
21
+
22
+ def formatted_timestamp(datetime, format=timestamp_format)
23
+ datetime.strftime(format)
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,211 @@
1
+ require 'soap/wsdlDriver'
2
+
3
+ class CampaignMonitor
4
+ # Provides access to the subscribers and info about subscribers
5
+ # associated with a Mailing List
6
+ class List < Base
7
+ include CampaignMonitor::Helpers
8
+
9
+ id_field "ListID"
10
+ name_field "Title"
11
+
12
+ VALID_ATTRIBUTES=%w{ConfirmOptIn UnsubscribePage ConfirmationSuccessPage ListID Title}
13
+
14
+ # Example
15
+ # @list = new List(12345)
16
+ def initialize(attrs={})
17
+ super
18
+ @attributes=attrs
19
+ end
20
+
21
+ # Example
22
+ #
23
+ # @list = @client.new_list.defaults
24
+
25
+ def defaults
26
+ defaults={"ConfirmOptIn" => "false",
27
+ "UnsubscribePage" => "",
28
+ "ConfirmationSuccessPage" => ""}
29
+ @attributes=defaults.merge(@attributes)
30
+ self
31
+ end
32
+
33
+ # AR like
34
+ def save
35
+ id ? Update : Create
36
+ end
37
+
38
+ # Loads a list by it's ID
39
+ #
40
+ # @list = List.GetDetail(1234)
41
+ def self.GetDetail(id)
42
+ list=self.new("ListID" => id)
43
+ list.GetDetail(true)
44
+ list.result.code == 101 ? nil : list
45
+ end
46
+
47
+ # loads a list by it's ID
48
+ #
49
+ # @list = List.GetDetail(1234)
50
+ def self.[](k)
51
+ GetDetail(k)
52
+ end
53
+
54
+ def GetDetail(overwrite=false)
55
+ @result=Result.new(cm_client.List_GetDetail("ListID" => id))
56
+ @attributes=@result.raw.merge(@attributes)
57
+ @attributes.merge!(@result.raw) if overwrite
58
+ @result.success?
59
+ end
60
+
61
+ def Update
62
+ # if we're dealing with a half baked object that Client#lists has given
63
+ # us then we need to popular all the fields before we can attempt an update
64
+ unless @fully_baked
65
+ self.GetDetail
66
+ @fully_baked=true
67
+ end
68
+ @result=Result.new(cm_client.List_Update(@attributes))
69
+ @result.success?
70
+ end
71
+
72
+ def Delete
73
+ @result=Result.new(cm_client.List_Delete("ListID" => id))
74
+ @result.success?
75
+ end
76
+
77
+ def Create
78
+ @result=Result.new(cm_client.List_Create(@attributes))
79
+ self.id = @result.content if @result.success?
80
+ @result.success?
81
+ end
82
+
83
+ # Example
84
+ # @list = new List(12345)
85
+ # result = @list.add_subscriber("ralph.wiggum@simpsons.net")
86
+ #
87
+ # if result.succeeded?
88
+ # puts "Added Subscriber"
89
+ # end
90
+ def add_subscriber(email, name=nil, custom_fields=nil)
91
+ if custom_fields.nil?
92
+ Result.new(cm_client.Subscriber_Add("ListID" => self.id, "Email" => email, "Name" => name))
93
+ else
94
+ add_subscriber_with_custom_fields(email, name, custom_fields)
95
+ end
96
+ end
97
+
98
+ def add_and_resubscribe(email, name=nil, custom_fields=nil)
99
+ if custom_fields.nil?
100
+ Result.new(cm_client.Subscriber_AddAndResubscribe("ListID" => self.id, "Email" => email, "Name" => name))
101
+ else
102
+ add_and_resubscribe_with_custom_fields(email, name, custom_fields)
103
+ end
104
+ end
105
+
106
+ # Example
107
+ # @list = new List(12345)
108
+ # result = @list.remove_subscriber("ralph.wiggum@simpsons.net")
109
+ #
110
+ # if result.succeeded?
111
+ # puts "Deleted Subscriber"
112
+ # end
113
+ def remove_subscriber(email)
114
+ Result.new(cm_client.Subscriber_Unsubscribe("ListID" => self.id, "Email" => email))
115
+ end
116
+
117
+ # email The subscriber's email address.
118
+ # name The subscriber's name.
119
+ # custom_fields A hash of field name => value pairs.
120
+ def add_subscriber_with_custom_fields(email, name, custom_fields)
121
+ response = cm_client.using_soap do |driver|
122
+ driver.addSubscriberWithCustomFields \
123
+ :ApiKey => cm_client.api_key,
124
+ :ListID => self.id,
125
+ :Email => email,
126
+ :Name => name,
127
+ :CustomFields => { :SubscriberCustomField => custom_fields_array(custom_fields) }
128
+ end
129
+
130
+ response.subscriber_AddWithCustomFieldsResult
131
+ end
132
+
133
+ # email The subscriber's email address.
134
+ # name The subscriber's name.
135
+ # custom_fields A hash of field name => value pairs.
136
+ def add_and_resubscribe_with_custom_fields(email, name, custom_fields)
137
+ response = cm_client.using_soap do |driver|
138
+ driver.addAndResubscribeWithCustomFields \
139
+ :ApiKey => cm_client.api_key,
140
+ :ListID => self.id,
141
+ :Email => email,
142
+ :Name => name,
143
+ :CustomFields => { :SubscriberCustomField => custom_fields_array(custom_fields) }
144
+ end
145
+
146
+ response.subscriber_AddAndResubscribeWithCustomFieldsResult
147
+ end
148
+
149
+ # Example
150
+ # current_date = DateTime.new
151
+ # @list = new List(12345)
152
+ # @subscribers = @list.active_subscribers(current_date)
153
+ #
154
+ # for subscriber in @subscribers
155
+ # puts subscriber.email
156
+ # end
157
+ def active_subscribers(date)
158
+ response = cm_client.Subscribers_GetActive('ListID' => self.id, 'Date' => formatted_timestamp(date))
159
+ handle_response(response) do
160
+ response['Subscriber'].collect{|s| Subscriber.new(s['EmailAddress'], s['Name'], s['Date'])}
161
+ end
162
+ end
163
+
164
+ # Example
165
+ # current_date = DateTime.new
166
+ # @list = new List(12345)
167
+ # @subscribers = @list.unsubscribed(current_date)
168
+ #
169
+ # for subscriber in @subscribers
170
+ # puts subscriber.email
171
+ # end
172
+ def unsubscribed(date)
173
+ date = formatted_timestamp(date) unless date.is_a?(String)
174
+
175
+ response = cm_client.Subscribers_GetUnsubscribed('ListID' => self.id, 'Date' => date)
176
+
177
+ handle_response(response) do
178
+ response['Subscriber'].collect{|s| Subscriber.new(s['EmailAddress'], s['Name'], s['Date'])}
179
+ end
180
+ end
181
+
182
+ # Example
183
+ # current_date = DateTime.new
184
+ # @list = new List(12345)
185
+ # @subscribers = @list.bounced(current_date)
186
+ #
187
+ # for subscriber in @subscribers
188
+ # puts subscriber.email
189
+ # end
190
+ def bounced(date)
191
+ response = cm_client.Subscribers_GetBounced('ListID' => self.id, 'Date' => formatted_timestamp(date))
192
+
193
+ handle_response(response) do
194
+ response["Subscriber"].collect{|s| Subscriber.new(s["EmailAddress"], s["Name"], s["Date"])}
195
+ end
196
+ end
197
+
198
+
199
+ protected
200
+
201
+ # Converts hash of custom field name/values to array of hashes for the SOAP API.
202
+ def custom_fields_array(custom_fields)
203
+ arr = []
204
+ custom_fields.each do |key, value|
205
+ arr << { "Key" => key, "Value" => value }
206
+ end
207
+ arr
208
+ end
209
+
210
+ end
211
+ end
@@ -0,0 +1,46 @@
1
+ class CampaignMonitor
2
+
3
+ # Encapsulates
4
+ class SubscriberBounce #:nodoc:
5
+ attr_reader :email_address, :bounce_type, :list_id
6
+
7
+ def initialize(email_address, list_id, bounce_type)
8
+ @email_address = email_address
9
+ @bounce_type = bounce_type
10
+ @list_id = list_id
11
+ end
12
+ end
13
+
14
+ # Encapsulates
15
+ class SubscriberOpen #:nodoc:
16
+ attr_reader :email_address, :list_id, :opens
17
+
18
+ def initialize(email_address, list_id, opens)
19
+ @email_address = email_address
20
+ @list_id = list_id
21
+ @opens = opens
22
+ end
23
+ end
24
+
25
+ # Encapsulates
26
+ class SubscriberClick #:nodoc:
27
+ attr_reader :email_address, :list_id, :clicked_links
28
+
29
+ def initialize(email_address, list_id, clicked_links)
30
+ @email_address = email_address
31
+ @list_id = list_id
32
+ @clicked_links = clicked_links
33
+ end
34
+ end
35
+
36
+ # Encapsulates
37
+ class SubscriberUnsubscribe #:nodoc:
38
+ attr_reader :email_address, :list_id
39
+
40
+ def initialize(email_address, list_id)
41
+ @email_address = email_address
42
+ @list_id = list_id
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,31 @@
1
+ class CampaignMonitor
2
+ # Encapsulates the response received from the CampaignMonitor webservice.
3
+ class Result
4
+ attr_reader :message, :code, :raw
5
+
6
+ def initialize(response)
7
+ @message = response["Message"]
8
+ @code = response["Code"].to_i
9
+ @raw = response
10
+ end
11
+
12
+ def success?
13
+ code == 0
14
+ end
15
+
16
+ def failed?
17
+ not success?
18
+ end
19
+
20
+ def content
21
+ # if we're a string (likely from SOAP)
22
+ return raw if raw.is_a?(String)
23
+ # if we're a hash
24
+ raw["__content__"]
25
+ end
26
+
27
+ alias :succeeded? :success?
28
+ alias :failure? :failed?
29
+
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ class CampaignMonitor
2
+ # Provides the ability to add/remove subscribers from a list
3
+ class Subscriber < Base
4
+ include CampaignMonitor::Helpers
5
+
6
+ attr_accessor :email_address, :name, :date_subscribed
7
+
8
+ def initialize(email_address, name=nil, date=nil)
9
+ @email_address = email_address
10
+ @name = name
11
+ @date_subscribed = date_subscribed
12
+ super
13
+ end
14
+
15
+ # Example
16
+ # @subscriber = Subscriber.new("ralph.wiggum@simpsons.net")
17
+ # @subscriber.add(12345)
18
+ def add(list_id)
19
+ Result.new(cm_client.Subscriber_Add("ListID" => list_id, "Email" => @email_address, "Name" => @name))
20
+ end
21
+
22
+ # Example
23
+ # @subscriber = Subscriber.new("ralph.wiggum@simpsons.net")
24
+ # @subscriber.add_and_resubscribe(12345)
25
+ def add_and_resubscribe(list_id)
26
+ Result.new(cm_client.Subscriber_AddAndResubscribe("ListID" => list_id, "Email" => @email_address, "Name" => @name))
27
+ end
28
+
29
+ # Example
30
+ # @subscriber = Subscriber.new("ralph.wiggum@simpsons.net")
31
+ # @subscriber.unsubscribe(12345)
32
+ def unsubscribe(list_id)
33
+ Result.new(cm_client.Subscriber_Unsubscribe("ListID" => list_id, "Email" => @email_address))
34
+ end
35
+
36
+ def is_subscribed?(list_id)
37
+ result = cm_client.Subscribers_GetIsSubscribed("ListID" => list_id, "Email" => @email_address)
38
+ return true if result == 'True'
39
+ return false if result == 'False'
40
+ raise "Invalid value for is_subscribed?: #{result}"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,270 @@
1
+ require 'rubygems'
2
+ require 'cgi'
3
+ require 'net/http'
4
+ require 'xmlsimple'
5
+ require 'date'
6
+ gem 'soap4r'
7
+
8
+ require File.join(File.dirname(__FILE__), '../support/class_enhancements.rb')
9
+ require File.join(File.dirname(__FILE__), 'campaign_monitor/helpers.rb')
10
+ require File.join(File.dirname(__FILE__), 'campaign_monitor/misc.rb')
11
+ require File.join(File.dirname(__FILE__), 'campaign_monitor/base.rb')
12
+ require File.join(File.dirname(__FILE__), 'campaign_monitor/client.rb')
13
+ require File.join(File.dirname(__FILE__), 'campaign_monitor/list.rb')
14
+ require File.join(File.dirname(__FILE__), 'campaign_monitor/subscriber.rb')
15
+ require File.join(File.dirname(__FILE__), 'campaign_monitor/result.rb')
16
+ require File.join(File.dirname(__FILE__), 'campaign_monitor/campaign.rb')
17
+
18
+ # A wrapper class to access the Campaign Monitor API. Written using the wonderful
19
+ # Flickr interface by Scott Raymond as a guide on how to access remote web services
20
+ #
21
+ # For more information on the Campaign Monitor API, visit http://campaignmonitor.com/api
22
+ #
23
+ # Author:: Jordan Brock <jordan@spintech.com.au>
24
+ # Copyright:: Copyright (c) 2006 Jordan Brock <jordan@spintech.com.au>
25
+ # License:: MIT <http://www.opensource.org/licenses/mit-license.php>
26
+ #
27
+ # USAGE:
28
+ # require 'campaign_monitor'
29
+ # cm = CampaignMonitor.new(API_KEY) # creates a CampaignMonitor object
30
+ # # Can set CAMPAIGN_MONITOR_API_KEY in environment.rb
31
+ # cm.clients # Returns an array of clients associated with
32
+ # # the user account
33
+ # cm.campaigns(client_id)
34
+ # cm.lists(client_id)
35
+ # cm.add_subscriber(list_id, email, name)
36
+ #
37
+ # == CLIENT
38
+ # client = Client[client_id] # find an existing client
39
+ # client = Client.new(attributes)
40
+ # client.Create
41
+ # client.Delete
42
+ # client.GetDetail
43
+ # client.UpdateAccessAndBilling
44
+ # client.UpdateBasics
45
+ # client.update # update basics, access, and billing
46
+ # client.lists # OR
47
+ # client.GetLists
48
+ # client.lists.build # to create a new unsaved list for a client
49
+ # client.campaigns # OR
50
+ # client.GetCampaigns
51
+ #
52
+ # == LIST
53
+ # list = List[list_id] # find an existing list
54
+ # list = List.new(attributes)
55
+ # list.Create
56
+ # list.Delete
57
+ # list.Update
58
+ # list.add_subscriber(email, name)
59
+ # list.remove_subscriber(email)
60
+ # list.active_subscribers(date)
61
+ # list.unsubscribed(date)
62
+ # list.bounced(date)
63
+ #
64
+ # == CAMPAIGN
65
+ # campaign = Campaign.new(campaign_id)
66
+ # campaign.clicks
67
+ # campaign.opens
68
+ # campaign.bounces
69
+ # campaign.unsubscribes
70
+ # campaign.number_recipients
71
+ # campaign.number_clicks
72
+ # campaign.number_opens
73
+ # campaign.number_bounces
74
+ # campaign.number_unsubscribes
75
+ #
76
+ #
77
+ # == SUBSCRIBER
78
+ # subscriber = Subscriber.new(email)
79
+ # subscriber.add(list_id)
80
+ # subscriber.unsubscribe(list_id)
81
+ #
82
+ # == Data Types
83
+ # SubscriberBounce
84
+ # SubscriberClick
85
+ # SubscriberOpen
86
+ # SubscriberUnsubscribe
87
+ # Result
88
+ #
89
+ class CampaignMonitor
90
+ include CampaignMonitor::Helpers
91
+
92
+ class InvalidAPIKey < StandardError
93
+ end
94
+
95
+ class ApiError < StandardError
96
+ end
97
+
98
+ attr_reader :api_key, :api_url
99
+
100
+ # Replace this API key with your own (http://www.campaignmonitor.com/api/)
101
+ def initialize(api_key=CAMPAIGN_MONITOR_API_KEY)
102
+ @api_key = api_key
103
+ @api_url = 'http://api.createsend.com/api/api.asmx'
104
+ CampaignMonitor::Base.client=self
105
+ end
106
+
107
+ # Takes a CampaignMonitor API method name and set of parameters;
108
+ # returns an XmlSimple object with the response
109
+ def request(method, params)
110
+ request_xml=http_get(request_url(method, params))
111
+ begin
112
+ response = PARSER.xml_in(request_xml, { 'keeproot' => false,
113
+ 'forcearray' => %w[List Campaign Subscriber Client SubscriberOpen SubscriberUnsubscribe SubscriberClick SubscriberBounce],
114
+ 'noattr' => true })
115
+ response.delete('d1p1:type')
116
+ response.delete("d1p1:http://www.w3.org/2001/XMLSchema-instance:type")
117
+ response
118
+ # rescue XML::Parser::ParseError
119
+ rescue XML::Error
120
+ { "Code" => 500, "Message" => request_xml.split(/\r?\n/).first, "FullError" => request_xml }
121
+ end
122
+ end
123
+
124
+ # Takes a CampaignMonitor API method name and set of parameters; returns the correct URL for the REST API.
125
+ def request_url(method, params={})
126
+ params.merge!('ApiKey' => api_key)
127
+
128
+ query = params.collect do |key, value|
129
+ "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
130
+ end.sort * '&'
131
+
132
+ "#{api_url}/#{method}?#{query}"
133
+ end
134
+
135
+ # Does an HTTP GET on a given URL and returns the response body
136
+ def http_get(url)
137
+ response=Net::HTTP.get_response(URI.parse(url))
138
+ response.body.to_s
139
+ end
140
+
141
+ # By overriding the method_missing method, it is possible to easily support all of the methods
142
+ # available in the API
143
+ def method_missing(method_id, params = {})
144
+ puts " CM: #{method_id} (#{params.inspect})" if $debug
145
+ res=request(method_id.id2name.gsub(/_/, '.'), params)
146
+ puts " returning: #{res.inspect}" if $debug
147
+ res
148
+ end
149
+
150
+ # Returns an array of Client objects associated with the API Key
151
+ #
152
+ # Example
153
+ # @cm = CampaignMonitor.new()
154
+ # @clients = @cm.clients
155
+ #
156
+ # for client in @clients
157
+ # puts client.name
158
+ # end
159
+ def clients
160
+ handle_response(User_GetClients()) do |response|
161
+ response["Client"].collect{|c| Client.new({"ClientID" => c["ClientID"], "CompanyName" => c["Name"]})}
162
+ end
163
+ end
164
+
165
+ def new_client
166
+ Client.new(nil)
167
+ end
168
+
169
+ def system_date
170
+ User_GetSystemDate()
171
+ end
172
+
173
+ def parsed_system_date
174
+ DateTime.strptime(system_date, timestamp_format)
175
+ end
176
+
177
+ def countries
178
+ handle_response(User_GetCountries()) do | response |
179
+ response["string"]
180
+ end
181
+ end
182
+
183
+ def timezones
184
+ handle_response(User_GetTimezones()) do | response |
185
+ response["string"]
186
+ end
187
+ end
188
+
189
+ # Returns an array of Campaign objects associated with the specified Client ID
190
+ #
191
+ # Example
192
+ # @cm = CampaignMonitor.new()
193
+ # @campaigns = @cm.campaigns(12345)
194
+ #
195
+ # for campaign in @campaigns
196
+ # puts campaign.subject
197
+ # end
198
+ def campaigns(client_id)
199
+ handle_response(Client_GetCampaigns("ClientID" => client_id)) do |response|
200
+ response["Campaign"].collect{|c| Campaign.new(c) }
201
+ end
202
+ end
203
+
204
+ # Returns an array of Subscriber Lists for the specified Client ID
205
+ #
206
+ # Example
207
+ # @cm = CampaignMonitor.new()
208
+ # @lists = @cm.lists(12345)
209
+ #
210
+ # for list in @lists
211
+ # puts list.name
212
+ # end
213
+ def lists(client_id)
214
+ handle_response(Client_GetLists("ClientID" => client_id)) do |response|
215
+ response["List"].collect{|l| List.new({"ListID" => l["ListID"], "Title" => l["Name"]})}
216
+ end
217
+ end
218
+
219
+ # A quick method of adding a subscriber to a list. Returns a Result object
220
+ #
221
+ # Example
222
+ # @cm = CampaignMonitor.new()
223
+ # result = @cm.add_subscriber(12345, "ralph.wiggum@simpsons.net", "Ralph Wiggum")
224
+ #
225
+ # if result.succeeded?
226
+ # puts "Subscriber Added to List"
227
+ # end
228
+ def add_subscriber(list_id, email, name)
229
+ Result.new(Subscriber_Add("ListID" => list_id, "Email" => email, "Name" => name))
230
+ end
231
+
232
+ def using_soap
233
+ driver = wsdl_driver_factory.create_rpc_driver
234
+ driver.wiredump_dev = STDERR if $debug
235
+ response = yield(driver)
236
+ driver.reset_stream
237
+
238
+ response
239
+ end
240
+
241
+ protected
242
+
243
+ def wsdl_driver_factory
244
+ SOAP::WSDLDriverFactory.new("#{api_url}?WSDL")
245
+ end
246
+
247
+ end
248
+
249
+ # If libxml is installed, we use the FasterXmlSimple library, that provides most of the functionality of XmlSimple
250
+ # except it uses the xml/libxml library for xml parsing (rather than REXML).
251
+ # If libxml isn't installed, we just fall back on XmlSimple.
252
+
253
+ PARSER =
254
+ begin
255
+ require 'xml/libxml'
256
+ # Older version of libxml aren't stable (bus error when requesting attributes that don't exist) so we
257
+ # have to use a version greater than '0.3.8.2'.
258
+ raise LoadError unless XML::Parser::VERSION > '0.3.8.2'
259
+ $:.push(File.join(File.dirname(__FILE__), '..', 'support', 'faster-xml-simple', 'lib'))
260
+ require 'faster_xml_simple'
261
+ p 'Using libxml-ruby'
262
+ FasterXmlSimple
263
+ rescue LoadError
264
+ begin
265
+ require 'rexml-expansion-fix'
266
+ rescue LoadError => e
267
+ p 'Cannot load rexml security patch'
268
+ end
269
+ XmlSimple
270
+ end
@@ -0,0 +1,35 @@
1
+ module ClassEnhancements
2
+
3
+ def inherited_property(accessor, default = nil)
4
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
5
+ @#{accessor} = default
6
+
7
+ def set_#{accessor}(value)
8
+ @#{accessor} = value
9
+ end
10
+ alias #{accessor} set_#{accessor}
11
+
12
+ def get_#{accessor}
13
+ return @#{accessor} if instance_variable_defined?(:@#{accessor})
14
+ superclass.send(:get_#{accessor})
15
+ end
16
+ RUBY
17
+
18
+ # @path = default
19
+ #
20
+ # def set_path(value)
21
+ # @path = value
22
+ # end
23
+ # alias_method path, set_path
24
+
25
+ # def get_path
26
+ # return @path if instance_variable_defined?(:path)
27
+ # superclass.send(:path)
28
+ # end
29
+ end
30
+
31
+ end
32
+
33
+ class Class #:nodoc:
34
+ include ClassEnhancements
35
+ end