aiwilliams-mlist 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,14 @@
1
+ *0.1.7 [Bug Fixes, Delivery Improvements] (2009-08-14)
2
+
3
+ *   is maintained as spacing in html_to_text conversions [aiwilliams]
4
+ * Fixed bug where delivered email included the original Cc header. This could cause all kinds of problems. [aiwilliams]
5
+ * Fixed bug where list addresses in Cc header were not be utilized in determining the lists to deliver to. [aiwilliams]
6
+ * Fixed bug where delivery date was left to the mercy of the Rails time configuration. Using Time.now. [aiwilliams]
7
+ * Manager list can indicate if reply-to should be list address or subscriber. [aiwilliams]
8
+ * Leaving the reply-to field intact when it already exists on incoming email. [aiwilliams]
9
+ * Messages will not be delivered to addresses found in the TO and CC fields in order to keep those recipients from getting two emails. [aiwilliams]
10
+ * Added ability to optionally copy sender. [aiwilliams]
11
+
1
12
  *0.1.6 [Bug Fixes] (March 5, 2009)
2
13
 
3
14
  * Messages are processed even when there are no recipients [aiwilliams]
@@ -1,4 +1,4 @@
1
1
  ---
2
- :minor: 1
3
- :patch: 6
2
+ :patch: 7
4
3
  :major: 0
4
+ :minor: 1
@@ -10,6 +10,14 @@ module MList
10
10
  tmail.header_string('x-beenthere') == list.address
11
11
  end
12
12
 
13
+ def date
14
+ if date_from_email = super
15
+ return date_from_email
16
+ else
17
+ self.created_at ||= Time.now
18
+ end
19
+ end
20
+
13
21
  def from
14
22
  tmail.header_string('from')
15
23
  end
@@ -17,7 +25,7 @@ module MList
17
25
  # Answers the usable destination addresses of the email.
18
26
  #
19
27
  def list_addresses
20
- bounce? ? tmail.header_string('to').match(/\Amlist-(.*)\Z/)[1] : tmail.to.collect(&:downcase)
28
+ bounce? ? tmail.header_string('to').match(/\Amlist-(.*)\Z/)[1] : recipient_addresses
21
29
  end
22
30
 
23
31
  # Answers true if this email is a bounce.
@@ -48,6 +56,12 @@ module MList
48
56
  end
49
57
  end
50
58
 
59
+ # Answers the set of addresses found in the TO and CC fields of the email.
60
+ #
61
+ def recipient_addresses
62
+ (Array(tmail.to) + Array(tmail.cc)).collect(&:downcase).uniq
63
+ end
64
+
51
65
  def respond_to?(method)
52
66
  super || (method.to_s !~ /=\Z/ && tmail.respond_to?(method))
53
67
  end
@@ -12,7 +12,7 @@ module MList
12
12
  class EmailPost
13
13
  include MList::Util::EmailHelpers
14
14
 
15
- ATTRIBUTE_NAMES = %w(html text mailer subject subscriber)
15
+ ATTRIBUTE_NAMES = %w(copy_sender html text mailer subject subscriber)
16
16
  ATTRIBUTE_NAMES.each do |attribute_name|
17
17
  define_method(attribute_name) do
18
18
  @attributes[attribute_name]
@@ -44,6 +44,10 @@ module MList
44
44
  end
45
45
  end
46
46
 
47
+ def copy_sender=(value)
48
+ @attributes['copy_sender'] = %w(true 1).include?(value.to_s)
49
+ end
50
+
47
51
  def reply_to_message=(message)
48
52
  if message
49
53
  @parent_identifier = message.identifier
@@ -74,10 +78,7 @@ module MList
74
78
  builder.references = [bracket(parent_identifier)]
75
79
  end
76
80
 
77
- from = subscriber.email_address
78
- from = "#{subscriber.display_name} #{bracket(from)}" if subscriber.respond_to?(:display_name)
79
- builder.from = from
80
-
81
+ builder.from = subscriber_name_and_address(subscriber)
81
82
  builder.subject = subject
82
83
 
83
84
  if html
@@ -94,6 +95,14 @@ module MList
94
95
 
95
96
  # vvv ActiveRecord validations interface implementation vvv
96
97
 
98
+ def self.human_name(options = {})
99
+ self.name.humanize
100
+ end
101
+
102
+ def self.self_and_descendants_from_active_record #nodoc:
103
+ [self]
104
+ end
105
+
97
106
  def self.human_attribute_name(attribute_key_name, options = {})
98
107
  attribute_key_name.humanize
99
108
  end
@@ -25,10 +25,10 @@ module MList
25
25
  end
26
26
 
27
27
  # Answers the footer content for this list. Default implementation is very
28
- # simple right now. Expect improvements in the future.
28
+ # simple.
29
29
  #
30
30
  def footer_content(message)
31
- %Q{The "#{label}" mailing list\nTo post messages, send email to #{post_url}}
31
+ %Q{The "#{label}" mailing list\nPost messages: #{post_url}}
32
32
  end
33
33
 
34
34
  # Answer a suitable label for the list, which will be used in various
@@ -67,6 +67,16 @@ module MList
67
67
  nil
68
68
  end
69
69
 
70
+ # Should the sender of an email be copied in the publication? Defaults to
71
+ # false. If _recipients_ includes the sender email address, it will be
72
+ # removed if this answers false.
73
+ #
74
+ # The sending subscriber is provided to support preferential answering.
75
+ #
76
+ def copy_sender?(subscriber)
77
+ false
78
+ end
79
+
70
80
  # The web address of the list help site, nil if this is not supported.
71
81
  #
72
82
  def help_url
@@ -86,6 +96,13 @@ module MList
86
96
  address
87
97
  end
88
98
 
99
+ # Should the reply-to header be set to the list's address? Defaults to
100
+ # true. If false is returned, the reply-to will be the subscriber address.
101
+ #
102
+ def reply_to_list?
103
+ true
104
+ end
105
+
89
106
  # The web url where subscriptions to this list may be created, nil if this
90
107
  # is not supported.
91
108
  #
@@ -100,16 +117,15 @@ module MList
100
117
  nil
101
118
  end
102
119
 
103
- # A list is responsible for answering the recipient subscribers.
120
+ # A list is responsible for answering the recipient subscribers. The
121
+ # answer may or may not include the subscriber; _copy_sender?_ will be
122
+ # invoked and the subscriber will be added or removed from the Array.
104
123
  #
105
- # The subscriber of the incoming message is provided if the list would
106
- # like to exclude it from the returned list. It is not assumed that it
107
- # will be included or excluded, thereby allowing the list to decide. This
108
- # default implementation does not include the sending subscriber in the
109
- # list of recipients.
124
+ # The sending subscriber is provided if the list would like to utilize it
125
+ # in calculating the recipients.
110
126
  #
111
127
  def recipients(subscriber)
112
- subscribers.reject {|s| s.email_address == subscriber.email_address}
128
+ subscribers
113
129
  end
114
130
 
115
131
  # A list must answer the subscriber who's email address is that of the one
@@ -43,7 +43,7 @@ module MList
43
43
  :subscriber => email.subscriber,
44
44
  :recipients => list.recipients(email.subscriber),
45
45
  :email => MList::Email.new(:source => email.to_s)
46
- ), :search_parent => false
46
+ ), :search_parent => false, :copy_sender => email.copy_sender
47
47
  end
48
48
 
49
49
  # Processes the email received by the MList::Server.
@@ -55,7 +55,7 @@ module MList
55
55
  :subscriber => subscriber,
56
56
  :recipients => recipients,
57
57
  :email => email
58
- )
58
+ ), :copy_sender => list.copy_sender?(subscriber)
59
59
  end
60
60
 
61
61
  # Answers the provided subject with superfluous 're:' and this list's
@@ -175,7 +175,8 @@ module MList
175
175
 
176
176
  options = {
177
177
  :search_parent => true,
178
- :delivery_time => Time.now
178
+ :delivery_time => Time.now,
179
+ :copy_sender => false
179
180
  }.merge(options)
180
181
 
181
182
  transaction do
@@ -198,15 +199,25 @@ module MList
198
199
  message.identifier = outgoing_server.generate_message_id
199
200
  message.created_at = options[:delivery_time]
200
201
  message.subject = clean_subject(message.subject)
202
+
203
+ recipient_addresses = message.recipient_addresses
204
+ sender_address = message.subscriber.email_address
205
+ if options[:copy_sender]
206
+ recipient_addresses << sender_address unless recipient_addresses.include?(sender_address)
207
+ else
208
+ recipient_addresses.delete(sender_address)
209
+ end
210
+
201
211
  returning(message.delivery) do |delivery|
202
- delivery.date = message.created_at
212
+ delivery.date ||= options[:delivery_time]
203
213
  delivery.message_id = message.identifier
204
214
  delivery.mailer = message.mailer
205
215
  delivery.headers = list_headers
206
216
  delivery.subject = list_subject(message.subject)
207
217
  delivery.to = address
208
- delivery.bcc = message.recipients.collect(&:email_address)
209
- delivery.reply_to = "#{label} <#{post_url}>"
218
+ delivery.cc = []
219
+ delivery.bcc = recipient_addresses
220
+ delivery.reply_to ||= reply_to_header(message)
210
221
  prepare_list_footer(delivery, message)
211
222
  end
212
223
  end
@@ -240,6 +251,14 @@ module MList
240
251
  message.parent ? message.parent.thread : threads.build
241
252
  end
242
253
 
254
+ def reply_to_header(message)
255
+ if list.reply_to_list?
256
+ "#{label} #{bracket(address)}"
257
+ else
258
+ subscriber_name_and_address(message.subscriber)
259
+ end
260
+ end
261
+
243
262
  def subject_prefix_regex
244
263
  @subject_prefix_regex ||= Regexp.new(Regexp.escape(subject_prefix) + ' ')
245
264
  end
@@ -86,6 +86,16 @@ module MList
86
86
  end
87
87
  alias_method_chain :parent=, :identifier_capture
88
88
 
89
+ # Answers the recipient email addresses from the MList::List recipient
90
+ # subscribers, except those that are in the email TO or CC fields as
91
+ # placed there by the sending MUA. It is assumed that those addresses have
92
+ # received a copy of the email already, and that by including them here,
93
+ # we would cause them to receive two copies of the message.
94
+ #
95
+ def recipient_addresses
96
+ @recipients.collect(&:email_address).collect(&:downcase) - email.recipient_addresses
97
+ end
98
+
89
99
  # Answers the subject with 'Re:' prefixed. Note that it is the
90
100
  # responsibility of the MList::MailList to perform any processing of the
91
101
  # persisted subject (ie, cleaning up labels, etc).
@@ -15,12 +15,12 @@ module MList
15
15
 
16
16
  def next(message)
17
17
  i = tree_order.index(message)
18
- messages[i + 1] unless messages.size < i
18
+ tree_order[i + 1] unless messages.size < i
19
19
  end
20
20
 
21
21
  def previous(message)
22
22
  i = tree_order.index(message)
23
- messages[i - 1] if i > 0
23
+ tree_order[i - 1] if i > 0
24
24
  end
25
25
 
26
26
  def subject
@@ -2,8 +2,15 @@ module MList
2
2
  module Util
3
3
 
4
4
  class HtmlTextExtraction
5
+
6
+ # We need a way to maintain non-breaking spaces. Hpricot will replace
7
+ # them with ??.chr. We can easily teach it to convert it to a space, but
8
+ # then we lose the information in the Text node that we need to keep the
9
+ # space around, since that is what they would see in a view of the HTML.
10
+ NBSP = '!!!NBSP!!!'
11
+
5
12
  def initialize(html)
6
- @doc = Hpricot(html)
13
+ @doc = Hpricot(html.gsub('&nbsp;', NBSP))
7
14
  end
8
15
 
9
16
  def execute
@@ -19,7 +26,7 @@ module MList
19
26
  end
20
27
  @text << "\n\n--\n#{refs.join("\n")}"
21
28
  end
22
- @text
29
+ @text.gsub(NBSP, ' ')
23
30
  end
24
31
 
25
32
  def extract_text_from_node(node)
@@ -95,6 +102,12 @@ module MList
95
102
  text.to_s.gsub(/\r\n?/, "\n")
96
103
  end
97
104
 
105
+ def subscriber_name_and_address(subscriber)
106
+ a = subscriber.email_address
107
+ a = "#{subscriber.display_name} #{bracket(a)}" if subscriber.respond_to?(:display_name)
108
+ a
109
+ end
110
+
98
111
  BRACKETS_RE = /\A<(.*?)>\Z/
99
112
  def bracket(string)
100
113
  string.blank? || string =~ BRACKETS_RE ? string : "<#{string}>"
@@ -5,8 +5,6 @@ module MList
5
5
  def date
6
6
  if date = tmail.header_string('date')
7
7
  Time.parse(date)
8
- else
9
- self.created_at ||= Time.now
10
8
  end
11
9
  end
12
10
 
@@ -1,28 +1,22 @@
1
- require 'dispatcher' unless defined?(::Dispatcher)
2
-
3
- Dispatcher.module_eval do
4
- # Provides the mechinism to support applications that want to observe MList
5
- # models.
6
- #
7
- # ActiveRecord observers are reloaded at each request in development mode.
8
- # They will be registered with the MList models each time. Since the MList
9
- # models are required once at initialization, there will always only be one
10
- # instance of the model class, and therefore, many instances of the observer
11
- # class registered with it; all but the most recent are invalid, since they
12
- # were undefined when the dispatcher reloaded the application.
13
- #
14
- # Why not an initializer "to_prepare" block? Simply because we must clear
15
- # the observers in the ActiveRecord classes before the
16
- # ActiveRecord::Base.instantiate_observers call is made by the prepare block
17
- # that we cannot get in front of with the initializer approach. Also, it
18
- # lessens the configuration burden of the MList client application.
19
- #
20
- # Should we ever have observers in MList, this will likely need more careful
21
- # attention.
22
- #
23
- def reload_application_with_plugin_record_support
24
- ActiveRecord::Base.send(:subclasses).each(&:delete_observers)
25
- reload_application_without_plugin_record_support
1
+ # Provides the mechinism to support applications that want to observe MList
2
+ # models.
3
+ #
4
+ # ActiveRecord observers are reloaded at each request in development mode.
5
+ # They will be registered with the MList models each time. Since the MList
6
+ # models are required once at initialization, there will always only be one
7
+ # instance of the model class, and therefore, many instances of the observer
8
+ # class registered with it; all but the most recent are invalid, since they
9
+ # were undefined when the dispatcher reloaded the application.
10
+ #
11
+ # Should we ever have observers in MList, this will likely need more careful
12
+ # attention.
13
+ #
14
+ unless Rails.configuration.cache_classes
15
+ class << ActiveRecord::Base
16
+ def instantiate_observers_with_mlist_observers
17
+ subclasses.each(&:delete_observers)
18
+ instantiate_observers_without_mlist_observers
19
+ end
20
+ alias_method_chain :instantiate_observers, :mlist_observers
26
21
  end
27
- alias_method_chain :reload_application, :plugin_record_support
28
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aiwilliams-mlist
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Williams
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-03 00:00:00 -08:00
12
+ date: 2009-08-14 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -59,6 +59,7 @@ files:
59
59
  - rails/init.rb
60
60
  has_rdoc: false
61
61
  homepage: http://github.com/aiwilliams/mlist
62
+ licenses:
62
63
  post_install_message:
63
64
  rdoc_options: []
64
65
 
@@ -79,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
80
  requirements: []
80
81
 
81
82
  rubyforge_project:
82
- rubygems_version: 1.2.0
83
+ rubygems_version: 1.3.5
83
84
  signing_key:
84
85
  specification_version: 2
85
86
  summary: A Ruby mailing list library designed to be integrated into other applications.