yyyc514-campaign_monitor 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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