reidab-campaign_monitor 1.3.3

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,55 @@
1
+ class CampaignMonitor
2
+ # Provides access to the lists and campaigns associated with a client
3
+ class Base
4
+
5
+ attr_reader :result, :attributes, :cm_client
6
+
7
+ @@client=nil
8
+
9
+ def self.client
10
+ @@client
11
+ end
12
+
13
+ def self.client=(a)
14
+ @@client=a
15
+ end
16
+
17
+ def [](k)
18
+ if m=self.class.get_data_types[k]
19
+ @attributes[k].send(m)
20
+ else
21
+ @attributes[k]
22
+ end
23
+ end
24
+
25
+ def []=(k,v)
26
+ @attributes[k]=v
27
+ end
28
+
29
+ def initialize(*args)
30
+ @attributes={}
31
+ @cm_client=@@client
32
+ end
33
+
34
+ # id and name field stuff
35
+
36
+ inherited_property "id_field", "id"
37
+ inherited_property "name_field", "name"
38
+ inherited_property "data_types", {}
39
+
40
+ def id
41
+ @attributes[self.class.get_id_field]
42
+ end
43
+
44
+ def id=(v)
45
+ @attributes[self.class.get_id_field]=v
46
+ end
47
+
48
+ def name
49
+ @attributes[self.class.get_name_field]
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
@@ -0,0 +1,240 @@
1
+ class CampaignMonitor
2
+
3
+ # The Client class aims to impliment the full functionality of the CampaignMonitor
4
+ # Clients API as detailed at: http://www.campaignmonitor.com/api/
5
+ # === Attributes
6
+ #
7
+ # Attriutes can be read and set as if Campaign were a Hash
8
+ #
9
+ # @client["CompanyName"]="Road Running, Inc."
10
+ # @client["ContactName"] => "Wiley Coyote"
11
+ #
12
+ # Convenience attribute readers are provided for name and id
13
+ #
14
+ # @campaign.id == @client["CampaignID"]
15
+ # @campaign.name == @client["CampaignName"]
16
+ #
17
+ # === API calls supported
18
+ #
19
+ # * Campaign.Create
20
+ # * Campaign.Send
21
+ # * Campaign.GetBounces
22
+ # * Campaign.GetLists
23
+ # * Campaign.GetOpens
24
+ # * Campaign.GetSubscriberClicks
25
+ # * Campaign.GetUnsubscribes
26
+ # * Campaign.GetSummary
27
+ #
28
+ class Campaign < Base
29
+ include CampaignMonitor::Helpers
30
+ id_field "CampaignID"
31
+ name_field "Subject"
32
+
33
+ class MissingParameter < StandardError
34
+ end
35
+
36
+ data_types "TotalRecipients" => "to_i"
37
+
38
+ def initialize(attrs={})
39
+ super
40
+ @attributes=attrs
41
+ end
42
+
43
+ # Calls Campaign.Create
44
+ # It will return true if successful and false if not.
45
+ # Campaign#result will have the result of the API call
46
+ #
47
+ # Example
48
+ # @camp=@client.new_campaign
49
+ # @camp["CampaignName"]="Yummy Gummy Bears"
50
+ # @camp["CampaignSubject"]="Yummy Gummy Bears"
51
+ # @camp["FromName"]="Mr Yummy"
52
+ # @camp["FromEmail"]="yummy@gummybears.com"
53
+ # @camp["ReplyTo"]="support@gummybears.com"
54
+ # @camp["HtmlUrl"]="http://www.gummybears.com/newsletter2009.html"
55
+ # @camp["TextUrl"]="http://www.gummybears.com/newsletter2009.txt"
56
+ # @camp.Create
57
+ def Create
58
+ required_params=%w{CampaignName CampaignSubject FromName FromEmail ReplyTo HtmlUrl TextUrl}
59
+ required_params.each do |f|
60
+ raise MissingParameter, "'#{f}' is required to call Create" unless self[f]
61
+ end
62
+ response = cm_client.using_soap do |driver|
63
+ opts=attributes.merge(:ApiKey => cm_client.api_key, :SubscriberListIDs => @lists.map {|x| x.id})
64
+ driver.createCampaign opts
65
+ end
66
+ @result=Result.new(response["Campaign.CreateResult"])
67
+ self.id=@result.content if @result.success?
68
+ @result.success?
69
+ end
70
+
71
+ # Calls Campaign.Send
72
+ # It will return true if successful and false if not.
73
+ # Campaign#result will have the result of the API call
74
+ #
75
+ # Example
76
+ # @camp=@client.new_campaign(attributes)
77
+ # @camp.Create
78
+ # @camp.Send("ConfirmationEmail" => "bob@aol.com", "SendDate" => "Immediately")
79
+ def Send(options={})
80
+ required_params=%w{ConfirmationEmail SendDate}
81
+ required_params.each do |f|
82
+ raise MissingParameter, "'#{f}' is required to call Send" unless options[f]
83
+ end
84
+ options.merge!("CampaignID" => self.id)
85
+ @result=Result.new(@cm_client.Campaign_Send(options))
86
+ @result.success?
87
+ end
88
+
89
+ # Calls Campaign.GetLists. Often you probably should just use Campaign#lists
90
+ # It will raise an ApiError if an error occurs
91
+ # Campaign#result will have the result of the API call
92
+ #
93
+ # Example
94
+ # @camp=@client.campaigns.first
95
+ # @camp.GetLists
96
+ def GetLists
97
+ handle_response(@cm_client.Campaign_GetLists(:CampaignID => id)) do |response|
98
+ @result=Result.new(response)
99
+ @lists=response["List"].collect{|l| List.new({"ListID" => l["ListID"], "Title" => l["Name"]})}
100
+ end
101
+ end
102
+
103
+ # Creates a new list object with the given id.
104
+ # You'll still need to call another method to load data or actually do anything useful
105
+ # as this method just generators a new object and doesn't hit the API at all. This was
106
+ # added as a quick way to setup an object to request data from it
107
+ #
108
+ # Example
109
+ # @campaign=Campaign[1234]
110
+ # @campaign.lists.each do ...
111
+ def self.[](id)
112
+ Campaign.new("CampaignID" => id)
113
+ end
114
+
115
+ # Convenience method for accessing or adding lists to a new (uncreated) campaign
116
+ # Calls GetLists behind the scenes if needed
117
+ #
118
+ # Example
119
+ # @camp=@client.campaigns.first
120
+ # @camp.lists.each do
121
+ #
122
+ # @camp=@client.new_campaign(attributes)
123
+ # @camp.lists << @client.lists.first
124
+ # @camp.Create
125
+ def lists
126
+ # pull down the list of lists if we have an id
127
+ self.GetLists if @lists.nil? and id
128
+ @lists||=[]
129
+ end
130
+
131
+ # Example
132
+ # @campaign = Campaign[12345]
133
+ # @subscriber_opens = @campaign.opens
134
+ #
135
+ # for subscriber in @subscriber_opens
136
+ # puts subscriber.email
137
+ # end
138
+ def GetOpens
139
+ handle_response(cm_client.Campaign_GetOpens("CampaignID" => self.id)) do |response|
140
+ response["SubscriberOpen"].collect{|s| SubscriberOpen.new(s["EmailAddress"], s["ListID"], s["NumberOfOpens"])}
141
+ end
142
+ end
143
+ alias opens GetOpens
144
+
145
+ # Example
146
+ # @campaign = Campaign[12345]
147
+ # @subscriber_bounces = @campaign.bounces
148
+ #
149
+ # for subscriber in @subscriber_bounces
150
+ # puts subscriber.email
151
+ # end
152
+ def GetBounces
153
+ handle_response(cm_client.Campaign_GetBounces("CampaignID"=> self.id)) do |response|
154
+ response["SubscriberBounce"].collect{|s| SubscriberBounce.new(s["EmailAddress"], s["ListID"], s["BounceType"])}
155
+ end
156
+ end
157
+ alias bounces GetBounces
158
+
159
+ # Example
160
+ # @campaign = Campaign[12345]
161
+ # @subscriber_clicks = @campaign.clicks
162
+ #
163
+ # for subscriber in @subscriber_clicks
164
+ # puts subscriber.email
165
+ # end
166
+ def GetSubscriberClicks
167
+ handle_response(cm_client.Campaign_GetSubscriberClicks("CampaignID" => self.id)) do |response|
168
+ response["SubscriberClick"].collect{|s| SubscriberClick.new(s["EmailAddress"], s["ListID"], s["ClickedLinks"])}
169
+ end
170
+ end
171
+ alias clicks GetSubscriberClicks
172
+
173
+ # Example
174
+ # @campaign = Campaign[12345]
175
+ # @subscriber_unsubscribes = @campaign.unsubscribes
176
+ #
177
+ # for subscriber in @subscriber_unsubscribes
178
+ # puts subscriber.email
179
+ # end
180
+ def GetUnsubscribes
181
+ handle_response(cm_client.Campaign_GetUnsubscribes("CampaignID" => self.id)) do |response|
182
+ response["SubscriberUnsubscribe"].collect{|s| SubscriberUnsubscribe.new(s["EmailAddress"], s["ListID"])}
183
+ end
184
+ end
185
+ alias unsubscribes GetUnsubscribes
186
+
187
+ # hook up the old API calls
188
+ def method_missing(m, *args)
189
+ if %w{number_bounced number_unsubscribed number_clicks number_opened number_recipients}.include?(m.to_s)
190
+ summary[m]
191
+ else
192
+ super
193
+ end
194
+ end
195
+
196
+ # Calls Campaign.GetSummary. OYou probably should just use Campaign#summary which caches results
197
+ # It will raise ApiError if an error occurs
198
+ # Campaign#result will have the result of the API call
199
+ #
200
+ # Example
201
+ # @camp=@client.campaigns.first
202
+ # @camp.GetSummary["Clicks"]
203
+ def GetSummary
204
+ handle_response(cm_client.Campaign_GetSummary('CampaignID' => self.id)) do |response|
205
+ @result=Result.new(response)
206
+ @summary=parse_summary(@result.raw)
207
+ end
208
+ @summary
209
+ end
210
+
211
+ # Convenience method for accessing summary details of a campaign
212
+ #
213
+ # Examples
214
+ # @camp.summary["Recipients"]
215
+ # @camp.summary['Recipients']
216
+ # @camp.summary['TotalOpened']
217
+ # @camp.summary['Clicks']
218
+ # @camp.summary['Unsubscribed']
219
+ # @camp.summary['Bounced']
220
+ def summary(refresh=false)
221
+ self.GetSummary if refresh or @summary.nil?
222
+ @summary
223
+ end
224
+
225
+ private
226
+ def parse_summary(summary)
227
+ @summary = {
228
+ :number_recipients => summary['Recipients'].to_i,
229
+ :number_opened => summary['TotalOpened'].to_i,
230
+ :number_clicks => summary['Clicks'].to_i,
231
+ :number_unsubscribed => summary['Unsubscribed'].to_i,
232
+ :number_bounced => summary['Bounced'].to_i
233
+ }
234
+ summary.each do |key, value|
235
+ @summary[key]=value.to_i
236
+ end
237
+ @summary
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,228 @@
1
+ class CampaignMonitor
2
+ # Provides access to the lists and campaigns associated with a client
3
+ class ClientLists < Array
4
+ def initialize(v,parent)
5
+ @parent=parent
6
+ super(v)
7
+ end
8
+ def build(attrs={})
9
+ List.new(attrs.merge(:ClientID => @parent.id))
10
+ end
11
+ end
12
+
13
+ # The Client class aims to impliment the full functionality of the CampaignMonitor
14
+ # Clients API as detailed at: http://www.campaignmonitor.com/api/
15
+ # === Attributes
16
+ #
17
+ # Attriutes can be read and set as if Client were a Hash
18
+ #
19
+ # @client["CompanyName"]="Road Running, Inc."
20
+ # @client["ContactName"] => "Wiley Coyote"
21
+ #
22
+ # Convenience attribute readers are provided for name and id
23
+ #
24
+ # @client.id == @client["ClientID"]
25
+ # @client.name == @client["CompanyName"]
26
+ #
27
+ # === API calls supported
28
+ #
29
+ # * Client.Create
30
+ # * Client.Delete
31
+ # * Client.GetCampaigns
32
+ # * Client.GetDetail
33
+ # * Client.GetLists
34
+ # * Client.UpdateAccessAndBilling
35
+ # * Client.UpdateBasics
36
+ #
37
+ # === Not yet supported
38
+ #
39
+ # * Client.GetSegments - TODO
40
+ # * Client.GetSuppressionList - TODO
41
+ class Client < Base
42
+ include CampaignMonitor::Helpers
43
+ id_field "ClientID"
44
+ name_field "CompanyName"
45
+
46
+ data_types "AccessLevel" => "to_i"
47
+
48
+ # we will assume if something isn't a basic attribute that it's a AccessAndBilling attribute
49
+ BASIC_ATTRIBUTES=%w{CompanyName ContactName EmailAddress Country Timezone}
50
+
51
+ # Creates a new client that you can later create (or load)
52
+ # The prefered way to load a client is using Client#[] however
53
+ #
54
+ # Example
55
+ #
56
+ # @client = Client.new(attributes)
57
+ # @client.Create
58
+ #
59
+ # @client = Client.new("ClientID" => 12345)
60
+ # @client.GetDetails
61
+ def initialize(attrs={})
62
+ super
63
+ @attributes=attrs
64
+ end
65
+
66
+ # Calls Client.GetLists and returns a collection of CM::Campaign objects
67
+ #
68
+ # Example
69
+ # @client = @cm.clients.first
70
+ # @new_list = @client.lists.build
71
+ # @lists = @client.lists
72
+ #
73
+ # for list in @lists
74
+ # puts list.name # a shortcut for list["Title"]
75
+ # end
76
+ def GetLists
77
+ ClientLists.new(cm_client.lists(self.id), self)
78
+ end
79
+
80
+ alias lists GetLists
81
+
82
+ # Calls Client.GetCampaigns and returns a collection of CM::List objects
83
+ #
84
+ # Example
85
+ # @client = @cm.clients.first
86
+ # @campaigns = @client.campaigns
87
+ #
88
+ # for campaign in @campaigns
89
+ # puts campaign.subject
90
+ # end
91
+ def GetCampaigns
92
+ cm_client.campaigns(self.id)
93
+ end
94
+
95
+ alias campaigns GetCampaigns
96
+
97
+ def new_campaign(attrs={})
98
+ Campaign.new(attrs.merge("ClientID" => self.id))
99
+ end
100
+
101
+
102
+ # Calls Client.GetDetails to load a specific client
103
+ # Client#result will have the result of the API call
104
+ #
105
+ # Example
106
+ #
107
+ # @client=Client[12345]
108
+ # puts @client.name if @client.result.success?
109
+ def self.[](id)
110
+ client=self.new("ClientID" => id)
111
+ client.GetDetail(true)
112
+ client.result.code == 102 ? nil : client
113
+ end
114
+
115
+ # Calls Client.GetDetails
116
+ # This is needed because often if you're working with a list of clients you really only
117
+ # have their company name when what you want is the full record.
118
+ # It will return true if successful and false if not.
119
+ # Client#result will have the result of the API call
120
+ #
121
+ # Example
122
+ #
123
+ # @client=@cm.clients.first
124
+ # @client["CompanyName"]="Ben's Widgets"
125
+ # @client["ContactName"] => nil
126
+ # @client.GetDetail
127
+ # @client["ContactName"] => "Ben Wilder"
128
+ def GetDetail(overwrite=false)
129
+ @result=Result.new(cm_client.Client_GetDetail("ClientID" => id))
130
+ return false if @result.failed?
131
+ @flatten={}
132
+ @flatten.merge!(@result.raw["BasicDetails"])
133
+ @flatten.merge!(@result.raw["AccessAndBilling"])
134
+ # TODO - look into
135
+ # map {} to nil - some weird XML converstion issue?
136
+ @flatten=@flatten.inject({}) { |sum,a| sum[a[0]]=a[1]=={} ? nil : a[1]; sum }
137
+ @attributes=@flatten.merge(@attributes)
138
+ @attributes.merge!(@flatten) if overwrite
139
+ @fully_baked=true if @result.success?
140
+ @result.success?
141
+ end
142
+
143
+ # This is just a convenience method that calls both Client.UpdateBasics and Client.UpdateAccessAndBilling.
144
+ # It will return true if successful and false if not.
145
+ # Client#result will have the result of the API call
146
+ #
147
+ # Example
148
+ # @client=@cm.clients.first
149
+ # @client["CompanyName"]="Ben's Widgets"
150
+ # @client.update
151
+ def update
152
+ self.UpdateBasics
153
+ self.UpdateAccessAndBilling if result.success?
154
+ @result.success?
155
+ end
156
+
157
+ # Calls Client.UpdateAccessAndBilling
158
+ # This will also call GetDetails first to prepoluate any empty fields the API call needs
159
+ # It will return true if successful and false if not.
160
+ # Client#result will have the result of the API call
161
+ #
162
+ # Example
163
+ # @client=@cm.clients.first
164
+ # @client["Currency"]="USD"
165
+ # @client.UpdateAccessAndBilling
166
+ def UpdateAccessAndBilling
167
+ fully_bake
168
+ @result=Result.new(cm_client.Client_UpdateAccessAndBilling(@attributes))
169
+ @result.success?
170
+ end
171
+
172
+ # Calls Client.UpdateBasics
173
+ # This will also call GetDetails first to prepoluate any empty fields the API call needs
174
+ # It will return true if successful and false if not.
175
+ # Client#result will have the result of the API call
176
+ #
177
+ # Example
178
+ # @client=@cm.clients.first
179
+ # @client["CompanyName"]="Ben's Widgets"
180
+ # @client.UpdateBasics
181
+ def UpdateBasics
182
+ fully_bake
183
+ @result=Result.new(cm_client.Client_UpdateBasics(@attributes))
184
+ @result.success?
185
+ end
186
+
187
+ # Calls Client.Create
188
+ # It will return true if successful and false if not.
189
+ # Client#result will have the result of the API call
190
+ #
191
+ # Example
192
+ # @client=CampaignMonitor::Client.new
193
+ # @client["CompanyName"]="Ben's Widgets"
194
+ # @client["ContactName"]="Ben Winters"
195
+ # @client["Country"]=@cm.countries.first
196
+ # @client["Timezone"]=@cm.timezones.first
197
+ # ...
198
+ # @client.Create
199
+ def Create
200
+ @result=Result.new(cm_client.Client_Create(@attributes))
201
+ self.id = @result.content if @result.success?
202
+ @result.success?
203
+ end
204
+
205
+ # Calls Client.Delete.
206
+ # It will return true if successful and false if not.
207
+ # Client#result will have the result of the API call
208
+ #
209
+ # Example
210
+ # @client=@cm.clients.first
211
+ # @client.Delete
212
+ def Delete
213
+ @result=Result.new(cm_client.Client_Delete("ClientID" => id))
214
+ @result.success?
215
+ end
216
+
217
+ private
218
+
219
+ #:nodoc:
220
+ def fully_bake
221
+ unless @fully_baked
222
+ self.GetDetail
223
+ @fully_baked=true
224
+ end
225
+ end
226
+
227
+ end
228
+ end