cirandas.net

ref: master

plugins/newsletter/lib/newsletter_plugin/newsletter.rb


  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
188
189
require 'csv'

class NewsletterPlugin::Newsletter < ApplicationRecord

  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 || "").html_safe, :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

  extend ActsAsHavingImage::ClassMethods
  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!
    save!
  end

end