patientslikeme-campaign_monitor 1.1

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.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006 Jordan Brock
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,26 @@
1
+ = campaign_monitor
2
+
3
+ This library provides access to the Campaign Monitor API (http://www.campaignmonitor.com/api)
4
+
5
+ == Pre-requisites
6
+
7
+ An account with Campaign Monitor and the API Key. Accounts are free and can be obtained from
8
+ http://www.campaignmonitor.com
9
+
10
+ == Resources
11
+
12
+ Install
13
+
14
+ * gem install campaign_monitor
15
+
16
+ Home Page
17
+
18
+ * http://github.com/jordanbrock/campaign-monitor-ruby/wikis
19
+
20
+ Bugtracking
21
+
22
+ * http://jordanbrock.lighthouseapp.com/projects/13212/home
23
+
24
+ Git Repository
25
+
26
+ * http://github.com/jordanbrock/campaign-monitor-ruby
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+
6
+ # read the contents of the gemspec, eval it, and assign it to 'spec'
7
+ # this lets us maintain all gemspec info in one place. Nice and DRY.
8
+ spec = eval(IO.read("campaign_monitor.gemspec"))
9
+
10
+ Rake::GemPackageTask.new(spec) do |pkg|
11
+ pkg.gem_spec = spec
12
+ end
13
+
14
+ task :install => [:package] do
15
+ sh %{sudo gem install pkg/#{spec.name}-#{spec.version}}
16
+ end
17
+
18
+ Rake::TestTask.new do |t|
19
+ t.libs << "test"
20
+ t.test_files = FileList['test/test*.rb']
21
+ t.verbose = true
22
+ end
23
+
24
+ Rake::RDocTask.new do |rd|
25
+ rd.main = "README.rdoc"
26
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
27
+ rd.rdoc_dir = 'doc'
28
+ rd.options = spec.rdoc_options
29
+ end
@@ -0,0 +1,41 @@
1
+ Gem::Specification.new do |s|
2
+ s.platform = Gem::Platform::RUBY
3
+ s.name = 'campaign_monitor'
4
+ s.version = "1.1"
5
+ s.summary = 'Provides access to the Campaign Monitor API.'
6
+ s.description = <<-EOF
7
+ A simple wrapper class that provides basic access to the Campaign Monitor API
8
+ EOF
9
+ s.author = 'Jeremy Weiskotten'
10
+ s.email = 'jweiskotten@patientslikeme.com'
11
+ s.homepage = 'http://github.com/patientslikeme/campaign_monitor/'
12
+ s.has_rdoc = true
13
+
14
+ s.requirements << 'none'
15
+ s.require_path = 'lib'
16
+
17
+ s.files = [
18
+ 'campaign_monitor.gemspec',
19
+ 'init.rb',
20
+ 'install.rb',
21
+ 'MIT-LICENSE',
22
+ 'Rakefile',
23
+ 'README.rdoc',
24
+
25
+ 'lib/campaign_monitor.rb',
26
+ 'lib/campaign_monitor/campaign.rb',
27
+ 'lib/campaign_monitor/client.rb',
28
+ 'lib/campaign_monitor/list.rb',
29
+ 'lib/campaign_monitor/result.rb',
30
+ 'lib/campaign_monitor/subscriber.rb',
31
+
32
+ 'support/faster-xml-simple/lib/faster_xml_simple.rb',
33
+ 'support/faster-xml-simple/test/xml_simple_comparison_test.rb',
34
+ 'support/faster-xml-simple/test/regression_test.rb',
35
+ 'support/faster-xml-simple/lib/faster_xml_simple.rb',
36
+
37
+ 'test/campaign_monitor_test.rb',
38
+ ]
39
+
40
+ s.test_file = 'test/campaign_monitor_test.rb'
41
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'campaign_monitor'
data/install.rb ADDED
File without changes
@@ -0,0 +1,129 @@
1
+ class CampaignMonitor
2
+ # Provides access to the information about a campaign
3
+ class Campaign
4
+ attr_reader :id, :subject, :sent_date, :total_recipients
5
+
6
+ def initialize(id=nil, subject=nil, sent_date=nil, total_recipients=nil)
7
+ @id = id
8
+ @subject = subject
9
+ @sent_date = sent_date
10
+ @total_recipients = total_recipients
11
+ @cm_client = CampaignMonitor.new
12
+ end
13
+
14
+ # Example
15
+ # @campaign = Campaign.new(12345)
16
+ # @subscriber_opens = @campaign.opens
17
+ #
18
+ # for subscriber in @subscriber_opens
19
+ # puts subscriber.email
20
+ # end
21
+ def opens
22
+ response = @cm_client.Campaign_GetOpens("CampaignID" => @id)
23
+ return [] if response.empty?
24
+ unless response["Code"].to_i != 0
25
+ response["SubscriberOpen"].collect{|s| SubscriberOpen.new(s["EmailAddress"], s["ListID"], s["NumberOfOpens"])}
26
+ else
27
+ raise response["Code"] + " - " + response["Message"]
28
+ end
29
+ end
30
+
31
+ # Example
32
+ # @campaign = Campaign.new(12345)
33
+ # @subscriber_bounces = @campaign.bounces
34
+ #
35
+ # for subscriber in @subscriber_bounces
36
+ # puts subscriber.email
37
+ # end
38
+ def bounces
39
+ response = @cm_client.Campaign_GetBounces("CampaignID"=> @id)
40
+ return [] if response.empty?
41
+ unless response["Code"].to_i != 0
42
+ response["SubscriberBounce"].collect{|s| SubscriberBounce.new(s["EmailAddress"], s["ListID"], s["BounceType"])}
43
+ else
44
+ raise response["Code"] + " - " + response["Message"]
45
+ end
46
+ end
47
+
48
+ # Example
49
+ # @campaign = Campaign.new(12345)
50
+ # @subscriber_clicks = @campaign.clicks
51
+ #
52
+ # for subscriber in @subscriber_clicks
53
+ # puts subscriber.email
54
+ # end
55
+ def clicks
56
+ response = @cm_client.Campaign_GetSubscriberClicks("CampaignID" => @id)
57
+ return [] if response.empty?
58
+ unless response["Code"].to_i != 0
59
+ response["SubscriberClick"].collect{|s| SubscriberClick.new(s["EmailAddress"], s["ListID"], s["ClickedLinks"])}
60
+ else
61
+ raise response["Code"] + " - " + response["Message"]
62
+ end
63
+ end
64
+
65
+ # Example
66
+ # @campaign = Campaign.new(12345)
67
+ # @subscriber_unsubscribes = @campaign.unsubscribes
68
+ #
69
+ # for subscriber in @subscriber_unsubscribes
70
+ # puts subscriber.email
71
+ # end
72
+ def unsubscribes
73
+ response = @cm_client.Campaign_GetUnsubscribes("CampaignID" => @id)
74
+ return [] if response.empty?
75
+ unless response["Code"].to_i != 0
76
+ response["SubscriberUnsubscribe"].collect{|s| SubscriberUnsubscribe.new(s["EmailAddress"], s["ListID"])}
77
+ else
78
+ raise response["Code"] + " - " + response["Message"]
79
+ end
80
+ end
81
+
82
+ # Example
83
+ # @campaign = Campaign.new(12345)
84
+ # puts @campaign.number_recipients
85
+ def number_recipients
86
+ @number_recipients.nil? ? getInfo.number_recipients : @number_recipients
87
+ end
88
+
89
+ # Example
90
+ # @campaign = Campaign.new(12345)
91
+ # puts @campaign.number_opened
92
+ def number_opened
93
+ @number_opened.nil? ? getInfo.number_opened : @number_opened
94
+ end
95
+
96
+ # Example
97
+ # @campaign = Campaign.new(12345)
98
+ # puts @campaign.number_clicks
99
+ def number_clicks
100
+ @number_clicks.nil? ? getInfo.number_clicks : @number_clicks
101
+ end
102
+
103
+ # Example
104
+ # @campaign = Campaign.new(12345)
105
+ # puts @campaign.number_unsubscribed
106
+ def number_unsubscribed
107
+ @number_unsubscribed.nil? ? getInfo.number_unsubscribed : @number_unsubscribed
108
+ end
109
+
110
+ # Example
111
+ # @campaign = Campaign.new(12345)
112
+ # puts @campaign.number_bounced
113
+ def number_bounced
114
+ @number_bounced.nil? ? getInfo.number_bounced : @number_bounced
115
+ end
116
+
117
+ private
118
+ def getInfo
119
+ info = @cm_client.Campaign_GetSummary('CampaignID'=>@id)
120
+ @title = info['title']
121
+ @number_recipients = info["Recipients"].to_i
122
+ @number_opened = info["TotalOpened"].to_i
123
+ @number_clicks = info["Click"].to_i
124
+ @number_unsubscribed = info["Unsubscribed"].to_i
125
+ @number_bounced = info["Bounced"].to_i
126
+ self
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,47 @@
1
+ class CampaignMonitor
2
+ # Provides access to the lists and campaigns associated with a client
3
+ class Client
4
+ attr_reader :id, :name, :cm_client
5
+
6
+ # Example
7
+ # @client = new Client(12345)
8
+ def initialize(id, name=nil)
9
+ @id = id
10
+ @name = name
11
+ @cm_client = CampaignMonitor.new
12
+ end
13
+
14
+ # Example
15
+ # @client = new Client(12345)
16
+ # @lists = @client.lists
17
+ #
18
+ # for list in @lists
19
+ # puts list.name
20
+ # end
21
+ def lists
22
+ response = @cm_client.Client_GetLists("ClientID" => @id)
23
+ return [] if response.empty?
24
+ unless response["Code"].to_i != 0
25
+ response["List"].collect{|l| List.new(l["ListID"], l["Name"])}
26
+ else
27
+ raise response["Code"] + " - " + response["Message"]
28
+ end
29
+ end
30
+
31
+ # Example
32
+ # @client = new Client(12345)
33
+ # @campaigns = @client.campaigns
34
+ #
35
+ # for campaign in @campaigns
36
+ # puts campaign.subject
37
+ # end
38
+ def campaigns
39
+ response = @cm_client.Client_GetCampaigns("ClientID" => @id)
40
+ unless response["Code"].to_i != 0
41
+ response["Campaign"].collect{|c| Campaign.new(c["CampaignID"], c["Subject"], c["SentDate"], c["TotalRecipients"].to_i)}
42
+ else
43
+ raise response["Code"] + " - " + response["Message"]
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,92 @@
1
+ class CampaignMonitor
2
+ # Provides access to the subscribers and info about subscribers
3
+ # associated with a Mailing List
4
+ class List
5
+ attr_reader :id, :name, :cm_client
6
+
7
+ # Example
8
+ # @list = new List(12345)
9
+ def initialize(id=nil, name=nil)
10
+ @id = id
11
+ @name = name
12
+ @cm_client = CampaignMonitor.new
13
+ end
14
+
15
+ # Example
16
+ # @list = new List(12345)
17
+ # result = @list.add_subscriber("ralph.wiggum@simpsons.net")
18
+ #
19
+ # if result.succeeded?
20
+ # puts "Added Subscriber"
21
+ # end
22
+ def add_subscriber(email, name = nil)
23
+ Result.new(@cm_client.Subscriber_Add("ListID" => @id, "Email" => email, "Name" => name))
24
+ end
25
+
26
+ # Example
27
+ # @list = new List(12345)
28
+ # result = @list.remove_subscriber("ralph.wiggum@simpsons.net")
29
+ #
30
+ # if result.succeeded?
31
+ # puts "Deleted Subscriber"
32
+ # end
33
+ def remove_subscriber(email)
34
+ Result.new(@cm_client.Subscriber_Unsubscribe("ListID" => @id, "Email" => email))
35
+ end
36
+
37
+ # Example
38
+ # current_date = DateTime.new
39
+ # @list = new List(12345)
40
+ # @subscribers = @list.active_subscribers(current_date)
41
+ #
42
+ # for subscriber in @subscribers
43
+ # puts subscriber.email
44
+ # end
45
+ def active_subscribers(date)
46
+ response = @cm_client.Subscribers_GetActive('ListID' => @id, "Date" => date.strftime("%Y-%m-%d %H:%M:%S"))
47
+ return [] if response.empty?
48
+ unless response["Code"].to_i != 0
49
+ response["Subscriber"].collect{|s| Subscriber.new(s["EmailAddress"], s["Name"], s["Date"])}
50
+ else
51
+ raise response["Code"] + " - " + response["Message"]
52
+ end
53
+ end
54
+
55
+ # Example
56
+ # current_date = DateTime.new
57
+ # @list = new List(12345)
58
+ # @subscribers = @list.unsubscribed(current_date)
59
+ #
60
+ # for subscriber in @subscribers
61
+ # puts subscriber.email
62
+ # end
63
+ def unsubscribed(date)
64
+ response = @cm_client.Subscribers_GetUnsubscribed('ListID' => @id, 'Date' => date.strftime("%Y-%m-%d %H:%M:%S"))
65
+ return [] if response.empty?
66
+ unless response["Code"].to_i != 0
67
+ response["Subscriber"].collect{|s| Subscriber.new(s["EmailAddress"], s["Name"], s["Date"])}
68
+ else
69
+ raise response["Code"] + " - " + response["Message"]
70
+ end
71
+ end
72
+
73
+ # Example
74
+ # current_date = DateTime.new
75
+ # @list = new List(12345)
76
+ # @subscribers = @list.bounced(current_date)
77
+ #
78
+ # for subscriber in @subscribers
79
+ # puts subscriber.email
80
+ # end
81
+ def bounced(date)
82
+ response = @cm_client.Subscribers_GetBounced('ListID' => @id, 'Date' => date.strftime("%Y-%m-%d %H:%M:%S"))
83
+ return [] if response.empty?
84
+ unless response["Code"].to_i != 0
85
+ response["Subscriber"].collect{|s| Subscriber.new(s["EmailAddress"], s["Name"], s["Date"])}
86
+ else
87
+ raise response["Code"] + " - " + response["Message"]
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,19 @@
1
+ class CampaignMonitor
2
+ # Encapsulates the response received from the CampaignMonitor webservice.
3
+ class Result
4
+ attr_reader :message, :code
5
+
6
+ def initialize(response)
7
+ @message = response["Message"]
8
+ @code = response["Code"].to_i
9
+ end
10
+
11
+ def succeeded?
12
+ code == 0
13
+ end
14
+
15
+ def failed?
16
+ !succeeded?
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ class CampaignMonitor
2
+ # Provides the ability to add/remove subscribers from a list
3
+ class Subscriber
4
+ attr_accessor :email_address, :name, :date_subscribed
5
+
6
+ def initialize(email_address, name=nil, date=nil)
7
+ @email_address = email_address
8
+ @name = name
9
+ @date_subscribed = date_subscribed
10
+ @cm_client = CampaignMonitor.new
11
+ end
12
+
13
+ # Example
14
+ # @subscriber = Subscriber.new("ralph.wiggum@simpsons.net")
15
+ # @subscriber.add(12345)
16
+ def add(list_id)
17
+ Result.new(@cm_client.Subscriber_Add("ListID" => list_id, "Email" => @email_address, "Name" => @name))
18
+ end
19
+
20
+ # Example
21
+ # @subscriber = Subscriber.new("ralph.wiggum@simpsons.net")
22
+ # @subscriber.add_and_resubscribe(12345)
23
+ def add_and_resubscribe(list_id)
24
+ Result.new(@cm_client.Subscriber_AddAndResubscribe("ListID" => list_id, "Email" => @email_address, "Name" => @name))
25
+ end
26
+
27
+ # Example
28
+ # @subscriber = Subscriber.new("ralph.wiggum@simpsons.net")
29
+ # @subscriber.unsubscribe(12345)
30
+ def unsubscribe(list_id)
31
+ Result.new(@cm_client.Subscriber_Unsubscribe("ListID" => list_id, "Email" => @email_address))
32
+ end
33
+
34
+ def is_subscribed?(list_id)
35
+ result = @cm_client.Subscribers_GetIsSubscribed("ListID" => list_id, "Email" => @email_address)
36
+ return true if result == 'True'
37
+ return false if result == 'False'
38
+ raise "Invalid value for is_subscribed?: #{result}"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,243 @@
1
+ # CampaignMonitor
2
+ # A wrapper class to access the Campaign Monitor API. Written using the wonderful
3
+ # Flickr interface by Scott Raymond as a guide on how to access remote web services
4
+ #
5
+ # For more information on the Campaign Monitor API, visit http://campaignmonitor.com/api
6
+ #
7
+ # Author:: Jordan Brock <jordan@spintech.com.au>
8
+ # Copyright:: Copyright (c) 2006 Jordan Brock <jordan@spintech.com.au>
9
+ # License:: MIT <http://www.opensource.org/licenses/mit-license.php>
10
+ #
11
+ # USAGE:
12
+ # require 'campaign_monitor'
13
+ # cm = CampaignMonitor.new(API_KEY) # creates a CampaignMonitor object
14
+ # # Can set CAMPAIGN_MONITOR_API_KEY in environment.rb
15
+ # cm.clients # Returns an array of clients associated with
16
+ # # the user account
17
+ # cm.campaigns(client_id)
18
+ # cm.lists(client_id)
19
+ # cm.add_subscriber(list_id, email, name)
20
+ #
21
+ # CLIENT
22
+ # client = Client.new(client_id)
23
+ # client.lists
24
+ # client.campaigns
25
+ #
26
+ # LIST
27
+ # list = List.new(list_id)
28
+ # list.add_subscriber(email, name)
29
+ # list.remove_subscriber(email)
30
+ # list.active_subscribers(date)
31
+ # list.unsubscribed(date)
32
+ # list.bounced(date)
33
+ #
34
+ # CAMPAIGN
35
+ # campaign = Campaign.new(campaign_id)
36
+ # campaign.clicks
37
+ # campaign.opens
38
+ # campaign.bounces
39
+ # campaign.unsubscribes
40
+ # campaign.number_recipients
41
+ # campaign.number_clicks
42
+ # campaign.number_opens
43
+ # campaign.number_bounces
44
+ # campaign.number_unsubscribes
45
+ #
46
+ #
47
+ # SUBSCRIBER
48
+ # subscriber = Subscriber.new(email)
49
+ # subscriber.add(list_id)
50
+ # subscriber.unsubscribe(list_id)
51
+ #
52
+ # Data Types
53
+ # SubscriberBounce
54
+ # SubscriberClick
55
+ # SubscriberOpen
56
+ # SubscriberUnsubscribe
57
+ # Result
58
+ #
59
+
60
+ require 'rubygems'
61
+ require 'cgi'
62
+ require 'net/http'
63
+ require 'xmlsimple'
64
+ require 'date'
65
+
66
+ Dir[File.join(File.dirname(__FILE__), 'campaign_monitor/*.rb')].each do |f|
67
+ require f
68
+ end
69
+
70
+ class CampaignMonitor
71
+ # Replace this API key with your own (http://www.campaignmonitor.com/api/)
72
+ def initialize(api_key=CAMPAIGN_MONITOR_API_KEY)
73
+ @api_key = api_key
74
+ @host = 'http://api.createsend.com'
75
+ @api = '/api/api.asmx/'
76
+ end
77
+
78
+ # Takes a CampaignMonitor API method name and set of parameters;
79
+ # returns an XmlSimple object with the response
80
+ def request(method, params)
81
+ response = PARSER.xml_in(http_get(request_url(method, params)), { 'keeproot' => false,
82
+ 'forcearray' => %w[List Campaign Subscriber Client SubscriberOpen SubscriberUnsubscribe SubscriberClick SubscriberBounce],
83
+ 'noattr' => true })
84
+ response.delete('d1p1:type')
85
+ response
86
+ end
87
+
88
+ # Takes a CampaignMonitor API method name and set of parameters; returns the correct URL for the REST API.
89
+ def request_url(method, params={})
90
+ url = "#{@host}#{@api}/#{method}?ApiKey=#{@api_key}"
91
+ params.each_pair do |key, val|
92
+ url += "&#{key}=" + CGI::escape(val.to_s)
93
+ end
94
+ url
95
+ end
96
+
97
+ # Does an HTTP GET on a given URL and returns the response body
98
+ def http_get(url)
99
+ Net::HTTP.get_response(URI.parse(url)).body.to_s
100
+ end
101
+
102
+ # By overriding the method_missing method, it is possible to easily support all of the methods
103
+ # available in the API
104
+ def method_missing(method_id, params = {})
105
+ request(method_id.id2name.gsub(/_/, '.'), params)
106
+ end
107
+
108
+ # Returns an array of Client objects associated with the API Key
109
+ #
110
+ # Example
111
+ # @cm = CampaignMonitor.new()
112
+ # @clients = @cm.clients
113
+ #
114
+ # for client in @clients
115
+ # puts client.name
116
+ # end
117
+ def clients
118
+ response = User_GetClients()
119
+ return [] if response.empty?
120
+ unless response["Code"].to_i != 0
121
+ response["Client"].collect{|c| Client.new(c["ClientID"], c["Name"])}
122
+ else
123
+ raise response["Code"] + " - " + response["Message"]
124
+ end
125
+ end
126
+
127
+ # Returns an array of Campaign objects associated with the specified Client ID
128
+ #
129
+ # Example
130
+ # @cm = CampaignMonitor.new()
131
+ # @campaigns = @cm.campaigns(12345)
132
+ #
133
+ # for campaign in @campaigns
134
+ # puts campaign.subject
135
+ # end
136
+ def campaigns(client_id)
137
+ response = Client_GetCampaigns("ClientID" => client_id)
138
+ return [] if response.empty?
139
+ unless response["Code"].to_i != 0
140
+ response["Campaign"].collect{|c| Campaign.new(c["CampaignID"], c["Subject"], c["SentDate"], c["TotalRecipients"].to_i)}
141
+ else
142
+ raise response["Code"] + " - " + response["Message"]
143
+ end
144
+ end
145
+
146
+ # Returns an array of Subscriber Lists for the specified Client ID
147
+ #
148
+ # Example
149
+ # @cm = CampaignMonitor.new()
150
+ # @lists = @cm.lists(12345)
151
+ #
152
+ # for list in @lists
153
+ # puts list.name
154
+ # end
155
+ def lists(client_id)
156
+ response = Client_GetLists("ClientID" => client_id)
157
+ return [] if response.empty?
158
+ unless response["Code"].to_i != 0
159
+ response["List"].collect{|l| List.new(l["ListID"], l["Name"])}
160
+ else
161
+ raise response["Code"] + " - " + response["Message"]
162
+ end
163
+ end
164
+
165
+ # A quick method of adding a subscriber to a list. Returns a Result object
166
+ #
167
+ # Example
168
+ # @cm = CampaignMonitor.new()
169
+ # result = @cm.add_subscriber(12345, "ralph.wiggum@simpsons.net", "Ralph Wiggum")
170
+ #
171
+ # if result.succeeded?
172
+ # puts "Subscriber Added to List"
173
+ # end
174
+ def add_subscriber(list_id, email, name)
175
+ Result.new(Subscriber_Add("ListID" => list_id, "Email" => email, "Name" => name))
176
+ end
177
+
178
+ # Encapsulates
179
+ class SubscriberBounce
180
+ attr_reader :email_address, :bounce_type, :list_id
181
+
182
+ def initialize(email_address, list_id, bounce_type)
183
+ @email_address = email_address
184
+ @bounce_type = bounce_type
185
+ @list_id = list_id
186
+ end
187
+ end
188
+
189
+ # Encapsulates
190
+ class SubscriberOpen
191
+ attr_reader :email_address, :list_id, :opens
192
+
193
+ def initialize(email_address, list_id, opens)
194
+ @email_address = email_address
195
+ @list_id = list_id
196
+ @opens = opens
197
+ end
198
+ end
199
+
200
+ # Encapsulates
201
+ class SubscriberClick
202
+ attr_reader :email_address, :list_id, :clicked_links
203
+
204
+ def initialize(email_address, list_id, clicked_links)
205
+ @email_address = email_address
206
+ @list_id = list_id
207
+ @clicked_links = clicked_links
208
+ end
209
+ end
210
+
211
+ # Encapsulates
212
+ class SubscriberUnsubscribe
213
+ attr_reader :email_address, :list_id
214
+
215
+ def initialize(email_address, list_id)
216
+ @email_address = email_address
217
+ @list_id = list_id
218
+ end
219
+ end
220
+ end
221
+
222
+ # If libxml is installed, we use the FasterXmlSimple library, that provides most of the functionality of XmlSimple
223
+ # except it uses the xml/libxml library for xml parsing (rather than REXML).
224
+ # If libxml isn't installed, we just fall back on XmlSimple.
225
+
226
+ PARSER =
227
+ begin
228
+ require 'xml/libxml'
229
+ # Older version of libxml aren't stable (bus error when requesting attributes that don't exist) so we
230
+ # have to use a version greater than '0.3.8.2'.
231
+ raise LoadError unless XML::Parser::VERSION > '0.3.8.2'
232
+ $:.push(File.join(File.dirname(__FILE__), '..', 'support', 'faster-xml-simple', 'lib'))
233
+ require 'faster_xml_simple'
234
+ p 'Using libxml-ruby'
235
+ FasterXmlSimple
236
+ rescue LoadError
237
+ begin
238
+ require 'rexml-expansion-fix'
239
+ rescue LoadError => e
240
+ p 'Cannot load rexml security patch'
241
+ end
242
+ XmlSimple
243
+ end
@@ -0,0 +1,187 @@
1
+ #
2
+ # Copyright (c) 2006 Michael Koziarski
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in the
6
+ # Software without restriction, including without limitation the rights to use,
7
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8
+ # Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
18
+ # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+
21
+ require 'rubygems'
22
+ require 'xml/libxml'
23
+
24
+ class FasterXmlSimple
25
+ Version = '0.5.0'
26
+ class << self
27
+ # Take an string containing XML, and returns a hash representing that
28
+ # XML document. For example:
29
+ #
30
+ # FasterXmlSimple.xml_in("<root><something>1</something></root>")
31
+ # {"root"=>{"something"=>{"__content__"=>"1"}}}
32
+ #
33
+ # Faster XML Simple is designed to be a drop in replacement for the xml_in
34
+ # functionality of http://xml-simple.rubyforge.org
35
+ #
36
+ # The following options are supported:
37
+ #
38
+ # * <tt>contentkey</tt>: The key to use for the content of text elements,
39
+ # defaults to '\_\_content__'
40
+ # * <tt>forcearray</tt>: The list of elements which should always be returned
41
+ # as arrays. Under normal circumstances single element arrays are inlined.
42
+ # * <tt>suppressempty</tt>: The value to return for empty elements, pass +true+
43
+ # to remove empty elements entirely.
44
+ # * <tt>keeproot</tt>: By default the hash returned has a single key with the
45
+ # name of the root element. If the name of the root element isn't
46
+ # interesting to you, pass +false+.
47
+ # * <tt>forcecontent</tt>: By default a text element with no attributes, will
48
+ # be collapsed to just a string instead of a hash with a single key.
49
+ # Pass +true+ to prevent this.
50
+ #
51
+ #
52
+ def xml_in(string, options={})
53
+ new(string, options).out
54
+ end
55
+ end
56
+
57
+ def initialize(string, options) #:nodoc:
58
+ @doc = parse(string)
59
+ @options = default_options.merge options
60
+ end
61
+
62
+ def out #:nodoc:
63
+ if @options['keeproot']
64
+ {@doc.root.name => collapse(@doc.root)}
65
+ else
66
+ collapse(@doc.root)
67
+ end
68
+ end
69
+
70
+ private
71
+ def default_options
72
+ {'contentkey' => '__content__', 'forcearray' => [], 'keeproot'=>true}
73
+ end
74
+
75
+ def collapse(element)
76
+ result = hash_of_attributes(element)
77
+ if text_node? element
78
+ text = collapse_text(element)
79
+ result[content_key] = text if text =~ /\S/
80
+ elsif element.children?
81
+ element.inject(result) do |hash, child|
82
+ unless child.text?
83
+ child_result = collapse(child)
84
+ (hash[child.name] ||= []) << child_result
85
+ end
86
+ hash
87
+ end
88
+ end
89
+ if result.empty?
90
+ return empty_element
91
+ end
92
+ # Compact them to ensure it complies with the user's requests
93
+ inline_single_element_arrays(result)
94
+ remove_empty_elements(result) if suppress_empty?
95
+ if content_only?(result) && !force_content?
96
+ result[content_key]
97
+ else
98
+ result
99
+ end
100
+ end
101
+
102
+ def content_only?(result)
103
+ result.keys == [content_key]
104
+ end
105
+
106
+ def content_key
107
+ @options['contentkey']
108
+ end
109
+
110
+ def force_array?(key_name)
111
+ Array(@options['forcearray']).include?(key_name)
112
+ end
113
+
114
+ def inline_single_element_arrays(result)
115
+ result.each do |key, value|
116
+ if value.size == 1 && value.is_a?(Array) && !force_array?(key)
117
+ result[key] = value.first
118
+ end
119
+ end
120
+ end
121
+
122
+ def remove_empty_elements(result)
123
+ result.each do |key, value|
124
+ if value == empty_element
125
+ result.delete key
126
+ end
127
+ end
128
+ end
129
+
130
+ def suppress_empty?
131
+ @options['suppressempty'] == true
132
+ end
133
+
134
+ def empty_element
135
+ if !@options.has_key? 'suppressempty'
136
+ {}
137
+ else
138
+ @options['suppressempty']
139
+ end
140
+ end
141
+
142
+ # removes the content if it's nothing but blanks, prevents
143
+ # the hash being polluted with lots of content like "\n\t\t\t"
144
+ def suppress_empty_content(result)
145
+ result.delete content_key if result[content_key] !~ /\S/
146
+ end
147
+
148
+ def force_content?
149
+ @options['forcecontent']
150
+ end
151
+
152
+ # a text node is one with 1 or more child nodes which are
153
+ # text nodes, and no non-text children, there's no sensible
154
+ # way to support nodes which are text and markup like:
155
+ # <p>Something <b>Bold</b> </p>
156
+ def text_node?(element)
157
+ !element.text? && element.all? {|c| c.text?}
158
+ end
159
+
160
+ # takes a text node, and collapses it into a string
161
+ def collapse_text(element)
162
+ element.map {|c| c.content } * ''
163
+ end
164
+
165
+ def hash_of_attributes(element)
166
+ result = {}
167
+ element.each_attr do |attribute|
168
+ name = attribute.name
169
+ name = [attribute.ns, attribute.name].join(':') if attribute.ns?
170
+ result[name] = attribute.value
171
+ end
172
+ result
173
+ end
174
+
175
+ def parse(string)
176
+ if string == ''
177
+ string = ' '
178
+ end
179
+ XML::Parser.string(string).parse
180
+ end
181
+ end
182
+
183
+ class XmlSimple # :nodoc:
184
+ def self.xml_in(*args)
185
+ FasterXmlSimple.xml_in *args
186
+ end
187
+ end
@@ -0,0 +1,47 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RegressionTest < FasterXSTest
4
+ def test_content_nil_regressions
5
+ expected = {"asdf"=>{"jklsemicolon"=>{}}}
6
+ assert_equal expected, FasterXmlSimple.xml_in("<asdf><jklsemicolon /></asdf>")
7
+ assert_equal expected, FasterXmlSimple.xml_in("<asdf><jklsemicolon /></asdf>", 'forcearray'=>['asdf'])
8
+ end
9
+
10
+ def test_s3_regression
11
+ str = File.read("test/fixtures/test-7.xml")
12
+ assert_nil FasterXmlSimple.xml_in(str)["AccessControlPolicy"]["AccessControlList"]["__content__"]
13
+ end
14
+
15
+ def test_xml_simple_transparency
16
+ assert_equal XmlSimple.xml_in("<asdf />"), FasterXmlSimple.xml_in("<asdf />")
17
+ end
18
+
19
+ def test_suppress_empty_variations
20
+ str = "<asdf><fdsa /></asdf>"
21
+
22
+ assert_equal Hash.new, FasterXmlSimple.xml_in(str)["asdf"]["fdsa"]
23
+ assert_nil FasterXmlSimple.xml_in(str, 'suppressempty'=>nil)["asdf"]["fdsa"]
24
+ assert_equal '', FasterXmlSimple.xml_in(str, 'suppressempty'=>'')["asdf"]["fdsa"]
25
+ assert !FasterXmlSimple.xml_in(str, 'suppressempty'=>true)["asdf"].has_key?("fdsa")
26
+ end
27
+
28
+ def test_empty_string_doesnt_crash
29
+ assert_raise(XML::Parser::ParseError) do
30
+ silence_stderr do
31
+ FasterXmlSimple.xml_in('')
32
+ end
33
+ end
34
+ end
35
+
36
+ def test_keeproot_false
37
+ str = "<asdf><fdsa>1</fdsa></asdf>"
38
+ expected = {"fdsa"=>"1"}
39
+ assert_equal expected, FasterXmlSimple.xml_in(str, 'keeproot'=>false)
40
+ end
41
+
42
+ def test_keeproot_false_with_force_content
43
+ str = "<asdf><fdsa>1</fdsa></asdf>"
44
+ expected = {"fdsa"=>{"__content__"=>"1"}}
45
+ assert_equal expected, FasterXmlSimple.xml_in(str, 'keeproot'=>false, 'forcecontent'=>true)
46
+ end
47
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'yaml'
3
+
4
+ class XmlSimpleComparisonTest < FasterXSTest
5
+
6
+ # Define test methods
7
+
8
+ Dir["test/fixtures/test-*.xml"].each do |file_name|
9
+ xml_file_name = file_name
10
+ method_name = File.basename(file_name, ".xml").gsub('-', '_')
11
+ yml_file_name = file_name.gsub('xml', 'yml')
12
+ rails_yml_file_name = file_name.gsub('xml', 'rails.yml')
13
+ class_eval <<-EOV, __FILE__, __LINE__
14
+ def #{method_name}
15
+ assert_equal YAML.load(File.read('#{yml_file_name}')),
16
+ FasterXmlSimple.xml_in(File.read('#{xml_file_name}'), default_options )
17
+ end
18
+
19
+ def #{method_name}_rails
20
+ assert_equal YAML.load(File.read('#{rails_yml_file_name}')),
21
+ FasterXmlSimple.xml_in(File.read('#{xml_file_name}'), rails_options)
22
+ end
23
+ EOV
24
+ end
25
+
26
+ def default_options
27
+ {
28
+ 'keeproot' => true,
29
+ 'contentkey' => '__content__',
30
+ 'forcecontent' => true,
31
+ 'suppressempty' => nil,
32
+ 'forcearray' => ['something-else']
33
+ }
34
+ end
35
+
36
+ def rails_options
37
+ {
38
+ 'forcearray' => false,
39
+ 'forcecontent' => true,
40
+ 'keeproot' => true,
41
+ 'contentkey' => '__content__'
42
+ }
43
+ end
44
+
45
+
46
+ end
@@ -0,0 +1,6 @@
1
+ class CampaignMonitorTest < Test::Unit::TestCase
2
+
3
+ def setup
4
+
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: patientslikeme-campaign_monitor
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.1"
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Weiskotten
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-12-22 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A simple wrapper class that provides basic access to the Campaign Monitor API
17
+ email: jweiskotten@patientslikeme.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - campaign_monitor.gemspec
26
+ - init.rb
27
+ - install.rb
28
+ - MIT-LICENSE
29
+ - Rakefile
30
+ - README.rdoc
31
+ - lib/campaign_monitor.rb
32
+ - lib/campaign_monitor/campaign.rb
33
+ - lib/campaign_monitor/client.rb
34
+ - lib/campaign_monitor/list.rb
35
+ - lib/campaign_monitor/result.rb
36
+ - lib/campaign_monitor/subscriber.rb
37
+ - support/faster-xml-simple/lib/faster_xml_simple.rb
38
+ - support/faster-xml-simple/test/xml_simple_comparison_test.rb
39
+ - support/faster-xml-simple/test/regression_test.rb
40
+ - test/campaign_monitor_test.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/patientslikeme/campaign_monitor/
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements:
61
+ - none
62
+ rubyforge_project:
63
+ rubygems_version: 1.2.0
64
+ signing_key:
65
+ specification_version: 2
66
+ summary: Provides access to the Campaign Monitor API.
67
+ test_files:
68
+ - test/campaign_monitor_test.rb