reidab-campaign_monitor 1.3.3

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,217 @@
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
+ if value.is_a? Array
206
+ value.each do |array_value|
207
+ arr << { "Key" => key, "Value" => array_value }
208
+ end
209
+ else
210
+ arr << { "Key" => key, "Value" => value }
211
+ end
212
+ end
213
+ arr
214
+ end
215
+
216
+ end
217
+ 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['__content__'] == 'True'
39
+ return false if result['__content__'] == 'False'
40
+ raise "Invalid value for is_subscribed?: #{result}"
41
+ end
42
+ end
43
+ 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
@@ -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