newsletter.rb
7.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
require 'csv'
class NewsletterPlugin::Newsletter < ActiveRecord::Base
belongs_to :environment
belongs_to :person
validates_presence_of :environment, :person
validates_uniqueness_of :environment_id
validates_numericality_of :periodicity, only_integer: true, greater_than: -1, message: _('must be a positive number')
validates_numericality_of :posts_per_blog, only_integer: true, greater_than: -1, message: _('must be a positive number')
attr_accessible :environment, :enabled, :periodicity, :subject, :posts_per_blog, :footer, :blog_ids, :additional_recipients, :person, :person_id, :moderated
scope :enabled, -> { where enabled: true }
# These methods are used by NewsletterMailing
def people
list = unsubscribers.map{|i| "'#{i}'"}.join(',')
if list.empty?
environment.people
else
environment.people
.joins('LEFT OUTER JOIN users ON (users.id = profiles.user_id)')
.where("users.email NOT IN (#{list})")
end
end
def name
environment.name
end
def contact_email
environment.noreply_email
end
def top_url
environment.top_url
end
def unsubscribe_url
"#{top_url}/plugin/newsletter/unsubscribe"
end
serialize :blog_ids, Array
serialize :additional_recipients, Array
def blog_ids
self[:blog_ids].map(&:to_i) || []
end
validates_each :blog_ids do |record, attr, value|
if record.environment
unless value.delete_if(&:zero?).select { |id| !Blog.find_by(id: id) || Blog.find(id).environment != record.environment }.empty?
record.errors.add(attr, _('must be valid'))
end
end
unless value.uniq.length == value.length
record.errors.add(attr, _('must not have duplicates'))
end
end
validates_each :additional_recipients do |record, attr, value|
unless value.reject { |recipient| recipient[:email] =~ Noosfero::Constants::EMAIL_FORMAT }.empty?
record.errors.add(attr, _('must have only valid emails'))
end
end
def next_send_at
(self.last_send_at || DateTime.now) + self.periodicity.days
end
def must_be_sent_today?
return true unless self.last_send_at
Date.today >= self.next_send_at.to_date
end
def blogs
Blog.where(:id => blog_ids)
end
def posts(data = {})
limit = self.posts_per_blog.zero? ? nil : self.posts_per_blog
posts = if self.last_send_at.nil?
self.blogs.flat_map{ |blog| blog.posts.limit limit }
else
self.blogs.flat_map{ |blog| blog.posts.where("published_at >= :last_send_at", {last_send_at: self.last_send_at}).limit limit }
end
data[:post_ids].nil? ? posts : posts.select{|post| data[:post_ids].include?(post.id.to_s)}
end
CSS = {
'breakingnews-wrap' => 'background-color: #EFEFEF; padding: 40px 0',
'breakingnews' => 'width: 640px; margin: auto; background-color: white; border: 1px solid #ddd; border-spacing: 0; padding: 0',
'newsletter-public-link' => 'width: 640px; margin: auto; font-size: small; color: #555; font-style: italic; text-align: right; margin-bottom: 15px; font-family: sans;',
'newsletter-header' => 'padding: 0',
'header-image' => 'width: 100%',
'post-image' => 'padding-left: 20px; width: 25%; border-bottom: 1px dashed #DDD',
'post-info' => 'font-family: Arial, Verdana; padding: 20px; width: 75%; border-bottom: 1px dashed #DDD',
'post-date' => 'font-size: 12px;',
'post-lead' => 'font-size: 14px; text-align: justify',
'post-title' => 'color: #000; text-decoration: none; font-size: 16px; text-align: justify',
'read-more-line' => 'text-align: right',
'read-more-link' => 'color: #000; font-size: 12px;',
'newsletter-unsubscribe' => 'width: 640px; margin: auto; font-size: small; color: #555; font-style: italic; text-align: center; margin-top: 15px; font-family: sans;'
}
# to be able to generate HTML
include ActionView::Helpers
include Rails.application.routes.url_helpers
include DatesHelper
def message_to_public_link
content_tag(:p, (_("If you can't view this email, %s.") % link_to(_('click here'), '{mailing_url}')).html_safe, :id => 'newsletter-public-link').html_safe
end
def message_to_unsubscribe
content_tag(:div, _("This is an automatically generated email, please do not reply. If you do not wish to receive future newsletter emails, %s.").html_safe % link_to(_("cancel your subscription here"), self.unsubscribe_url, :style => CSS['public-link']), :style => CSS['newsletter-unsubscribe'], :id => 'newsletter-unsubscribe').html_safe
end
def read_more(link_address)
content_tag(:p, link_to(_('Read more'), link_address, :style => CSS['read-more-link']), :style => CSS['read-more-line'])
end
def post_with_image(post)
content_tag(:tr,content_tag(:td,tag(:img, :src => "#{self.environment.top_url}#{post.image.public_filename(:big)}", :id => post.id),:style => CSS['post-image'])+content_tag(:td,content_tag(:span, show_date(post.published_at), :style => CSS['post-date'])+content_tag(:h3, link_to(h(post.title), post.url, :style => CSS['post-title']))+content_tag(:p,sanitize(post.lead(190), tags: %w(strong em b i)),:style => CSS['post-lead'])+read_more(post.url), :style => CSS['post-info']))
end
def post_without_image(post)
content_tag(:tr, content_tag(:td,content_tag(:span, show_date(post.published_at),:style => CSS['post-date'], :id => post.id)+content_tag(:h3, link_to(h(post.title), post.url,:style => CSS['post-title']))+content_tag(:p,sanitize(post.lead(360), tags: %w(strong em b i)),:style => CSS['post-lead'])+read_more(post.url),:colspan => 2, :style => CSS['post-info']))
end
def body(data = {})
content_tag(:div, content_tag(:div, message_to_public_link, :style => CSS['newsletter-public-link']).html_safe+content_tag(:table,(self.image.nil? ? '' : content_tag(:tr, content_tag(:th, tag(:img, :src => "#{self.environment.top_url}#{self.image.public_filename}", :style => CSS['header-image']),:colspan => 2),:style => CSS['newsletter-header'])).html_safe+self.posts(data).map do |post|
if post.image
post_with_image(post)
else
post_without_image(post)
end
end.join().html_safe+content_tag(:tr, content_tag(:td, self.footer, :colspan => 2)),:style => CSS['breakingnews']).html_safe+content_tag(:div,message_to_unsubscribe, :style => CSS['newsletter-unsubscribe']),:style => CSS['breakingnews-wrap']).html_safe
end
def default_subject
_('Breaking news')
end
def subject
self[:subject] || default_subject
end
def import_recipients(file, name_column = nil, email_column = nil, headers = nil)
name_column ||= 1
email_column ||= 2
headers ||= false
if File.extname(file.original_filename) == '.csv'
[",", ";", "\t"].each do |sep|
parsed_recipients = []
CSV.foreach(file.path, { headers: headers, col_sep: sep }) do |row|
parsed_recipients << {name: row[name_column.to_i - 1], email: row[email_column.to_i - 1]}
end
self.additional_recipients = parsed_recipients
break if self.valid? || !self.errors.include?(:additional_recipients)
end
else
#FIXME find a better way to deal with errors
self.errors.add(:additional_recipients, _("have unknown file type: %s" % file.original_filename))
end
end
acts_as_having_image
def last_send_at
last_mailing = NewsletterPlugin::NewsletterMailing.where(source_id: self.id).last
last_mailing.nil? ? nil : last_mailing.created_at
end
def has_posts_in_the_period?
! self.posts.empty?
end
serialize :unsubscribers, Array
def unsubscribe(email)
unsubscribers.push(email).uniq!
end
end