Commit 4df54eb8b390b627c677c6969f18af2e39b49fa2

Authored by Joenio Costa
1 parent 296a549d

new plugin: newsletter

a plugin that periodically sends newsletter via email to network users

signed-off-by: Larissa Reis <larissa@colivre.coop.br>
signed-off-by: Melissa Wen <melissa@colivre.coop.br>
plugins/newsletter/config/schedule.rb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +every 1.day do
  2 + runner "NewsletterPlugin.compile_and_send_newsletters"
  3 +end
... ...
plugins/newsletter/controllers/newsletter_plugin_admin_controller.rb 0 → 100644
... ... @@ -0,0 +1,48 @@
  1 +class NewsletterPluginAdminController < PluginAdminController
  2 +
  3 + def index
  4 + @newsletter = NewsletterPlugin::Newsletter.where(environment_id: environment.id).first_or_initialize
  5 +
  6 + if request.post?
  7 + # token input gives the param as a comma separated string
  8 + params[:newsletter][:blog_ids] = (params[:newsletter][:blog_ids] || '').split(',')
  9 +
  10 + params[:newsletter][:person_id] = user.id
  11 +
  12 + file = params[:file]
  13 + if file && file[:recipients].present?
  14 + @newsletter.import_recipients(file[:recipients], file[:name], file[:email], file[:headers].present?)
  15 + end
  16 +
  17 + if !@newsletter.errors.any? && @newsletter.update_attributes(params[:newsletter])
  18 + if params['visualize']
  19 + @message = @newsletter.body
  20 + render :file => 'mailing/sender/notification', :layout => false
  21 + else
  22 + session[:notice] = _('Newsletter updated.')
  23 + end
  24 + else
  25 + session[:notice] = _('Newsletter could not be saved.')
  26 + end
  27 + end
  28 +
  29 + @blogs = Blog.includes(:profile).find_all_by_id(@newsletter.blog_ids)
  30 + end
  31 +
  32 + #TODO: Make this query faster
  33 + def search_communities
  34 + communities = environment.communities
  35 + blogs = Blog.joins(:profile).where(profiles: {environment_id: environment.id})
  36 +
  37 + found_communities = find_by_contents(:communities, environment, communities, params['q'], {:page => 1})[:results]
  38 + found_blogs = find_by_contents(:blogs, environment, blogs, params['q'], {:page => 1})[:results]
  39 +
  40 + results = (found_blogs + found_communities.map(&:blogs).flatten).uniq
  41 + render :text => results.map { |blog| {:id => blog.id, :name => _("%s in %s") % [blog.name, blog.profile.name]} }.to_json
  42 + end
  43 +
  44 + def recipients
  45 + @additional_recipients = NewsletterPlugin::Newsletter.where(environment_id: environment.id).first_or_initialize.additional_recipients
  46 + end
  47 +
  48 +end
... ...
plugins/newsletter/controllers/newsletter_plugin_controller.rb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +class NewsletterPluginController < PublicController
  2 +
  3 + before_filter :login_required, :only => :confirm_unsubscription
  4 +
  5 + def mailing
  6 + if NewsletterPlugin::NewsletterMailing.exists?(params[:id])
  7 + mailing = NewsletterPlugin::NewsletterMailing.find(params[:id])
  8 + @message = mailing.body
  9 + render :file => 'mailing/sender/notification', :layout => false
  10 + else
  11 + render :action => 'mailing_not_found'
  12 + end
  13 + end
  14 +
  15 + def confirm_unsubscription
  16 + if request.post?
  17 + session[:notice] = _('You was unsubscribed from newsletter.')
  18 + @newsletter = NewsletterPlugin::Newsletter.where(environment_id: environment.id).first
  19 + @newsletter.unsubscribe(current_user.email)
  20 + redirect_to :controller => :home
  21 + end
  22 + end
  23 +
  24 +end
... ...
plugins/newsletter/db/migrate/20150717195546_newsletter_plugin_newsletters.rb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +class NewsletterPluginNewsletters < ActiveRecord::Migration
  2 + def up
  3 + create_table :newsletter_plugin_newsletters do |t|
  4 + t.references :environment, :null => false
  5 + t.references :person, :null => false
  6 + t.boolean :enabled, :default => false
  7 + t.string :subject
  8 + t.integer :periodicity, :default => 0
  9 + t.integer :posts_per_blog, :default => 0
  10 + t.integer :image_id
  11 + t.text :footer
  12 + t.text :blog_ids
  13 + t.text :additional_recipients
  14 + t.boolean :moderated
  15 + t.text :unsubscribers
  16 + end
  17 + add_index :newsletter_plugin_newsletters, :environment_id, :uniq => true
  18 + end
  19 +
  20 + def down
  21 + remove_index :newsletter_plugin_newsletters, :environment_id
  22 + drop_table :newsletter_plugin_newsletters
  23 + end
  24 +end
... ...
plugins/newsletter/features/newsletter_plugin.feature 0 → 100644
... ... @@ -0,0 +1,40 @@
  1 +Feature: newsletter plugin
  2 +
  3 + Background:
  4 + Given the following users
  5 + | login | name |
  6 + | joaosilva | Joao Silva |
  7 + And I am logged in as "joaosilva"
  8 +
  9 + Scenario: as admin I can configure plugin
  10 + Given I am logged in as admin
  11 + When I go to the environment control panel
  12 + And I follow "Plugins"
  13 + Then I should see "Configuration" linking to "/admin/plugin/newsletter"
  14 +
  15 + Scenario: in the newsletter settings I can see the field to enable/disable
  16 + Given I am logged in as admin
  17 + When I go to the environment control panel
  18 + And I follow "Plugins"
  19 + And I follow "Configuration"
  20 + Then I should see "Enable send of newsletter to members on this environment"
  21 +
  22 + Scenario: redirect to newsletter visualization after save and visualize
  23 + Given I am logged in as admin
  24 + And "NewsletterPlugin" plugin is enabled
  25 + When I go to the environment control panel
  26 + And I follow "Plugins"
  27 + And I follow "Configuration"
  28 + And I press "Save and visualize"
  29 + Then I should see "If you can't view this email, click here"
  30 + And I should not see "Newsletter settings"
  31 +
  32 + Scenario: stay on newsletter settings page after save
  33 + Given I am logged in as admin
  34 + And "NewsletterPlugin" plugin is enabled
  35 + When I go to the environment control panel
  36 + And I follow "Plugins"
  37 + And I follow "Configuration"
  38 + And I press "Save"
  39 + Then I should see "Newsletter settings"
  40 + And I should not see "If you can't view this email, click here"
... ...
plugins/newsletter/lib/newsletter_plugin.rb 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +class NewsletterPlugin < Noosfero::Plugin
  2 +
  3 + def self.plugin_name
  4 + "Newsletter"
  5 + end
  6 +
  7 + def self.plugin_description
  8 + _("Periodically sends newsletter via email to network users")
  9 + end
  10 +
  11 + def js_files
  12 + 'newsletter_plugin.js'
  13 + end
  14 +
  15 + def stylesheet?
  16 + true
  17 + end
  18 +
  19 + def self.compile_and_send_newsletters
  20 + NewsletterPlugin::Newsletter.enabled.each do |newsletter|
  21 + if newsletter.must_be_sent_today? && newsletter.has_posts_in_the_period?
  22 + if newsletter.moderated
  23 + NewsletterPlugin::ModerateNewsletter.create!(
  24 + :newsletter_id => newsletter.id,
  25 + :environment => newsletter.environment
  26 + )
  27 + else
  28 + mailing = NewsletterPlugin::NewsletterMailing.create!(
  29 + :source => newsletter,
  30 + :subject => newsletter.subject,
  31 + :body => newsletter.body,
  32 + :person => newsletter.person,
  33 + :locale => newsletter.environment.default_locale,
  34 + )
  35 + mailing.update_attribute(:body, mailing.body.gsub('{mailing_url}', mailing.url))
  36 + end
  37 + end
  38 + end
  39 + end
  40 +
  41 +end
... ...
plugins/newsletter/lib/newsletter_plugin/moderate_newsletter.rb 0 → 100644
... ... @@ -0,0 +1,53 @@
  1 +class NewsletterPlugin::ModerateNewsletter < Task
  2 +
  3 + settings_items :newsletter_id, :post_ids
  4 + validates_presence_of :newsletter_id
  5 +
  6 + alias :environment :target
  7 + alias :environment= :target=
  8 +
  9 + def perform
  10 + newsletter = NewsletterPlugin::Newsletter.find(newsletter_id)
  11 + self.post_ids ||= []
  12 + mailing = NewsletterPlugin::NewsletterMailing.create!(
  13 + :source => newsletter,
  14 + :subject => newsletter.subject,
  15 + :body => newsletter.body(:post_ids => self.post_ids.reject{|id| id.to_i.zero?}),
  16 + :person => newsletter.person,
  17 + :locale => newsletter.environment.default_locale,
  18 + )
  19 + mailing.update_attribute(:body, mailing.body.gsub('{mailing_url}', mailing.url))
  20 + end
  21 +
  22 + def title
  23 + _("Moderate newsletter")
  24 + end
  25 +
  26 + def subject
  27 + nil
  28 + end
  29 +
  30 + def linked_subject
  31 + nil
  32 + end
  33 +
  34 + def information
  35 + {:message => _('You have to moderate a newsletter.') }
  36 + end
  37 +
  38 + def accept_details
  39 + true
  40 + end
  41 +
  42 + def icon
  43 + {:type => :defined_image, :src => "/images/control-panel/email.png", :name => 'Newsletter'}
  44 + end
  45 +
  46 + def target_notification_message
  47 + _('A newsletter was generated and you need to review it before it is sent to users.')
  48 + end
  49 +
  50 + def target_notification_description
  51 + _('You need to moderate a newsletter.')
  52 + end
  53 +end
... ...
plugins/newsletter/lib/newsletter_plugin/newsletter.rb 0 → 100644
... ... @@ -0,0 +1,191 @@
  1 +require 'csv'
  2 +
  3 +class NewsletterPlugin::Newsletter < Noosfero::Plugin::ActiveRecord
  4 +
  5 + belongs_to :environment
  6 + belongs_to :person
  7 + validates_presence_of :environment, :person
  8 + validates_uniqueness_of :environment_id
  9 + validates_numericality_of :periodicity, only_integer: true, greater_than: -1, message: _('must be a positive number')
  10 + validates_numericality_of :posts_per_blog, only_integer: true, greater_than: -1, message: _('must be a positive number')
  11 +
  12 + attr_accessible :environment, :enabled, :periodicity, :subject, :posts_per_blog, :footer, :blog_ids, :additional_recipients, :person, :person_id, :moderated
  13 +
  14 + scope :enabled, :conditions => { :enabled => true }
  15 +
  16 + # These methods are used by NewsletterMailing
  17 + def people
  18 + list = unsubscribers.map{|i| "'#{i}'"}.join(',')
  19 + if list.empty?
  20 + environment.people
  21 + else
  22 + environment.people.all(
  23 + :joins => "LEFT OUTER JOIN users ON (users.id = profiles.user_id)",
  24 + :conditions => "users.email NOT IN (#{list})"
  25 + )
  26 + end
  27 + end
  28 +
  29 + def name
  30 + environment.name
  31 + end
  32 +
  33 + def contact_email
  34 + environment.noreply_email
  35 + end
  36 +
  37 + def top_url
  38 + environment.top_url
  39 + end
  40 +
  41 + def unsubscribe_url
  42 + "#{top_url}/plugin/newsletter/unsubscribe"
  43 + end
  44 +
  45 + serialize :blog_ids, Array
  46 + serialize :additional_recipients, Array
  47 +
  48 + def blog_ids
  49 + self[:blog_ids].map(&:to_i) || []
  50 + end
  51 +
  52 + validates_each :blog_ids do |record, attr, value|
  53 + if record.environment
  54 + unless value.delete_if(&:zero?).select { |id| !Blog.find_by_id(id) || Blog.find(id).environment != record.environment }.empty?
  55 + record.errors.add(attr, _('must be valid'))
  56 + end
  57 + end
  58 + unless value.uniq.length == value.length
  59 + record.errors.add(attr, _('must not have duplicates'))
  60 + end
  61 + end
  62 +
  63 + validates_each :additional_recipients do |record, attr, value|
  64 + unless value.reject { |recipient| recipient[:email] =~ Noosfero::Constants::EMAIL_FORMAT }.empty?
  65 + record.errors.add(attr, _('must have only valid emails'))
  66 + end
  67 + end
  68 +
  69 + def next_send_at
  70 + (self.last_send_at || DateTime.now) + self.periodicity.days
  71 + end
  72 +
  73 + def must_be_sent_today?
  74 + return true unless self.last_send_at
  75 + Date.today >= self.next_send_at.to_date
  76 + end
  77 +
  78 + def blogs
  79 + Blog.where(:id => blog_ids)
  80 + end
  81 +
  82 + def posts(data = {})
  83 + limit = self.posts_per_blog.zero? ? nil : self.posts_per_blog
  84 + posts = if self.last_send_at.nil?
  85 + self.blogs.map{|blog| blog.posts.all(:limit => limit)}.flatten
  86 + else
  87 + self.blogs.map{|blog| blog.posts.where("published_at >= :last_send_at", {last_send_at: self.last_send_at}).all(:limit => limit)}.flatten
  88 + end
  89 + data[:post_ids].nil? ? posts : posts.select{|post| data[:post_ids].include?(post.id.to_s)}
  90 + end
  91 +
  92 + CSS = {
  93 + 'breakingnews-wrap' => 'background-color: #EFEFEF; padding: 40px 0',
  94 + 'breakingnews' => 'width: 640px; margin: auto; background-color: white; border: 1px solid #ddd; border-spacing: 0; padding: 0',
  95 + 'newsletter-public-link' => 'width: 640px; margin: auto; font-size: small; color: #555; font-style: italic; text-align: right; margin-bottom: 15px; font-family: sans;',
  96 + 'newsletter-header' => 'padding: 0',
  97 + 'header-image' => 'width: 100%',
  98 + 'post-image' => 'padding-left: 20px; width: 25%; border-bottom: 1px dashed #DDD',
  99 + 'post-info' => 'font-family: Arial, Verdana; padding: 20px; width: 75%; border-bottom: 1px dashed #DDD',
  100 + 'post-date' => 'font-size: 12px;',
  101 + 'post-lead' => 'font-size: 14px; text-align: justify',
  102 + 'post-title' => 'color: #000; text-decoration: none; font-size: 16px; text-align: justify',
  103 + 'read-more-line' => 'text-align: right',
  104 + 'read-more-link' => 'color: #000; font-size: 12px;',
  105 + 'newsletter-unsubscribe' => 'width: 640px; margin: auto; font-size: small; color: #555; font-style: italic; text-align: center; margin-top: 15px; font-family: sans;'
  106 + }
  107 +
  108 + # to be able to generate HTML
  109 + include ActionView::Helpers
  110 + include Rails.application.routes.url_helpers
  111 + include DatesHelper
  112 +
  113 + def message_to_public_link
  114 + content_tag(:p, N_("If you can't view this email, %s.") % link_to(N_('click here'), '{mailing_url}'), :id => 'newsletter-public-link')
  115 + end
  116 +
  117 + def message_to_unsubscribe
  118 + content_tag(:div, N_("This is an automatically generated email, please do not reply. If you do not wish to receive future newsletter emails, %s.") % link_to(N_("cancel your subscription here"), self.unsubscribe_url, :style => CSS['public-link']), :style => CSS['newsletter-unsubscribe'], :id => 'newsletter-unsubscribe')
  119 + end
  120 +
  121 + def read_more(link_address)
  122 + content_tag(:p, link_to(N_('Read more'), link_address, :style => CSS['read-more-link']), :style => CSS['read-more-line'])
  123 + end
  124 +
  125 + def post_with_image(post)
  126 + 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)),:style => CSS['post-lead'])+read_more(post.url), :style => CSS['post-info']))
  127 + end
  128 +
  129 + def post_without_image(post)
  130 + 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)),:style => CSS['post-lead'])+read_more(post.url),:colspan => 2, :style => CSS['post-info']))
  131 + end
  132 +
  133 + def body(data = {})
  134 + content_tag(:div, content_tag(:div, message_to_public_link, :style => CSS['newsletter-public-link'])+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']))+self.posts(data).map do |post|
  135 + if post.image
  136 + post_with_image(post)
  137 + else
  138 + post_without_image(post)
  139 + end
  140 + end.join()+content_tag(:tr, content_tag(:td, self.footer, :colspan => 2)),:style => CSS['breakingnews'])+content_tag(:div,message_to_unsubscribe, :style => CSS['newsletter-unsubscribe']),:style => CSS['breakingnews-wrap'])
  141 + end
  142 +
  143 + def default_subject
  144 + N_('Breaking news')
  145 + end
  146 +
  147 + def subject
  148 + self[:subject] || default_subject
  149 + end
  150 +
  151 + def import_recipients(file, name_column = nil, email_column = nil, headers = nil)
  152 + name_column ||= 1
  153 + email_column ||= 2
  154 + headers ||= false
  155 +
  156 + if File.extname(file.original_filename) == '.csv'
  157 + parsed_recipients = []
  158 + CSV.foreach(file.path, headers: headers) do |row|
  159 + parsed_recipients << {name: row[name_column.to_i - 1], email: row[email_column.to_i - 1]}
  160 + end
  161 + self.additional_recipients = parsed_recipients
  162 + else
  163 + #FIXME find a better way to deal with errors
  164 + self.errors.add(:additional_recipients, _("have unknown file type: %s" % file.original_filename))
  165 + end
  166 + end
  167 +
  168 + acts_as_having_image
  169 +
  170 + def last_send_at
  171 + last_mailing = NewsletterPlugin::NewsletterMailing.last(
  172 + :conditions => {:source_id => self.id}
  173 + )
  174 + last_mailing.nil? ? nil : last_mailing.created_at
  175 + end
  176 +
  177 + def sanitize(html)
  178 + html.gsub(/<\/?p>/, '')
  179 + end
  180 +
  181 + def has_posts_in_the_period?
  182 + ! self.posts.empty?
  183 + end
  184 +
  185 + serialize :unsubscribers, Array
  186 +
  187 + def unsubscribe(email)
  188 + unsubscribers.push(email).uniq!
  189 + end
  190 +
  191 +end
... ...
plugins/newsletter/lib/newsletter_plugin/newsletter_mailing.rb 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +class NewsletterPlugin::NewsletterMailing < EnvironmentMailing
  2 +
  3 + attr_accessible :source, :person, :locale
  4 +
  5 + validates_presence_of :person
  6 +
  7 + def url
  8 + "#{self.source.top_url}/plugin/newsletter/mailing/#{self.id}"
  9 + end
  10 +
  11 + def source
  12 + NewsletterPlugin::Newsletter.find(source_id)
  13 + end
  14 +
  15 + def deliver
  16 + source.additional_recipients.each do |recipient|
  17 + begin
  18 + Mailing::Sender.notification(self, recipient[:email]).deliver
  19 + rescue Exception => ex
  20 + Rails.logger.error("#{ex.class.to_s} - #{ex.to_s} at #{__FILE__}:#{__LINE__}")
  21 + end
  22 + end
  23 + super
  24 + end
  25 +
  26 +end
... ...
plugins/newsletter/public/newsletter_plugin.js 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +jQuery(function($) {
  2 + $(".newsletter-toggle-link").live('click', function(){
  3 + element_id = this.getAttribute('element_id');
  4 + toggle_link = this;
  5 + $(element_id).slideToggle(400, function() {
  6 + if ($(toggle_link).find('.ui-icon').hasClass('ui-icon-triangle-1-s'))
  7 + $(toggle_link).find('.ui-icon')
  8 + .removeClass('ui-icon-triangle-1-s')
  9 + .addClass('ui-icon-triangle-1-n');
  10 + else
  11 + $(toggle_link).find('.ui-icon')
  12 + .removeClass('ui-icon-triangle-1-n')
  13 + .addClass('ui-icon-triangle-1-s');
  14 + });
  15 + return false;
  16 + });
  17 +
  18 + $('#file_recipients').change(function(){
  19 + $('#newsletter-file-options input').enable();
  20 + });
  21 +});
... ...
plugins/newsletter/public/style.css 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +.newsletter-toggle-link {
  2 + cursor: pointer;
  3 +}
  4 +#newsletter-footer-field {
  5 + display: none;
  6 +}
  7 +#newsletter-enabled-field input#settings_enabled {
  8 + margin-right: 6px;
  9 +}
  10 +
  11 +#newsletter-moderation-preview #newsletter-public-link,
  12 +#newsletter-moderation-preview #newsletter-unsubscribe {
  13 + display: none;
  14 +}
  15 +
  16 +#newsletter-moderation-preview {
  17 + margin-left: 25px;
  18 +}
  19 +
  20 +#newsletter-moderation-preview input[type=checkbox] {
  21 + margin-left: -27px;
  22 + margin-top: 16px;
  23 + float: left;
  24 +}
... ...
plugins/newsletter/test/functional/newsletter_plugin_admin_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,176 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +
  3 +class NewsletterPluginAdminControllerTest < ActionController::TestCase
  4 +
  5 + def setup
  6 + @controller = NewsletterPluginAdminController.new
  7 + @request = ActionController::TestRequest.new
  8 + @response = ActionController::TestResponse.new
  9 +
  10 + @admin = create_user('admin_newsletter').person
  11 + @environment = @admin.environment
  12 + @environment.add_admin(@admin)
  13 +
  14 + @environment.enable_plugin(NewsletterPlugin)
  15 + @controller.stubs(:environment).returns(@environment)
  16 + end
  17 +
  18 + should 'allow access to admin' do
  19 + login_as @admin.identifier
  20 + get :index
  21 + assert_response :success
  22 + end
  23 +
  24 + should 'save footer setting' do
  25 + login_as @admin.identifier
  26 + post :index,
  27 + :newsletter => { :footer => 'footer of newsletter' }
  28 +
  29 + assert_equal 'footer of newsletter', assigns(:newsletter).footer
  30 + end
  31 +
  32 +
  33 + should 'save header image' do
  34 + login_as @admin.identifier
  35 + post :index,
  36 + :newsletter => {
  37 + :image_builder => {
  38 + :uploaded_data => fixture_file_upload('/files/rails.png', 'image/png')
  39 + }
  40 + }
  41 + assert_equal 'rails.png', assigns(:newsletter).image.filename
  42 + end
  43 +
  44 + should 'save enabled newsletter information' do
  45 + login_as @admin.identifier
  46 + post :index,
  47 + :newsletter => { :enabled => 'true' }
  48 +
  49 + newsletter = NewsletterPlugin::Newsletter.find_by_environment_id(@environment.id)
  50 +
  51 + assert newsletter.enabled
  52 + end
  53 +
  54 + should 'save periodicity newsletter information' do
  55 + login_as @admin.identifier
  56 + post :index,
  57 + :newsletter => { :periodicity => '10' }
  58 +
  59 + newsletter = NewsletterPlugin::Newsletter.find_by_environment_id(@environment.id)
  60 +
  61 + assert_equal 10, newsletter.periodicity
  62 + end
  63 +
  64 + should 'save number of posts per blog setting' do
  65 + login_as @admin.identifier
  66 + post :index,
  67 + :newsletter => { :posts_per_blog => '6' }
  68 +
  69 + assert_equal 6, assigns(:newsletter).posts_per_blog
  70 + end
  71 +
  72 + should 'show error if number of posts per blog is not a positive number' do
  73 + login_as @admin.identifier
  74 + post :index,
  75 + :newsletter => { :posts_per_blog => '-4' }
  76 +
  77 + assert_select 'li', 'Posts per blog must be a positive number'
  78 + end
  79 +
  80 + should 'save blogs for compiling newsletter setting' do
  81 + login_as @admin.identifier
  82 +
  83 + blog1 = fast_create(Blog)
  84 + blog1.profile = fast_create(Profile, environment_id: @environment.id)
  85 + blog1.save
  86 +
  87 + blog2 = fast_create(Blog)
  88 + blog2.profile = fast_create(Profile, environment_id: @environment.id)
  89 + blog2.save
  90 +
  91 + post :index,
  92 + :newsletter => { :blog_ids => "#{blog1.id},#{blog2.id}" }
  93 +
  94 + assert_equivalent [blog1.id,blog2.id], assigns(:newsletter).blog_ids
  95 + end
  96 +
  97 + should 'show error if blog is not in environment' do
  98 + login_as @admin.identifier
  99 +
  100 + blog = fast_create(Blog)
  101 + blog.profile = fast_create(Profile, environment_id: fast_create(Environment).id)
  102 + blog.save
  103 +
  104 + post :index,
  105 + :newsletter => { :blog_ids => "#{blog.id}" }
  106 +
  107 + assert_select 'li', 'Blog ids must be valid'
  108 + end
  109 +
  110 + should 'save logged in admin as person' do
  111 + login_as @admin.identifier
  112 + post :index, :newsletter => { }
  113 +
  114 + assert_equal @admin, assigns(:newsletter).person
  115 + end
  116 +
  117 + should 'receive csv file from user' do
  118 + content = <<-EOS
  119 +Coop1,name1@example.com
  120 +Coop2,name2@example.com
  121 +Coop3,name3@example.com
  122 +EOS
  123 +
  124 + file = Tempfile.new(['recipients', '.csv'])
  125 + file.write(content)
  126 + file.rewind
  127 +
  128 + login_as @admin.identifier
  129 + post :index, newsletter: {}, :file => { recipients: Rack::Test::UploadedFile.new(file, 'text/csv') }
  130 +
  131 + file.close
  132 + file.unlink
  133 +
  134 + assert_equivalent ["name1@example.com", "name2@example.com", "name3@example.com"], assigns(:newsletter).additional_recipients.map { |recipient| recipient[:email] }
  135 + assert_equivalent ["Coop1", "Coop2", "Coop3"], assigns(:newsletter).additional_recipients.map { |recipient| recipient[:name] }
  136 + end
  137 +
  138 + should 'parse csv file with configuration set by user' do
  139 + content = <<-EOS
  140 +Id,Name,City,Email
  141 +1,Coop1,Moscow,name1@example.com
  142 +2,Coop2,Beijing,name2@example.com
  143 +3,Coop3,Paris,name3@example.com
  144 +EOS
  145 +
  146 + file = Tempfile.new(['recipients', '.csv'])
  147 + file.write(content)
  148 + file.rewind
  149 +
  150 + login_as @admin.identifier
  151 + post :index, newsletter: {}, :file => { recipients: Rack::Test::UploadedFile.new(file, 'text/csv'), headers: 1, name: 2, email: 4 }
  152 +
  153 + file.close
  154 + file.unlink
  155 +
  156 + assert_equivalent ["name1@example.com", "name2@example.com", "name3@example.com"], assigns(:newsletter).additional_recipients.map { |recipient| recipient[:email] }
  157 + assert_equivalent ["Coop1", "Coop2", "Coop3"], assigns(:newsletter).additional_recipients.map { |recipient| recipient[:name] }
  158 + end
  159 +
  160 + should 'list additional recipients' do
  161 + login_as @admin.identifier
  162 + get :recipients
  163 + assert_select 'p', 'There are no additional recipients.'
  164 +
  165 + newsletter = NewsletterPlugin::Newsletter.create!(environment: @environment, person: fast_create(Person))
  166 + newsletter.additional_recipients = [ {name: 'Coop1', email: 'name1@example.com'} ]
  167 + newsletter.save!
  168 +
  169 + get :recipients
  170 + assert_select 'tr' do
  171 + assert_select 'td', 'Coop1'
  172 + assert_select 'td', 'name1@example.com'
  173 + end
  174 + end
  175 +
  176 +end
... ...
plugins/newsletter/test/functional/newsletter_plugin_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +
  3 +class NewsletterPluginControllerTest < ActionController::TestCase
  4 +
  5 + def setup
  6 + @controller = NewsletterPluginController.new
  7 + @request = ActionController::TestRequest.new
  8 + @response = ActionController::TestResponse.new
  9 + environment = fast_create(Environment)
  10 + environment.enable_plugin(NewsletterPlugin)
  11 + @controller.stubs(:environment).returns(environment)
  12 + end
  13 +
  14 + should 'require login to confirm unsubscription' do
  15 + post :confirm_unsubscription
  16 + assert_response 302
  17 + end
  18 +
  19 + should 'open unsubscription page for anonymous' do
  20 + get :unsubscribe
  21 + assert_response :success
  22 + end
  23 +
  24 + should 'add user email from unsubscribers list' do
  25 + NewsletterPlugin::Newsletter.create!(
  26 + :environment => @controller.environment,
  27 + :person => fast_create(Person)
  28 + )
  29 + maria = create_user("maria").person
  30 + login_as("maria")
  31 + post :confirm_unsubscription
  32 + assert_response :redirect
  33 + assert_redirected_to :controller => 'home'
  34 + assert_includes assigns(:newsletter).unsubscribers, maria.email
  35 + end
  36 +
  37 +end
... ...
plugins/newsletter/test/unit/newsletter_plugin_moderate_newsletter_test.rb 0 → 100644
... ... @@ -0,0 +1,50 @@
  1 +require 'test_helper'
  2 +
  3 +class NewsletterPluginModerateNewsletterTest < ActiveSupport::TestCase
  4 +
  5 + should 'validates presence of newsletter_id' do
  6 + task = NewsletterPlugin::ModerateNewsletter.new
  7 + task.valid?
  8 + assert task.errors.include?(:newsletter_id)
  9 +
  10 + task.newsletter_id = 1
  11 + task.valid?
  12 + refute task.errors.include?(:newsletter_id)
  13 + end
  14 +
  15 + should 'create mailing on perform' do
  16 + person = create_user('john').person
  17 + newsletter = NewsletterPlugin::Newsletter.create!(:environment => fast_create(Environment), :person => person, :enabled => true)
  18 + task = NewsletterPlugin::ModerateNewsletter.create!(
  19 + :newsletter_id => newsletter.id,
  20 + :target => newsletter.environment
  21 + )
  22 +
  23 + assert_difference 'NewsletterPlugin::NewsletterMailing.count', 1 do
  24 + task.finish
  25 + end
  26 + end
  27 +
  28 + should 'set posts for mailing body on perform' do
  29 + person = create_user('john').person
  30 + blog = fast_create(Blog, profile_id: person.id)
  31 + post_1 = fast_create(TextileArticle, :name => 'First post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
  32 + post_2 = fast_create(TextileArticle, :name => 'Second post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
  33 + post_3 = fast_create(TextileArticle, :name => 'Third post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
  34 +
  35 + newsletter = NewsletterPlugin::Newsletter.create!(:environment => person.environment, :person => person, :enabled => true)
  36 + newsletter.blog_ids = [blog.id]
  37 + newsletter.save!
  38 +
  39 + task = NewsletterPlugin::ModerateNewsletter.create!(
  40 + :newsletter_id => newsletter.id,
  41 + :target => newsletter.environment,
  42 + :post_ids => [post_1.id.to_s,post_2.id.to_s]
  43 + )
  44 +
  45 + task.finish
  46 + assert_match /First post/, NewsletterPlugin::NewsletterMailing.last.body
  47 + assert_match /Second post/, NewsletterPlugin::NewsletterMailing.last.body
  48 + assert_not_match /Third post/, NewsletterPlugin::NewsletterMailing.last.body
  49 + end
  50 +end
... ...
plugins/newsletter/test/unit/newsletter_plugin_newsletter_mailing_test.rb 0 → 100644
... ... @@ -0,0 +1,72 @@
  1 +require 'test_helper'
  2 +
  3 +class NewsletterPluginNewsletterMailingTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + ActionMailer::Base.delivery_method = :test
  7 + ActionMailer::Base.perform_deliveries = true
  8 + ActionMailer::Base.deliveries = []
  9 + end
  10 +
  11 + should 'require source id' do
  12 + mailing = NewsletterPlugin::NewsletterMailing.new
  13 + mailing.valid?
  14 + assert mailing.errors[:source_id].any?
  15 +
  16 + mailing.source_id = NewsletterPlugin::Newsletter.create!(:environment => fast_create(Environment), :person => fast_create(Person)).id
  17 + mailing.valid?
  18 + refute mailing.errors[:source_id].any?
  19 + end
  20 +
  21 + should 'deliver mail from noreply environment email address' do
  22 + environment = fast_create(Environment, :noreply_email => 'noreply@localhost')
  23 + person = fast_create Person
  24 + newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => person, :enabled => true)
  25 + mailing = NewsletterPlugin::NewsletterMailing.create!(
  26 + :source => newsletter,
  27 + :subject => newsletter.subject,
  28 + :body => newsletter.body,
  29 + :person => newsletter.person,
  30 + :locale => environment.default_locale,
  31 + )
  32 + response = NewsletterPlugin::NewsletterMailing::Sender.notification(mailing, 'recipient@example.com').deliver
  33 + assert_equal 'noreply@localhost', response.from.join
  34 + end
  35 +
  36 + should 'also send to additional recipients' do
  37 + environment = fast_create(Environment, :name => 'Network')
  38 + person = create_user('betty', :environment_id => environment.id).person
  39 + newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => person)
  40 +
  41 + newsletter.additional_recipients = [{name: 'example', email: 'exemple@mail.co'}, {name: 'jon', email: 'jonsnow@mail.co'}]
  42 + newsletter.save!
  43 +
  44 + mailing = NewsletterPlugin::NewsletterMailing.create!(
  45 + :source => newsletter,
  46 + :subject => newsletter.subject,
  47 + :body => newsletter.body,
  48 + :person => newsletter.person,
  49 + :locale => newsletter.environment.default_locale,
  50 + )
  51 +
  52 + process_delayed_job_queue
  53 + assert_equal 3, ActionMailer::Base.deliveries.count
  54 + end
  55 +
  56 + should 'generate url to view mailing' do
  57 + newsletter = NewsletterPlugin::Newsletter.create!(
  58 + :environment => fast_create(Environment),
  59 + :person => fast_create(Person),
  60 + :enabled => true
  61 + )
  62 + mailing = NewsletterPlugin::NewsletterMailing.create!(
  63 + :source => newsletter,
  64 + :subject => newsletter.subject,
  65 + :body => newsletter.body,
  66 + :person => newsletter.person,
  67 + :locale => newsletter.environment.default_locale,
  68 + )
  69 + assert_equal "http://localhost/plugin/newsletter/mailing/#{mailing.id}", mailing.url
  70 + end
  71 +
  72 +end
... ...
plugins/newsletter/test/unit/newsletter_plugin_newsletter_test.rb 0 → 100644
... ... @@ -0,0 +1,414 @@
  1 +require 'test_helper'
  2 +
  3 +class NewsletterPluginNewsletterTest < ActiveSupport::TestCase
  4 +
  5 + should 'throws exception when try to create newsletters without reference do environment' do
  6 + assert_raises ActiveRecord::RecordInvalid do |e|
  7 + NewsletterPlugin::Newsletter.create!
  8 + assert_match /Profile can't be blank/, e.to_s
  9 + end
  10 + end
  11 +
  12 + should 'allow save only one newsletter by environment' do
  13 + environment = fast_create Environment
  14 + NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
  15 + assert_raises ActiveRecord::RecordInvalid do |e|
  16 + NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
  17 + assert_match /Profile has already been taken/, e.to_s
  18 + end
  19 + end
  20 +
  21 + should 'collect enabled newsletters' do
  22 + enabled_newsletters = []
  23 + 5.times do
  24 + environment = fast_create(Environment)
  25 + enabled = environment.id % 2 == 0
  26 + newsletter = NewsletterPlugin::Newsletter.create!(
  27 + :environment => environment,
  28 + :enabled => enabled,
  29 + :person => fast_create(Person))
  30 + enabled_newsletters << newsletter.id if enabled
  31 + end
  32 + assert_equal enabled_newsletters, NewsletterPlugin::Newsletter.enabled.map(&:id)
  33 + end
  34 +
  35 + should 'people of newsletters are the same environment members' do
  36 + 3.times do
  37 + environment = fast_create(Environment)
  38 + 3.times do
  39 + fast_create(Person, environment_id: environment)
  40 + end
  41 + NewsletterPlugin::Newsletter.create!(
  42 + :environment => environment,
  43 + :enabled => true,
  44 + :person => fast_create(Person))
  45 + end
  46 + NewsletterPlugin::Newsletter.enabled.each do |newsletter|
  47 + assert_not_equal [], newsletter.people
  48 + assert_equal newsletter.environment.people, newsletter.people
  49 + end
  50 + end
  51 +
  52 + should 'save period for newsletter' do
  53 + environment = fast_create Environment
  54 + NewsletterPlugin::Newsletter.create!(
  55 + :environment => environment,
  56 + :periodicity => '3',
  57 + :person => fast_create(Person))
  58 +
  59 + assert_equal 3, NewsletterPlugin::Newsletter.find_by_environment_id(environment.id).periodicity
  60 + end
  61 +
  62 + should 'save period as number only' do
  63 + environment = fast_create Environment
  64 + assert_raises ActiveRecord::RecordInvalid do |e|
  65 + NewsletterPlugin::Newsletter.create!(:environment => environment, :periodicity => 'one week' )
  66 + assert_match /Periodicity must be a positive number/, e.to_s
  67 + end
  68 + end
  69 +
  70 + should 'save period as a positive number only' do
  71 + environment = fast_create Environment
  72 + assert_raises ActiveRecord::RecordInvalid do |e|
  73 + NewsletterPlugin::Newsletter.create!(:environment => environment, :periodicity => -1 )
  74 + assert_match /Periodicity must be a positive number/, e.to_s
  75 + end
  76 + end
  77 +
  78 + should 'save reference to environment blog' do
  79 + environment = fast_create Environment
  80 + blog = fast_create(Blog)
  81 + blog.profile = fast_create(Profile, environment_id: environment.id)
  82 + blog.save
  83 + assert_nothing_raised ActiveRecord::RecordInvalid do
  84 + NewsletterPlugin::Newsletter.create!(
  85 + :environment => environment,
  86 + :blog_ids => [blog.id],
  87 + :person => fast_create(Person))
  88 + end
  89 + end
  90 +
  91 + should 'not save reference to unknown blog' do
  92 + environment = fast_create Environment
  93 + blog = fast_create(Blog)
  94 + blog.profile = fast_create(Profile, environment_id: fast_create(Environment).id)
  95 + blog.save
  96 + assert_raises ActiveRecord::RecordInvalid do |e|
  97 + NewsletterPlugin::Newsletter.create!(:environment => environment, :blog_ids => [blog.id])
  98 + assert_match /Blog ids must be valid/, e.to_s
  99 + end
  100 + assert_raises ActiveRecord::RecordInvalid do |e|
  101 + NewsletterPlugin::Newsletter.create!(:environment => environment, :blog_ids => [blog.id*2])
  102 + assert_match /Blog ids must be valid/, e.to_s
  103 + end
  104 + end
  105 +
  106 + should 'not save duplicates for blog ids' do
  107 + environment = fast_create Environment
  108 + blog = fast_create(Blog)
  109 + blog.profile = fast_create(Profile, environment_id: environment.id)
  110 + blog.save
  111 + assert_raises ActiveRecord::RecordInvalid do |e|
  112 + NewsletterPlugin::Newsletter.create!(:environment => environment, :blog_ids => [blog.id, blog.id])
  113 + assert_match /Blog ids must not have duplicates/, e.to_s
  114 + end
  115 + end
  116 +
  117 + should "not send newsletters if periodicity isn't expired" do
  118 + newsletter = NewsletterPlugin::Newsletter.new
  119 + newsletter.periodicity = 10
  120 + newsletter.stubs(:last_send_at).returns(DateTime.parse("2015-01-01"))
  121 + Date.stubs(:today).returns(Date.parse("2015-01-07"))
  122 + assert_equal false, newsletter.must_be_sent_today?
  123 + end
  124 +
  125 + should 'send newsletters when periodicity expires' do
  126 + newsletter = NewsletterPlugin::Newsletter.new
  127 + newsletter.periodicity = 10
  128 + newsletter.stubs(:last_send_at).returns(DateTime.parse("2015-01-01"))
  129 + Date.stubs(:today).returns(Date.parse("2015-01-15"))
  130 + assert_equal true, newsletter.must_be_sent_today?
  131 + end
  132 +
  133 + should 'send now if never send before' do
  134 + newsletter = NewsletterPlugin::Newsletter.new(:environment => fast_create(Environment))
  135 + newsletter.periodicity = 10
  136 + assert newsletter.must_be_sent_today?
  137 + end
  138 +
  139 + should 'validate email format for additional recipients' do
  140 + environment = fast_create Environment
  141 + assert_raises ActiveRecord::RecordInvalid do |e|
  142 + NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person), additional_recipients: [{name: 'Cooperative', email: 'cooperative@example'}])
  143 + assert_match /Additional recipients must have only valid emails/, e.to_s
  144 + end
  145 + assert_nothing_raised ActiveRecord::RecordInvalid do |e|
  146 + NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person), additional_recipients: [{name: 'Cooperative', email: 'cooperative@example.com'}])
  147 + end
  148 + end
  149 +
  150 + should 'parse additional recipients' do
  151 + content = <<-EOS
  152 +Coop1,name1@example.com
  153 +Coop2,name2@example.com
  154 +Coop3,name3@example.com
  155 +EOS
  156 +
  157 + file = Tempfile.new(['recipients', '.csv'])
  158 + file.write(content)
  159 + file.rewind
  160 +
  161 + environment = fast_create Environment
  162 + newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
  163 + newsletter.import_recipients(Rack::Test::UploadedFile.new(file, 'text/csv'))
  164 +
  165 + file.close
  166 + file.unlink
  167 +
  168 + assert_equivalent ["name1@example.com", "name2@example.com", "name3@example.com"], newsletter.additional_recipients.map { |recipient| recipient[:email] }
  169 + assert_equivalent ["Coop1", "Coop2", "Coop3"], newsletter.additional_recipients.map { |recipient| recipient[:name] }
  170 + end
  171 +
  172 + should 'only parse csv files' do
  173 + content = <<-EOS
  174 +Coop1,name1@example.com
  175 +Coop2,name2@example.com
  176 +Coop3,name3@example.com
  177 +EOS
  178 +
  179 + file = Tempfile.new(['recipients', '.txt'])
  180 + file.write(content)
  181 + file.rewind
  182 +
  183 + environment = fast_create Environment
  184 + newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
  185 + newsletter.import_recipients(Rack::Test::UploadedFile.new(file))
  186 +
  187 + file.close
  188 + file.unlink
  189 +
  190 + assert_equal [], newsletter.additional_recipients
  191 + assert_match /Additional recipients have unknown file type.*/, newsletter.errors.full_messages[0]
  192 + end
  193 +
  194 + should 'parse additional recipients with given column number and header' do
  195 + content = <<-EOS
  196 +Id,Name,City,Email
  197 +1,Coop1,Moscow,name1@example.com
  198 +2,Coop2,Beijing,name2@example.com
  199 +3,Coop3,Paris,name3@example.com
  200 +EOS
  201 +
  202 + file = Tempfile.new(['recipients', '.csv'])
  203 + file.write(content)
  204 + file.rewind
  205 +
  206 + environment = fast_create Environment
  207 + newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
  208 + newsletter.import_recipients(Rack::Test::UploadedFile.new(file, 'text/csv'), 2, 4, true)
  209 +
  210 + file.close
  211 + file.unlink
  212 +
  213 + assert_equivalent ["name1@example.com", "name2@example.com", "name3@example.com"], newsletter.additional_recipients.map { |recipient| recipient[:email] }
  214 + assert_equivalent ["Coop1", "Coop2", "Coop3"], newsletter.additional_recipients.map { |recipient| recipient[:name] }
  215 + end
  216 +
  217 + should 'retrieve blogs related to newsletter' do
  218 + environment = fast_create Environment
  219 + community = fast_create(Community, :environment_id => environment.id)
  220 + blog1 = fast_create(Blog, :profile_id => community.id)
  221 + blog2 = fast_create(Blog, :profile_id => community.id)
  222 + newsletter = NewsletterPlugin::Newsletter.create!(
  223 + :environment => environment, :blog_ids => [blog1.id, blog2.id], :person => fast_create(Person)
  224 + )
  225 + assert_equivalent [blog1, blog2], newsletter.blogs
  226 + end
  227 +
  228 + should 'return empty if has no related blogs' do
  229 + environment = fast_create Environment
  230 + newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
  231 + assert_empty newsletter.blogs
  232 + end
  233 +
  234 + should 'list posts for all selected blogs' do
  235 + environment = fast_create Environment
  236 + community = fast_create(Community, :environment_id => environment.id)
  237 + blog = fast_create(Blog, :profile_id => community.id)
  238 + post = fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news')
  239 + newsletter = NewsletterPlugin::Newsletter.create!(
  240 + :environment => environment,
  241 + :blog_ids => [blog.id],
  242 + :person => fast_create(Person))
  243 + assert_includes newsletter.posts, post
  244 + end
  245 +
  246 + should 'generate HTML content using posts of selected blogs' do
  247 + environment = fast_create Environment
  248 + community = fast_create(Community, :environment_id => environment.id)
  249 + blog = fast_create(Blog, :profile_id => community.id)
  250 + fast_create(TextArticle, :profile_id => community.id, :parent_id => blog.id, :name => 'the last news')
  251 + newsletter = NewsletterPlugin::Newsletter.create!(
  252 + :environment => environment,
  253 + :blog_ids => [blog.id],
  254 + :person => fast_create(Person))
  255 + assert_tag_in_string newsletter.body, :tag => 'a', :content => 'the last news'
  256 + end
  257 +
  258 + should 'limit the number of posts per blog' do
  259 + environment = fast_create Environment
  260 + community = fast_create(Community, :environment_id => environment.id)
  261 + blog = fast_create(Blog, :profile_id => community.id)
  262 + fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 1')
  263 + fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 2')
  264 + fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 3')
  265 + newsletter = NewsletterPlugin::Newsletter.create!(
  266 + :environment => environment,
  267 + :blog_ids => [blog.id],
  268 + :person => fast_create(Person),
  269 + :posts_per_blog => 2)
  270 + assert_equal 2, newsletter.posts.count
  271 + end
  272 +
  273 + should 'include all posts before today' do
  274 + environment = fast_create Environment
  275 + community = fast_create(Community, :environment_id => environment.id)
  276 + blog = fast_create(Blog, :profile_id => community.id)
  277 +
  278 + post1 = fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 1',
  279 + :published_at => DateTime.parse("2015-01-01"))
  280 + post2 = fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 2',
  281 + :published_at => DateTime.parse("2015-01-09"))
  282 +
  283 + Date.stubs(:today).returns(DateTime.parse("2015-01-10").to_date)
  284 +
  285 + newsletter = NewsletterPlugin::Newsletter.create!(
  286 + :environment => environment,
  287 + :blog_ids => [blog.id],
  288 + :person => fast_create(Person))
  289 +
  290 + newsletter_posts = newsletter.posts
  291 + assert_includes newsletter_posts, post1
  292 + assert_includes newsletter_posts, post2
  293 + end
  294 +
  295 + should 'not include posts already sent' do
  296 + environment = fast_create Environment
  297 + community = fast_create(Community, :environment_id => environment.id)
  298 + blog = fast_create(Blog, :profile_id => community.id)
  299 +
  300 + post1 = fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 1',
  301 + :published_at => DateTime.parse("2015-01-01"))
  302 + post2 = fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 2',
  303 + :published_at => DateTime.parse("2015-01-09"))
  304 +
  305 + Date.stubs(:today).returns(DateTime.parse("2015-01-10").to_date)
  306 +
  307 + newsletter = NewsletterPlugin::Newsletter.create!(
  308 + :environment => environment,
  309 + :blog_ids => [blog.id],
  310 + :person => fast_create(Person))
  311 + newsletter.stubs(:last_send_at).returns(DateTime.parse("2015-01-05"))
  312 +
  313 + newsletter_posts = newsletter.posts
  314 + assert_not_includes newsletter_posts, post1
  315 + assert_includes newsletter_posts, post2
  316 + end
  317 +
  318 + should 'sanitize tags <p> from news lead' do
  319 + environment = fast_create Environment
  320 + community = fast_create(Community, :environment_id => environment.id)
  321 + blog = fast_create(Blog, :profile_id => community.id)
  322 + post = fast_create(TextArticle, :parent_id => blog.id,
  323 + :name => 'the last news 1',
  324 + :profile_id => community.id,
  325 + :body => "<p>paragraph of news</p>")
  326 +
  327 + newsletter = NewsletterPlugin::Newsletter.create!(
  328 + :environment => environment,
  329 + :blog_ids => [blog.id],
  330 + :person => fast_create(Person))
  331 +
  332 + assert_match /<p>paragraph of news<\/p>/, post.body
  333 + assert_not_match /<p>paragraph of news<\/p>/, newsletter.body
  334 + end
  335 +
  336 + should 'filter posts when listing posts for newsletter' do
  337 + person = fast_create(Person)
  338 + blog = fast_create(Blog, profile_id: person.id)
  339 +
  340 + post_1 = fast_create(TextileArticle, :name => 'First post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
  341 + post_2 = fast_create(TextileArticle, :name => 'Second post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
  342 + post_3 = fast_create(TextileArticle, :name => 'Third post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
  343 +
  344 + newsletter = NewsletterPlugin::Newsletter.create!(
  345 + :environment => person.environment,
  346 + :blog_ids => [blog.id],
  347 + :person => person)
  348 +
  349 + assert_equivalent [post_2.id, post_3.id], newsletter.posts({post_ids: [post_2.id.to_s, post_3.id.to_s]}).map(&:id)
  350 + end
  351 +
  352 + should 'filter posts in body for newsletter' do
  353 + person = fast_create(Person)
  354 + blog = fast_create(Blog, profile_id: person.id)
  355 +
  356 + post_1 = fast_create(TextileArticle, :name => 'First post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
  357 + post_2 = fast_create(TextileArticle, :name => 'Second post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
  358 + post_3 = fast_create(TextileArticle, :name => 'Third post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
  359 +
  360 + newsletter = NewsletterPlugin::Newsletter.create!(
  361 + :environment => person.environment,
  362 + :blog_ids => [blog.id],
  363 + :person => person)
  364 +
  365 + assert_match /First post/, NewsletterPlugin::Newsletter.last.body({post_ids: [post_1.id.to_s, post_3.id.to_s]})
  366 + assert_not_match /Second post/, NewsletterPlugin::Newsletter.last.body({post_ids: [post_1.id.to_s, post_3.id.to_s]})
  367 + assert_match /Third post/, NewsletterPlugin::Newsletter.last.body({post_ids: [post_1.id.to_s, post_3.id.to_s]})
  368 + end
  369 +
  370 + should 'add email to unsubscribers list' do
  371 + newsletter = NewsletterPlugin::Newsletter.create!(
  372 + :environment => fast_create(Environment),
  373 + :person => fast_create(Person)
  374 + )
  375 + newsletter.unsubscribe("ze@localhost.localdomain")
  376 + assert_includes newsletter.unsubscribers, "ze@localhost.localdomain"
  377 + end
  378 +
  379 + should 'not add same email twice to unsubscribers list' do
  380 + newsletter = NewsletterPlugin::Newsletter.create!(
  381 + :environment => fast_create(Environment),
  382 + :person => fast_create(Person)
  383 + )
  384 + newsletter.unsubscribe("ze@localhost.localdomain")
  385 + newsletter.unsubscribe("ze@localhost.localdomain")
  386 + assert_equal ["ze@localhost.localdomain"], newsletter.unsubscribers
  387 + end
  388 +
  389 + should "filter newsletter's recipients using unsubscribers list" do
  390 + environment = fast_create Environment
  391 + p1 = create_user("person1", :environment_id => environment.id).person
  392 + p2 = create_user("person2", :environment_id => environment.id).person
  393 + p3 = create_user("person3", :environment_id => environment.id).person
  394 + newsletter = NewsletterPlugin::Newsletter.create!(
  395 + :environment => environment,
  396 + :person => fast_create(Person)
  397 + )
  398 + newsletter.unsubscribe(p2.email)
  399 + assert_equivalent [p1, p3], newsletter.people
  400 + end
  401 +
  402 + should "no filter newsletter's recipients if unsubscribers list empty" do
  403 + environment = fast_create Environment
  404 + p1 = create_user("person1", :environment_id => environment.id).person
  405 + p2 = create_user("person2", :environment_id => environment.id).person
  406 + p3 = create_user("person3", :environment_id => environment.id).person
  407 + newsletter = NewsletterPlugin::Newsletter.create!(
  408 + :environment => environment,
  409 + :person => fast_create(Person)
  410 + )
  411 + assert_equivalent [p1, p2, p3], newsletter.people
  412 + end
  413 +
  414 +end
... ...
plugins/newsletter/test/unit/newsletter_plugin_test.rb 0 → 100644
... ... @@ -0,0 +1,113 @@
  1 +require 'test_helper'
  2 +
  3 +class NewsletterPluginTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + NewsletterPlugin::Newsletter.any_instance.stubs(:must_be_sent_today?).returns(true)
  7 + NewsletterPlugin::Newsletter.any_instance.stubs(:has_posts_in_the_period?).returns(true)
  8 + end
  9 +
  10 + should 'update newsletter send date only for enabled newsletters' do
  11 + newsletter_enabled = NewsletterPlugin::Newsletter.create!(
  12 + :environment => fast_create(Environment),
  13 + :enabled => true,
  14 + :subject => 'newsletter test',
  15 + :person => fast_create(Person))
  16 +
  17 + newsletter_disabled = NewsletterPlugin::Newsletter.create!(
  18 + :environment => fast_create(Environment),
  19 + :enabled => false,
  20 + :subject => 'newsletter test',
  21 + :person => fast_create(Person))
  22 +
  23 + NewsletterPlugin.compile_and_send_newsletters
  24 +
  25 + newsletter_enabled.reload
  26 + newsletter_disabled.reload
  27 +
  28 + assert_not_nil newsletter_enabled.last_send_at
  29 + assert_nil newsletter_disabled.last_send_at
  30 + end
  31 +
  32 + should 'create and schedule newsletter mailing if not moderated' do
  33 + NewsletterPlugin::Newsletter.create!(
  34 + :environment => fast_create(Environment),
  35 + :enabled => true,
  36 + :moderated => false,
  37 + :subject => 'newsletter test',
  38 + :person => fast_create(Person))
  39 +
  40 + assert_difference 'NewsletterPlugin::NewsletterMailing.count', 1 do
  41 + NewsletterPlugin.compile_and_send_newsletters
  42 + end
  43 +
  44 + assert_equal 0, NewsletterPlugin::ModerateNewsletter.count
  45 + end
  46 +
  47 + should 'use same environment locale on mailing' do
  48 + NewsletterPlugin::Newsletter.create!(
  49 + :environment => fast_create(Environment, :default_language => 'pt_BR'),
  50 + :enabled => true,
  51 + :subject => 'newsletter test',
  52 + :person => fast_create(Person))
  53 +
  54 + NewsletterPlugin.compile_and_send_newsletters
  55 + assert_equal 'pt_BR', NewsletterPlugin::NewsletterMailing.last.locale
  56 + end
  57 +
  58 + should 'create newsletter moderation task if newsletter is moderated' do
  59 + adminuser = create_user.person
  60 + Environment.any_instance.stubs(:admins).returns([adminuser])
  61 +
  62 + NewsletterPlugin::Newsletter.create!(
  63 + :environment => fast_create(Environment),
  64 + :enabled => true,
  65 + :moderated => true,
  66 + :subject => 'newsletter test',
  67 + :person => fast_create(Person))
  68 +
  69 + assert_difference 'NewsletterPlugin::ModerateNewsletter.count', 1 do
  70 + NewsletterPlugin.compile_and_send_newsletters
  71 + end
  72 +
  73 + assert_equal 0, NewsletterPlugin::NewsletterMailing.count
  74 + end
  75 +
  76 + should 'not create mailing if has no posts in the period' do
  77 + newsletter = NewsletterPlugin::Newsletter.create!(
  78 + :environment => fast_create(Environment),
  79 + :person => fast_create(Person),
  80 + :enabled => true
  81 + )
  82 + NewsletterPlugin::Newsletter.any_instance.stubs(:must_be_sent_today?).returns(true)
  83 + NewsletterPlugin::Newsletter.any_instance.stubs(:has_posts_in_the_period?).returns(false)
  84 + assert_no_difference 'NewsletterPlugin::NewsletterMailing.count' do
  85 + NewsletterPlugin.compile_and_send_newsletters
  86 + end
  87 + end
  88 +
  89 + should 'not create mailing if doesnt must be sent today' do
  90 + newsletter = NewsletterPlugin::Newsletter.create!(
  91 + :environment => fast_create(Environment),
  92 + :person => fast_create(Person),
  93 + :enabled => true
  94 + )
  95 + NewsletterPlugin::Newsletter.any_instance.stubs(:must_be_sent_today?).returns(false)
  96 + NewsletterPlugin::Newsletter.any_instance.stubs(:has_posts_in_the_period?).returns(true)
  97 + assert_no_difference 'NewsletterPlugin::NewsletterMailing.count' do
  98 + NewsletterPlugin.compile_and_send_newsletters
  99 + end
  100 + end
  101 +
  102 + should 'create mailing' do
  103 + newsletter = NewsletterPlugin::Newsletter.create!(
  104 + :environment => fast_create(Environment),
  105 + :person => fast_create(Person),
  106 + :enabled => true
  107 + )
  108 + assert_difference 'NewsletterPlugin::NewsletterMailing.count' do
  109 + NewsletterPlugin.compile_and_send_newsletters
  110 + end
  111 + end
  112 +
  113 +end
... ...
plugins/newsletter/views/newsletter_plugin/mailing_not_found.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<h2><%= _("Mailing not found") %></h2>
  2 +
  3 +<p><%= _("There is no mailing with id #%s") % params[:id] %></p>
... ...
plugins/newsletter/views/newsletter_plugin/unsubscribe.html.erb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +<h1><%= _('Cancel newsletter subscription') %></h1>
  2 +
  3 +<h4>
  4 +<%= _("I don't want to receive future newsletter emails from this network.") %>
  5 +</h4>
  6 +
  7 +<%= _('Send an email to %s requesting your unsubscription or click on the button below.') % link_to(environment.contact_email, "mailto:#{environment.contact_email}?subject=#{_('Cancel newsletter subscription')}") %>
  8 +
  9 +<% button_bar do %>
  10 + <%= button :ok, _('Confirm unsubscription'), {:action => 'confirm_unsubscription'}, :method => 'post' %>
  11 +<% end %>
... ...
plugins/newsletter/views/newsletter_plugin_admin/index.html.erb 0 → 100644
... ... @@ -0,0 +1,92 @@
  1 +<h1><%= _('Newsletter settings') %></h1>
  2 +
  3 +<%= render :file => 'shared/tiny_mce' %>
  4 +
  5 +<%= error_messages_for :newsletter %>
  6 +
  7 +<%= form_for(:newsletter, html: { multipart: true }) do |f| %>
  8 + <%= labelled_form_field(
  9 + content_tag('h3', hidden_field_tag('newsletter[enabled]', false) +
  10 + f.check_box('enabled') +
  11 + _('Enable send of newsletter to members on this environment'), :id => 'newsletter-enabled-field'),
  12 + nil)
  13 + %>
  14 +
  15 + <%= labelled_form_field(
  16 + content_tag('span', hidden_field_tag('newsletter[moderated]', false) + f.check_box('moderated') + _('Moderate newsletter each time before sending to users.')), nil)
  17 + %>
  18 +
  19 + <h2>
  20 + <%= _('Content') %>
  21 + </h2>
  22 +
  23 + <%= labelled_form_field(
  24 + _('Period (in days) for news compilation'), f.number_field(:periodicity, min: '0'))
  25 + %>
  26 +
  27 + <%= labelled_form_field(
  28 + _('Number of posts compiled per blog (choose 0 for all posts since last newsletter)'), f.number_field(:posts_per_blog, min: '0'))
  29 + %>
  30 +
  31 + <p><%= _('Blogs from which news will be compiled') %></p>
  32 + <% search_action = url_for(:action => 'search_communities') %>
  33 + <% selected_blogs = @blogs.map { |blog| {:id => blog.id, :name => _("%s in %s") % [blog.name, blog.profile.name]} } %>
  34 + <%= token_input_field_tag(
  35 + 'newsletter[blog_ids]', 'search-communities', search_action,
  36 + { hint_text: _('Type in the communities\' or blogs\' names'),
  37 + focus: false, pre_populate: selected_blogs }) %>
  38 +
  39 + <br/>
  40 +
  41 + <h2>
  42 + <%= _('Recipients') %>
  43 + </h2>
  44 +
  45 + <p>
  46 + <%= _('You can follow the link below to see which e-mails are currently being used as additional recipients for this newsletter.') %>
  47 + </p>
  48 + <p>
  49 + <%= link_to 'Currently set additional recipients', {action: :recipients}, target: '_blank' %>
  50 + </p>
  51 +
  52 + <p><%= _('You can set additional e-mails to send the newsletter to in addition to all environment\'s users that already receive the newsletter by default. To do that, you need to upload a CSV file that contains a column for the person\'s or enterprise\'s name as well as a column with their e-mail.') %></p>
  53 +
  54 + <%= labelled_form_field(
  55 + _('Additional recipients for newsletter'), file_field_tag('file[recipients]', accept: '.csv'))
  56 + %>
  57 +
  58 + <div id='newsletter-file-options'>
  59 + <%= labelled_form_field(
  60 + content_tag('span', check_box_tag('file[headers]', 1, false, disabled: true) + _('The CSV file contains a header row')), nil)
  61 + %>
  62 +
  63 + <%= labelled_form_field(
  64 + _('Number of colunm with name field'), number_field_tag('file[name]', '1', min: '1', disabled: true))
  65 + %>
  66 +
  67 + <%= labelled_form_field(
  68 + _('Number of colunm with email field'), number_field_tag('file[email]', '2', min: '1', disabled: true))
  69 + %>
  70 + </div>
  71 +
  72 + <h2>
  73 + <%= _('Layout') %>
  74 + </h2>
  75 +
  76 + <%= f.fields_for :image_builder, @newsletter.image do |i| %>
  77 + <%= file_field_or_thumbnail(_('Header image (images with 640px width):'), @newsletter.image, i) %>
  78 + <% end %>
  79 +
  80 + <%= labelled_form_field(
  81 + content_tag('h3', ui_icon('ui-icon-triangle-1-s') +
  82 + _('Newsletter footer'), :class => 'newsletter-toggle-link', :element_id => '#newsletter-footer-field'),
  83 + content_tag('div',
  84 + f.text_area(:footer, :style => 'width: 100%', :class => 'mceEditor'),
  85 + :id => 'newsletter-footer-field'
  86 + ))
  87 + %>
  88 + <% button_bar do %>
  89 + <%= submit_button :save, _('Save') %>
  90 + <%= submit_button :save, _('Save and visualize'), :name => "visualize", :cancel => {:controller => 'plugins'} %>
  91 + <% end %>
  92 +<% end %>
... ...
plugins/newsletter/views/newsletter_plugin_admin/recipients.html.erb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +<h1><%= _('Additional recipients') %></h1>
  2 +
  3 +<% if @additional_recipients.present? %>
  4 + <table border="0" cellspacing="5" cellpadding="5">
  5 + <tr>
  6 + <th><%= _('Name') %></th>
  7 + <th><%= _('E-mail') %></th>
  8 + </tr>
  9 + <% @additional_recipients.each do |recipient| %>
  10 + <tr>
  11 + <td><%= recipient[:name] %></td>
  12 + <td><%= recipient[:email] %></td>
  13 + </tr>
  14 + <% end %>
  15 + </table>
  16 +<% else %>
  17 + <p><%= _('There are no additional recipients.') %></p>
  18 +<% end %>
  19 +
  20 +<br/>
  21 +<%= button :back, _('Back'), action: :index %>
... ...
plugins/newsletter/views/tasks/newsletter_plugin/_moderate_newsletter_accept_details.html.erb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +<% newsletter = NewsletterPlugin::Newsletter.find(task.newsletter_id) %>
  2 +
  3 +<h1><%= _('Check posts you want to include') %></h1>
  4 +
  5 +<div id='newsletter-moderation-preview'>
  6 + <% newsletter_content = newsletter.body.gsub(/width: 640px;/,'').sub(/#{NewsletterPlugin::Newsletter::CSS['breaking-news-wrap']}/, '') %>
  7 +
  8 + <% newsletter.posts.each do |post| %>
  9 + <% input_name = "tasks[#{task.id}][task][post_ids][]" %>
  10 + <% post_check_box = hidden_field_tag(input_name, '0') +check_box_tag(input_name, post.id, true) %>
  11 +
  12 + <% newsletter_content.gsub!(/<span id="#{post.id}"/, post_check_box+ '<span') %>
  13 + <% newsletter_content.gsub!(/<img id="#{post.id}"/, post_check_box+ '<img') %>
  14 + <% end %>
  15 +
  16 + <%= newsletter_content %>
  17 +</div>
... ...