[Git][noosfero/noosfero][master] 3 commits: improvements in core required for the newsletter plugin
Antonio Terceiro
gitlab at gitlab.com
Fri Sep 11 17:06:00 BRT 2015
Antonio Terceiro pushed to branch master at Noosfero / noosfero
Commits:
296a549d by Larissa Reis at 2015-09-11T16:51:52Z
improvements in core required for the newsletter plugin
* models should use acts_as_having_image transparently
* force margin zero in HTML mailing
* using require_dependency instead of require to avoid error:
"superclass mismatch for class MailingJob"
* now plugins can schedule jobs via whenever rubygem
* improve 'sample-data' scripts to create some blog posts
signed-off-by: Joenio Costa <joenio at colivre.coop.br>
- - - - -
4df54eb8 by Joenio Costa at 2015-09-11T17:01:26Z
new plugin: newsletter
a plugin that periodically sends newsletter via email to network users
signed-off-by: Larissa Reis <larissa at colivre.coop.br>
signed-off-by: Melissa Wen <melissa at colivre.coop.br>
- - - - -
1d587511 by Antonio Terceiro at 2015-09-11T17:05:26Z
Merge branch 'newsletter-plugin' of https://gitlab.com/joenio/noosfero
- - - - -
28 changed files:
- app/mailers/mailing.rb
- app/models/textile_article.rb
- app/views/mailing/sender/notification.html.erb
- config/schedule.rb
- lib/acts_as_having_image.rb
- + plugins/newsletter/config/schedule.rb
- + plugins/newsletter/controllers/newsletter_plugin_admin_controller.rb
- + plugins/newsletter/controllers/newsletter_plugin_controller.rb
- + plugins/newsletter/db/migrate/20150717195546_newsletter_plugin_newsletters.rb
- + plugins/newsletter/features/newsletter_plugin.feature
- + plugins/newsletter/lib/newsletter_plugin.rb
- + plugins/newsletter/lib/newsletter_plugin/moderate_newsletter.rb
- + plugins/newsletter/lib/newsletter_plugin/newsletter.rb
- + plugins/newsletter/lib/newsletter_plugin/newsletter_mailing.rb
- + plugins/newsletter/public/newsletter_plugin.js
- + plugins/newsletter/public/style.css
- + plugins/newsletter/test/functional/newsletter_plugin_admin_controller_test.rb
- + plugins/newsletter/test/functional/newsletter_plugin_controller_test.rb
- + plugins/newsletter/test/unit/newsletter_plugin_moderate_newsletter_test.rb
- + plugins/newsletter/test/unit/newsletter_plugin_newsletter_mailing_test.rb
- + plugins/newsletter/test/unit/newsletter_plugin_newsletter_test.rb
- + plugins/newsletter/test/unit/newsletter_plugin_test.rb
- + plugins/newsletter/views/newsletter_plugin/mailing_not_found.html.erb
- + plugins/newsletter/views/newsletter_plugin/unsubscribe.html.erb
- + plugins/newsletter/views/newsletter_plugin_admin/index.html.erb
- + plugins/newsletter/views/newsletter_plugin_admin/recipients.html.erb
- + plugins/newsletter/views/tasks/newsletter_plugin/_moderate_newsletter_accept_details.html.erb
- script/sample-articles
Changes:
=====================================
app/mailers/mailing.rb
=====================================
--- a/app/mailers/mailing.rb
+++ b/app/mailers/mailing.rb
@@ -1,4 +1,4 @@
-require 'mailing_job'
+require_dependency 'mailing_job'
class Mailing < ActiveRecord::Base
@@ -40,10 +40,8 @@ class Mailing < ActiveRecord::Base
begin
Mailing::Sender.notification(self, recipient.email).deliver
self.mailing_sents.create(:person => recipient)
- rescue Exception
- # FIXME should not discard errors silently. An idea is to collect all
- # errors and generate a task (notification) for the +source+
- # (environment/organization) listing these errors.
+ rescue Exception => ex
+ Rails.logger.error("#{ex.class.to_s} - #{ex.to_s} at #{__FILE__}:#{__LINE__}")
end
end
end
=====================================
app/models/textile_article.rb
=====================================
--- a/app/models/textile_article.rb
+++ b/app/models/textile_article.rb
@@ -12,7 +12,7 @@ class TextileArticle < TextArticle
convert_to_html(body)
end
- def lead
+ def lead(length = nil)
if abstract.blank?
super
else
=====================================
app/views/mailing/sender/notification.html.erb
=====================================
--- a/app/views/mailing/sender/notification.html.erb
+++ b/app/views/mailing/sender/notification.html.erb
@@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
- <body>
+ <body style="margin: 0">
<%= word_wrap(@message) %>
<p>
--<br/>
=====================================
config/schedule.rb
=====================================
--- a/config/schedule.rb
+++ b/config/schedule.rb
@@ -28,3 +28,15 @@ end
every 30.days do
runner "ProfileSuggestion.generate_all_profile_suggestions"
end
+
+# Loads "schedule.rb" files from plugins
+#
+# Allows Noosfero's plugins schedule jobs using `whenever` Ruby gem the same
+# way we do here, just create the file "config/schedule.rb" into the plugin
+# root directory and write jobs using the same syntax used here (see example in
+# the `newsletter` plugin)
+
+Dir.glob("config/plugins/*/config/schedule.rb").each do |filename|
+ filecontent = IO.read(filename)
+ instance_eval(Whenever::NumericSeconds.process_string(filecontent), filename)
+end
=====================================
lib/acts_as_having_image.rb
=====================================
--- a/lib/acts_as_having_image.rb
+++ b/lib/acts_as_having_image.rb
@@ -5,6 +5,7 @@ module ActsAsHavingImage
belongs_to :image, dependent: :destroy
scope :with_image, :conditions => [ "#{table_name}.image_id IS NOT NULL" ]
scope :without_image, :conditions => [ "#{table_name}.image_id IS NULL" ]
+ attr_accessible :image_builder
self.send(:include, ActsAsHavingImage)
end
end
@@ -19,4 +20,4 @@ module ActsAsHavingImage
end
-ActiveRecord::Base.extend(ActsAsHavingImage::ClassMethods)
\ No newline at end of file
+ActiveRecord::Base.extend(ActsAsHavingImage::ClassMethods)
=====================================
plugins/newsletter/config/schedule.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/config/schedule.rb
@@ -0,0 +1,3 @@
+every 1.day do
+ runner "NewsletterPlugin.compile_and_send_newsletters"
+end
=====================================
plugins/newsletter/controllers/newsletter_plugin_admin_controller.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/controllers/newsletter_plugin_admin_controller.rb
@@ -0,0 +1,48 @@
+class NewsletterPluginAdminController < PluginAdminController
+
+ def index
+ @newsletter = NewsletterPlugin::Newsletter.where(environment_id: environment.id).first_or_initialize
+
+ if request.post?
+ # token input gives the param as a comma separated string
+ params[:newsletter][:blog_ids] = (params[:newsletter][:blog_ids] || '').split(',')
+
+ params[:newsletter][:person_id] = user.id
+
+ file = params[:file]
+ if file && file[:recipients].present?
+ @newsletter.import_recipients(file[:recipients], file[:name], file[:email], file[:headers].present?)
+ end
+
+ if !@newsletter.errors.any? && @newsletter.update_attributes(params[:newsletter])
+ if params['visualize']
+ @message = @newsletter.body
+ render :file => 'mailing/sender/notification', :layout => false
+ else
+ session[:notice] = _('Newsletter updated.')
+ end
+ else
+ session[:notice] = _('Newsletter could not be saved.')
+ end
+ end
+
+ @blogs = Blog.includes(:profile).find_all_by_id(@newsletter.blog_ids)
+ end
+
+ #TODO: Make this query faster
+ def search_communities
+ communities = environment.communities
+ blogs = Blog.joins(:profile).where(profiles: {environment_id: environment.id})
+
+ found_communities = find_by_contents(:communities, environment, communities, params['q'], {:page => 1})[:results]
+ found_blogs = find_by_contents(:blogs, environment, blogs, params['q'], {:page => 1})[:results]
+
+ results = (found_blogs + found_communities.map(&:blogs).flatten).uniq
+ render :text => results.map { |blog| {:id => blog.id, :name => _("%s in %s") % [blog.name, blog.profile.name]} }.to_json
+ end
+
+ def recipients
+ @additional_recipients = NewsletterPlugin::Newsletter.where(environment_id: environment.id).first_or_initialize.additional_recipients
+ end
+
+end
=====================================
plugins/newsletter/controllers/newsletter_plugin_controller.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/controllers/newsletter_plugin_controller.rb
@@ -0,0 +1,24 @@
+class NewsletterPluginController < PublicController
+
+ before_filter :login_required, :only => :confirm_unsubscription
+
+ def mailing
+ if NewsletterPlugin::NewsletterMailing.exists?(params[:id])
+ mailing = NewsletterPlugin::NewsletterMailing.find(params[:id])
+ @message = mailing.body
+ render :file => 'mailing/sender/notification', :layout => false
+ else
+ render :action => 'mailing_not_found'
+ end
+ end
+
+ def confirm_unsubscription
+ if request.post?
+ session[:notice] = _('You was unsubscribed from newsletter.')
+ @newsletter = NewsletterPlugin::Newsletter.where(environment_id: environment.id).first
+ @newsletter.unsubscribe(current_user.email)
+ redirect_to :controller => :home
+ end
+ end
+
+end
=====================================
plugins/newsletter/db/migrate/20150717195546_newsletter_plugin_newsletters.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/db/migrate/20150717195546_newsletter_plugin_newsletters.rb
@@ -0,0 +1,24 @@
+class NewsletterPluginNewsletters < ActiveRecord::Migration
+ def up
+ create_table :newsletter_plugin_newsletters do |t|
+ t.references :environment, :null => false
+ t.references :person, :null => false
+ t.boolean :enabled, :default => false
+ t.string :subject
+ t.integer :periodicity, :default => 0
+ t.integer :posts_per_blog, :default => 0
+ t.integer :image_id
+ t.text :footer
+ t.text :blog_ids
+ t.text :additional_recipients
+ t.boolean :moderated
+ t.text :unsubscribers
+ end
+ add_index :newsletter_plugin_newsletters, :environment_id, :uniq => true
+ end
+
+ def down
+ remove_index :newsletter_plugin_newsletters, :environment_id
+ drop_table :newsletter_plugin_newsletters
+ end
+end
=====================================
plugins/newsletter/features/newsletter_plugin.feature
=====================================
--- /dev/null
+++ b/plugins/newsletter/features/newsletter_plugin.feature
@@ -0,0 +1,40 @@
+Feature: newsletter plugin
+
+ Background:
+ Given the following users
+ | login | name |
+ | joaosilva | Joao Silva |
+ And I am logged in as "joaosilva"
+
+ Scenario: as admin I can configure plugin
+ Given I am logged in as admin
+ When I go to the environment control panel
+ And I follow "Plugins"
+ Then I should see "Configuration" linking to "/admin/plugin/newsletter"
+
+ Scenario: in the newsletter settings I can see the field to enable/disable
+ Given I am logged in as admin
+ When I go to the environment control panel
+ And I follow "Plugins"
+ And I follow "Configuration"
+ Then I should see "Enable send of newsletter to members on this environment"
+
+ Scenario: redirect to newsletter visualization after save and visualize
+ Given I am logged in as admin
+ And "NewsletterPlugin" plugin is enabled
+ When I go to the environment control panel
+ And I follow "Plugins"
+ And I follow "Configuration"
+ And I press "Save and visualize"
+ Then I should see "If you can't view this email, click here"
+ And I should not see "Newsletter settings"
+
+ Scenario: stay on newsletter settings page after save
+ Given I am logged in as admin
+ And "NewsletterPlugin" plugin is enabled
+ When I go to the environment control panel
+ And I follow "Plugins"
+ And I follow "Configuration"
+ And I press "Save"
+ Then I should see "Newsletter settings"
+ And I should not see "If you can't view this email, click here"
=====================================
plugins/newsletter/lib/newsletter_plugin.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/lib/newsletter_plugin.rb
@@ -0,0 +1,41 @@
+class NewsletterPlugin < Noosfero::Plugin
+
+ def self.plugin_name
+ "Newsletter"
+ end
+
+ def self.plugin_description
+ _("Periodically sends newsletter via email to network users")
+ end
+
+ def js_files
+ 'newsletter_plugin.js'
+ end
+
+ def stylesheet?
+ true
+ end
+
+ def self.compile_and_send_newsletters
+ NewsletterPlugin::Newsletter.enabled.each do |newsletter|
+ if newsletter.must_be_sent_today? && newsletter.has_posts_in_the_period?
+ if newsletter.moderated
+ NewsletterPlugin::ModerateNewsletter.create!(
+ :newsletter_id => newsletter.id,
+ :environment => newsletter.environment
+ )
+ else
+ mailing = NewsletterPlugin::NewsletterMailing.create!(
+ :source => newsletter,
+ :subject => newsletter.subject,
+ :body => newsletter.body,
+ :person => newsletter.person,
+ :locale => newsletter.environment.default_locale,
+ )
+ mailing.update_attribute(:body, mailing.body.gsub('{mailing_url}', mailing.url))
+ end
+ end
+ end
+ end
+
+end
=====================================
plugins/newsletter/lib/newsletter_plugin/moderate_newsletter.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/lib/newsletter_plugin/moderate_newsletter.rb
@@ -0,0 +1,53 @@
+class NewsletterPlugin::ModerateNewsletter < Task
+
+ settings_items :newsletter_id, :post_ids
+ validates_presence_of :newsletter_id
+
+ alias :environment :target
+ alias :environment= :target=
+
+ def perform
+ newsletter = NewsletterPlugin::Newsletter.find(newsletter_id)
+ self.post_ids ||= []
+ mailing = NewsletterPlugin::NewsletterMailing.create!(
+ :source => newsletter,
+ :subject => newsletter.subject,
+ :body => newsletter.body(:post_ids => self.post_ids.reject{|id| id.to_i.zero?}),
+ :person => newsletter.person,
+ :locale => newsletter.environment.default_locale,
+ )
+ mailing.update_attribute(:body, mailing.body.gsub('{mailing_url}', mailing.url))
+ end
+
+ def title
+ _("Moderate newsletter")
+ end
+
+ def subject
+ nil
+ end
+
+ def linked_subject
+ nil
+ end
+
+ def information
+ {:message => _('You have to moderate a newsletter.') }
+ end
+
+ def accept_details
+ true
+ end
+
+ def icon
+ {:type => :defined_image, :src => "/images/control-panel/email.png", :name => 'Newsletter'}
+ end
+
+ def target_notification_message
+ _('A newsletter was generated and you need to review it before it is sent to users.')
+ end
+
+ def target_notification_description
+ _('You need to moderate a newsletter.')
+ end
+end
=====================================
plugins/newsletter/lib/newsletter_plugin/newsletter.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/lib/newsletter_plugin/newsletter.rb
@@ -0,0 +1,191 @@
+require 'csv'
+
+class NewsletterPlugin::Newsletter < Noosfero::Plugin::ActiveRecord
+
+ 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, :conditions => { :enabled => true }
+
+ # These methods are used by NewsletterMailing
+ def people
+ list = unsubscribers.map{|i| "'#{i}'"}.join(',')
+ if list.empty?
+ environment.people
+ else
+ environment.people.all(
+ :joins => "LEFT OUTER JOIN users ON (users.id = profiles.user_id)",
+ :conditions => "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.map{|blog| blog.posts.all(:limit => limit)}.flatten
+ else
+ self.blogs.map{|blog| blog.posts.where("published_at >= :last_send_at", {last_send_at: self.last_send_at}).all(:limit => limit)}.flatten
+ 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, N_("If you can't view this email, %s.") % link_to(N_('click here'), '{mailing_url}'), :id => 'newsletter-public-link')
+ end
+
+ def message_to_unsubscribe
+ 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')
+ end
+
+ def read_more(link_address)
+ content_tag(:p, link_to(N_('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)),: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)),: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'])+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|
+ if post.image
+ post_with_image(post)
+ else
+ post_without_image(post)
+ end
+ 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'])
+ end
+
+ def default_subject
+ N_('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'
+ parsed_recipients = []
+ CSV.foreach(file.path, headers: headers) do |row|
+ parsed_recipients << {name: row[name_column.to_i - 1], email: row[email_column.to_i - 1]}
+ end
+ self.additional_recipients = parsed_recipients
+ 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.last(
+ :conditions => {:source_id => self.id}
+ )
+ last_mailing.nil? ? nil : last_mailing.created_at
+ end
+
+ def sanitize(html)
+ html.gsub(/<\/?p>/, '')
+ end
+
+ def has_posts_in_the_period?
+ ! self.posts.empty?
+ end
+
+ serialize :unsubscribers, Array
+
+ def unsubscribe(email)
+ unsubscribers.push(email).uniq!
+ end
+
+end
=====================================
plugins/newsletter/lib/newsletter_plugin/newsletter_mailing.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/lib/newsletter_plugin/newsletter_mailing.rb
@@ -0,0 +1,26 @@
+class NewsletterPlugin::NewsletterMailing < EnvironmentMailing
+
+ attr_accessible :source, :person, :locale
+
+ validates_presence_of :person
+
+ def url
+ "#{self.source.top_url}/plugin/newsletter/mailing/#{self.id}"
+ end
+
+ def source
+ NewsletterPlugin::Newsletter.find(source_id)
+ end
+
+ def deliver
+ source.additional_recipients.each do |recipient|
+ begin
+ Mailing::Sender.notification(self, recipient[:email]).deliver
+ rescue Exception => ex
+ Rails.logger.error("#{ex.class.to_s} - #{ex.to_s} at #{__FILE__}:#{__LINE__}")
+ end
+ end
+ super
+ end
+
+end
=====================================
plugins/newsletter/public/newsletter_plugin.js
=====================================
--- /dev/null
+++ b/plugins/newsletter/public/newsletter_plugin.js
@@ -0,0 +1,21 @@
+jQuery(function($) {
+ $(".newsletter-toggle-link").live('click', function(){
+ element_id = this.getAttribute('element_id');
+ toggle_link = this;
+ $(element_id).slideToggle(400, function() {
+ if ($(toggle_link).find('.ui-icon').hasClass('ui-icon-triangle-1-s'))
+ $(toggle_link).find('.ui-icon')
+ .removeClass('ui-icon-triangle-1-s')
+ .addClass('ui-icon-triangle-1-n');
+ else
+ $(toggle_link).find('.ui-icon')
+ .removeClass('ui-icon-triangle-1-n')
+ .addClass('ui-icon-triangle-1-s');
+ });
+ return false;
+ });
+
+ $('#file_recipients').change(function(){
+ $('#newsletter-file-options input').enable();
+ });
+});
=====================================
plugins/newsletter/public/style.css
=====================================
--- /dev/null
+++ b/plugins/newsletter/public/style.css
@@ -0,0 +1,24 @@
+.newsletter-toggle-link {
+ cursor: pointer;
+}
+#newsletter-footer-field {
+ display: none;
+}
+#newsletter-enabled-field input#settings_enabled {
+ margin-right: 6px;
+}
+
+#newsletter-moderation-preview #newsletter-public-link,
+#newsletter-moderation-preview #newsletter-unsubscribe {
+ display: none;
+}
+
+#newsletter-moderation-preview {
+ margin-left: 25px;
+}
+
+#newsletter-moderation-preview input[type=checkbox] {
+ margin-left: -27px;
+ margin-top: 16px;
+ float: left;
+}
=====================================
plugins/newsletter/test/functional/newsletter_plugin_admin_controller_test.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/test/functional/newsletter_plugin_admin_controller_test.rb
@@ -0,0 +1,176 @@
+require File.dirname(__FILE__) + '/../../../../test/test_helper'
+
+class NewsletterPluginAdminControllerTest < ActionController::TestCase
+
+ def setup
+ @controller = NewsletterPluginAdminController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @admin = create_user('admin_newsletter').person
+ @environment = @admin.environment
+ @environment.add_admin(@admin)
+
+ @environment.enable_plugin(NewsletterPlugin)
+ @controller.stubs(:environment).returns(@environment)
+ end
+
+ should 'allow access to admin' do
+ login_as @admin.identifier
+ get :index
+ assert_response :success
+ end
+
+ should 'save footer setting' do
+ login_as @admin.identifier
+ post :index,
+ :newsletter => { :footer => 'footer of newsletter' }
+
+ assert_equal 'footer of newsletter', assigns(:newsletter).footer
+ end
+
+
+ should 'save header image' do
+ login_as @admin.identifier
+ post :index,
+ :newsletter => {
+ :image_builder => {
+ :uploaded_data => fixture_file_upload('/files/rails.png', 'image/png')
+ }
+ }
+ assert_equal 'rails.png', assigns(:newsletter).image.filename
+ end
+
+ should 'save enabled newsletter information' do
+ login_as @admin.identifier
+ post :index,
+ :newsletter => { :enabled => 'true' }
+
+ newsletter = NewsletterPlugin::Newsletter.find_by_environment_id(@environment.id)
+
+ assert newsletter.enabled
+ end
+
+ should 'save periodicity newsletter information' do
+ login_as @admin.identifier
+ post :index,
+ :newsletter => { :periodicity => '10' }
+
+ newsletter = NewsletterPlugin::Newsletter.find_by_environment_id(@environment.id)
+
+ assert_equal 10, newsletter.periodicity
+ end
+
+ should 'save number of posts per blog setting' do
+ login_as @admin.identifier
+ post :index,
+ :newsletter => { :posts_per_blog => '6' }
+
+ assert_equal 6, assigns(:newsletter).posts_per_blog
+ end
+
+ should 'show error if number of posts per blog is not a positive number' do
+ login_as @admin.identifier
+ post :index,
+ :newsletter => { :posts_per_blog => '-4' }
+
+ assert_select 'li', 'Posts per blog must be a positive number'
+ end
+
+ should 'save blogs for compiling newsletter setting' do
+ login_as @admin.identifier
+
+ blog1 = fast_create(Blog)
+ blog1.profile = fast_create(Profile, environment_id: @environment.id)
+ blog1.save
+
+ blog2 = fast_create(Blog)
+ blog2.profile = fast_create(Profile, environment_id: @environment.id)
+ blog2.save
+
+ post :index,
+ :newsletter => { :blog_ids => "#{blog1.id},#{blog2.id}" }
+
+ assert_equivalent [blog1.id,blog2.id], assigns(:newsletter).blog_ids
+ end
+
+ should 'show error if blog is not in environment' do
+ login_as @admin.identifier
+
+ blog = fast_create(Blog)
+ blog.profile = fast_create(Profile, environment_id: fast_create(Environment).id)
+ blog.save
+
+ post :index,
+ :newsletter => { :blog_ids => "#{blog.id}" }
+
+ assert_select 'li', 'Blog ids must be valid'
+ end
+
+ should 'save logged in admin as person' do
+ login_as @admin.identifier
+ post :index, :newsletter => { }
+
+ assert_equal @admin, assigns(:newsletter).person
+ end
+
+ should 'receive csv file from user' do
+ content = <<-EOS
+Coop1,name1 at example.com
+Coop2,name2 at example.com
+Coop3,name3 at example.com
+EOS
+
+ file = Tempfile.new(['recipients', '.csv'])
+ file.write(content)
+ file.rewind
+
+ login_as @admin.identifier
+ post :index, newsletter: {}, :file => { recipients: Rack::Test::UploadedFile.new(file, 'text/csv') }
+
+ file.close
+ file.unlink
+
+ assert_equivalent ["name1 at example.com", "name2 at example.com", "name3 at example.com"], assigns(:newsletter).additional_recipients.map { |recipient| recipient[:email] }
+ assert_equivalent ["Coop1", "Coop2", "Coop3"], assigns(:newsletter).additional_recipients.map { |recipient| recipient[:name] }
+ end
+
+ should 'parse csv file with configuration set by user' do
+ content = <<-EOS
+Id,Name,City,Email
+1,Coop1,Moscow,name1 at example.com
+2,Coop2,Beijing,name2 at example.com
+3,Coop3,Paris,name3 at example.com
+EOS
+
+ file = Tempfile.new(['recipients', '.csv'])
+ file.write(content)
+ file.rewind
+
+ login_as @admin.identifier
+ post :index, newsletter: {}, :file => { recipients: Rack::Test::UploadedFile.new(file, 'text/csv'), headers: 1, name: 2, email: 4 }
+
+ file.close
+ file.unlink
+
+ assert_equivalent ["name1 at example.com", "name2 at example.com", "name3 at example.com"], assigns(:newsletter).additional_recipients.map { |recipient| recipient[:email] }
+ assert_equivalent ["Coop1", "Coop2", "Coop3"], assigns(:newsletter).additional_recipients.map { |recipient| recipient[:name] }
+ end
+
+ should 'list additional recipients' do
+ login_as @admin.identifier
+ get :recipients
+ assert_select 'p', 'There are no additional recipients.'
+
+ newsletter = NewsletterPlugin::Newsletter.create!(environment: @environment, person: fast_create(Person))
+ newsletter.additional_recipients = [ {name: 'Coop1', email: 'name1 at example.com'} ]
+ newsletter.save!
+
+ get :recipients
+ assert_select 'tr' do
+ assert_select 'td', 'Coop1'
+ assert_select 'td', 'name1 at example.com'
+ end
+ end
+
+end
=====================================
plugins/newsletter/test/functional/newsletter_plugin_controller_test.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/test/functional/newsletter_plugin_controller_test.rb
@@ -0,0 +1,37 @@
+require File.dirname(__FILE__) + '/../../../../test/test_helper'
+
+class NewsletterPluginControllerTest < ActionController::TestCase
+
+ def setup
+ @controller = NewsletterPluginController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ environment = fast_create(Environment)
+ environment.enable_plugin(NewsletterPlugin)
+ @controller.stubs(:environment).returns(environment)
+ end
+
+ should 'require login to confirm unsubscription' do
+ post :confirm_unsubscription
+ assert_response 302
+ end
+
+ should 'open unsubscription page for anonymous' do
+ get :unsubscribe
+ assert_response :success
+ end
+
+ should 'add user email from unsubscribers list' do
+ NewsletterPlugin::Newsletter.create!(
+ :environment => @controller.environment,
+ :person => fast_create(Person)
+ )
+ maria = create_user("maria").person
+ login_as("maria")
+ post :confirm_unsubscription
+ assert_response :redirect
+ assert_redirected_to :controller => 'home'
+ assert_includes assigns(:newsletter).unsubscribers, maria.email
+ end
+
+end
=====================================
plugins/newsletter/test/unit/newsletter_plugin_moderate_newsletter_test.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/test/unit/newsletter_plugin_moderate_newsletter_test.rb
@@ -0,0 +1,50 @@
+require 'test_helper'
+
+class NewsletterPluginModerateNewsletterTest < ActiveSupport::TestCase
+
+ should 'validates presence of newsletter_id' do
+ task = NewsletterPlugin::ModerateNewsletter.new
+ task.valid?
+ assert task.errors.include?(:newsletter_id)
+
+ task.newsletter_id = 1
+ task.valid?
+ refute task.errors.include?(:newsletter_id)
+ end
+
+ should 'create mailing on perform' do
+ person = create_user('john').person
+ newsletter = NewsletterPlugin::Newsletter.create!(:environment => fast_create(Environment), :person => person, :enabled => true)
+ task = NewsletterPlugin::ModerateNewsletter.create!(
+ :newsletter_id => newsletter.id,
+ :target => newsletter.environment
+ )
+
+ assert_difference 'NewsletterPlugin::NewsletterMailing.count', 1 do
+ task.finish
+ end
+ end
+
+ should 'set posts for mailing body on perform' do
+ person = create_user('john').person
+ blog = fast_create(Blog, profile_id: person.id)
+ post_1 = fast_create(TextileArticle, :name => 'First post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
+ post_2 = fast_create(TextileArticle, :name => 'Second post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
+ post_3 = fast_create(TextileArticle, :name => 'Third post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
+
+ newsletter = NewsletterPlugin::Newsletter.create!(:environment => person.environment, :person => person, :enabled => true)
+ newsletter.blog_ids = [blog.id]
+ newsletter.save!
+
+ task = NewsletterPlugin::ModerateNewsletter.create!(
+ :newsletter_id => newsletter.id,
+ :target => newsletter.environment,
+ :post_ids => [post_1.id.to_s,post_2.id.to_s]
+ )
+
+ task.finish
+ assert_match /First post/, NewsletterPlugin::NewsletterMailing.last.body
+ assert_match /Second post/, NewsletterPlugin::NewsletterMailing.last.body
+ assert_not_match /Third post/, NewsletterPlugin::NewsletterMailing.last.body
+ end
+end
=====================================
plugins/newsletter/test/unit/newsletter_plugin_newsletter_mailing_test.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/test/unit/newsletter_plugin_newsletter_mailing_test.rb
@@ -0,0 +1,72 @@
+require 'test_helper'
+
+class NewsletterPluginNewsletterMailingTest < ActiveSupport::TestCase
+
+ def setup
+ ActionMailer::Base.delivery_method = :test
+ ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.deliveries = []
+ end
+
+ should 'require source id' do
+ mailing = NewsletterPlugin::NewsletterMailing.new
+ mailing.valid?
+ assert mailing.errors[:source_id].any?
+
+ mailing.source_id = NewsletterPlugin::Newsletter.create!(:environment => fast_create(Environment), :person => fast_create(Person)).id
+ mailing.valid?
+ refute mailing.errors[:source_id].any?
+ end
+
+ should 'deliver mail from noreply environment email address' do
+ environment = fast_create(Environment, :noreply_email => 'noreply at localhost')
+ person = fast_create Person
+ newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => person, :enabled => true)
+ mailing = NewsletterPlugin::NewsletterMailing.create!(
+ :source => newsletter,
+ :subject => newsletter.subject,
+ :body => newsletter.body,
+ :person => newsletter.person,
+ :locale => environment.default_locale,
+ )
+ response = NewsletterPlugin::NewsletterMailing::Sender.notification(mailing, 'recipient at example.com').deliver
+ assert_equal 'noreply at localhost', response.from.join
+ end
+
+ should 'also send to additional recipients' do
+ environment = fast_create(Environment, :name => 'Network')
+ person = create_user('betty', :environment_id => environment.id).person
+ newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => person)
+
+ newsletter.additional_recipients = [{name: 'example', email: 'exemple at mail.co'}, {name: 'jon', email: 'jonsnow at mail.co'}]
+ newsletter.save!
+
+ mailing = NewsletterPlugin::NewsletterMailing.create!(
+ :source => newsletter,
+ :subject => newsletter.subject,
+ :body => newsletter.body,
+ :person => newsletter.person,
+ :locale => newsletter.environment.default_locale,
+ )
+
+ process_delayed_job_queue
+ assert_equal 3, ActionMailer::Base.deliveries.count
+ end
+
+ should 'generate url to view mailing' do
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => fast_create(Environment),
+ :person => fast_create(Person),
+ :enabled => true
+ )
+ mailing = NewsletterPlugin::NewsletterMailing.create!(
+ :source => newsletter,
+ :subject => newsletter.subject,
+ :body => newsletter.body,
+ :person => newsletter.person,
+ :locale => newsletter.environment.default_locale,
+ )
+ assert_equal "http://localhost/plugin/newsletter/mailing/#{mailing.id}", mailing.url
+ end
+
+end
=====================================
plugins/newsletter/test/unit/newsletter_plugin_newsletter_test.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/test/unit/newsletter_plugin_newsletter_test.rb
@@ -0,0 +1,414 @@
+require 'test_helper'
+
+class NewsletterPluginNewsletterTest < ActiveSupport::TestCase
+
+ should 'throws exception when try to create newsletters without reference do environment' do
+ assert_raises ActiveRecord::RecordInvalid do |e|
+ NewsletterPlugin::Newsletter.create!
+ assert_match /Profile can't be blank/, e.to_s
+ end
+ end
+
+ should 'allow save only one newsletter by environment' do
+ environment = fast_create Environment
+ NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
+ assert_raises ActiveRecord::RecordInvalid do |e|
+ NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
+ assert_match /Profile has already been taken/, e.to_s
+ end
+ end
+
+ should 'collect enabled newsletters' do
+ enabled_newsletters = []
+ 5.times do
+ environment = fast_create(Environment)
+ enabled = environment.id % 2 == 0
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :enabled => enabled,
+ :person => fast_create(Person))
+ enabled_newsletters << newsletter.id if enabled
+ end
+ assert_equal enabled_newsletters, NewsletterPlugin::Newsletter.enabled.map(&:id)
+ end
+
+ should 'people of newsletters are the same environment members' do
+ 3.times do
+ environment = fast_create(Environment)
+ 3.times do
+ fast_create(Person, environment_id: environment)
+ end
+ NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :enabled => true,
+ :person => fast_create(Person))
+ end
+ NewsletterPlugin::Newsletter.enabled.each do |newsletter|
+ assert_not_equal [], newsletter.people
+ assert_equal newsletter.environment.people, newsletter.people
+ end
+ end
+
+ should 'save period for newsletter' do
+ environment = fast_create Environment
+ NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :periodicity => '3',
+ :person => fast_create(Person))
+
+ assert_equal 3, NewsletterPlugin::Newsletter.find_by_environment_id(environment.id).periodicity
+ end
+
+ should 'save period as number only' do
+ environment = fast_create Environment
+ assert_raises ActiveRecord::RecordInvalid do |e|
+ NewsletterPlugin::Newsletter.create!(:environment => environment, :periodicity => 'one week' )
+ assert_match /Periodicity must be a positive number/, e.to_s
+ end
+ end
+
+ should 'save period as a positive number only' do
+ environment = fast_create Environment
+ assert_raises ActiveRecord::RecordInvalid do |e|
+ NewsletterPlugin::Newsletter.create!(:environment => environment, :periodicity => -1 )
+ assert_match /Periodicity must be a positive number/, e.to_s
+ end
+ end
+
+ should 'save reference to environment blog' do
+ environment = fast_create Environment
+ blog = fast_create(Blog)
+ blog.profile = fast_create(Profile, environment_id: environment.id)
+ blog.save
+ assert_nothing_raised ActiveRecord::RecordInvalid do
+ NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :blog_ids => [blog.id],
+ :person => fast_create(Person))
+ end
+ end
+
+ should 'not save reference to unknown blog' do
+ environment = fast_create Environment
+ blog = fast_create(Blog)
+ blog.profile = fast_create(Profile, environment_id: fast_create(Environment).id)
+ blog.save
+ assert_raises ActiveRecord::RecordInvalid do |e|
+ NewsletterPlugin::Newsletter.create!(:environment => environment, :blog_ids => [blog.id])
+ assert_match /Blog ids must be valid/, e.to_s
+ end
+ assert_raises ActiveRecord::RecordInvalid do |e|
+ NewsletterPlugin::Newsletter.create!(:environment => environment, :blog_ids => [blog.id*2])
+ assert_match /Blog ids must be valid/, e.to_s
+ end
+ end
+
+ should 'not save duplicates for blog ids' do
+ environment = fast_create Environment
+ blog = fast_create(Blog)
+ blog.profile = fast_create(Profile, environment_id: environment.id)
+ blog.save
+ assert_raises ActiveRecord::RecordInvalid do |e|
+ NewsletterPlugin::Newsletter.create!(:environment => environment, :blog_ids => [blog.id, blog.id])
+ assert_match /Blog ids must not have duplicates/, e.to_s
+ end
+ end
+
+ should "not send newsletters if periodicity isn't expired" do
+ newsletter = NewsletterPlugin::Newsletter.new
+ newsletter.periodicity = 10
+ newsletter.stubs(:last_send_at).returns(DateTime.parse("2015-01-01"))
+ Date.stubs(:today).returns(Date.parse("2015-01-07"))
+ assert_equal false, newsletter.must_be_sent_today?
+ end
+
+ should 'send newsletters when periodicity expires' do
+ newsletter = NewsletterPlugin::Newsletter.new
+ newsletter.periodicity = 10
+ newsletter.stubs(:last_send_at).returns(DateTime.parse("2015-01-01"))
+ Date.stubs(:today).returns(Date.parse("2015-01-15"))
+ assert_equal true, newsletter.must_be_sent_today?
+ end
+
+ should 'send now if never send before' do
+ newsletter = NewsletterPlugin::Newsletter.new(:environment => fast_create(Environment))
+ newsletter.periodicity = 10
+ assert newsletter.must_be_sent_today?
+ end
+
+ should 'validate email format for additional recipients' do
+ environment = fast_create Environment
+ assert_raises ActiveRecord::RecordInvalid do |e|
+ NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person), additional_recipients: [{name: 'Cooperative', email: 'cooperative at example'}])
+ assert_match /Additional recipients must have only valid emails/, e.to_s
+ end
+ assert_nothing_raised ActiveRecord::RecordInvalid do |e|
+ NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person), additional_recipients: [{name: 'Cooperative', email: 'cooperative at example.com'}])
+ end
+ end
+
+ should 'parse additional recipients' do
+ content = <<-EOS
+Coop1,name1 at example.com
+Coop2,name2 at example.com
+Coop3,name3 at example.com
+EOS
+
+ file = Tempfile.new(['recipients', '.csv'])
+ file.write(content)
+ file.rewind
+
+ environment = fast_create Environment
+ newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
+ newsletter.import_recipients(Rack::Test::UploadedFile.new(file, 'text/csv'))
+
+ file.close
+ file.unlink
+
+ assert_equivalent ["name1 at example.com", "name2 at example.com", "name3 at example.com"], newsletter.additional_recipients.map { |recipient| recipient[:email] }
+ assert_equivalent ["Coop1", "Coop2", "Coop3"], newsletter.additional_recipients.map { |recipient| recipient[:name] }
+ end
+
+ should 'only parse csv files' do
+ content = <<-EOS
+Coop1,name1 at example.com
+Coop2,name2 at example.com
+Coop3,name3 at example.com
+EOS
+
+ file = Tempfile.new(['recipients', '.txt'])
+ file.write(content)
+ file.rewind
+
+ environment = fast_create Environment
+ newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
+ newsletter.import_recipients(Rack::Test::UploadedFile.new(file))
+
+ file.close
+ file.unlink
+
+ assert_equal [], newsletter.additional_recipients
+ assert_match /Additional recipients have unknown file type.*/, newsletter.errors.full_messages[0]
+ end
+
+ should 'parse additional recipients with given column number and header' do
+ content = <<-EOS
+Id,Name,City,Email
+1,Coop1,Moscow,name1 at example.com
+2,Coop2,Beijing,name2 at example.com
+3,Coop3,Paris,name3 at example.com
+EOS
+
+ file = Tempfile.new(['recipients', '.csv'])
+ file.write(content)
+ file.rewind
+
+ environment = fast_create Environment
+ newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
+ newsletter.import_recipients(Rack::Test::UploadedFile.new(file, 'text/csv'), 2, 4, true)
+
+ file.close
+ file.unlink
+
+ assert_equivalent ["name1 at example.com", "name2 at example.com", "name3 at example.com"], newsletter.additional_recipients.map { |recipient| recipient[:email] }
+ assert_equivalent ["Coop1", "Coop2", "Coop3"], newsletter.additional_recipients.map { |recipient| recipient[:name] }
+ end
+
+ should 'retrieve blogs related to newsletter' do
+ environment = fast_create Environment
+ community = fast_create(Community, :environment_id => environment.id)
+ blog1 = fast_create(Blog, :profile_id => community.id)
+ blog2 = fast_create(Blog, :profile_id => community.id)
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => environment, :blog_ids => [blog1.id, blog2.id], :person => fast_create(Person)
+ )
+ assert_equivalent [blog1, blog2], newsletter.blogs
+ end
+
+ should 'return empty if has no related blogs' do
+ environment = fast_create Environment
+ newsletter = NewsletterPlugin::Newsletter.create!(:environment => environment, :person => fast_create(Person))
+ assert_empty newsletter.blogs
+ end
+
+ should 'list posts for all selected blogs' do
+ environment = fast_create Environment
+ community = fast_create(Community, :environment_id => environment.id)
+ blog = fast_create(Blog, :profile_id => community.id)
+ post = fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news')
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :blog_ids => [blog.id],
+ :person => fast_create(Person))
+ assert_includes newsletter.posts, post
+ end
+
+ should 'generate HTML content using posts of selected blogs' do
+ environment = fast_create Environment
+ community = fast_create(Community, :environment_id => environment.id)
+ blog = fast_create(Blog, :profile_id => community.id)
+ fast_create(TextArticle, :profile_id => community.id, :parent_id => blog.id, :name => 'the last news')
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :blog_ids => [blog.id],
+ :person => fast_create(Person))
+ assert_tag_in_string newsletter.body, :tag => 'a', :content => 'the last news'
+ end
+
+ should 'limit the number of posts per blog' do
+ environment = fast_create Environment
+ community = fast_create(Community, :environment_id => environment.id)
+ blog = fast_create(Blog, :profile_id => community.id)
+ fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 1')
+ fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 2')
+ fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 3')
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :blog_ids => [blog.id],
+ :person => fast_create(Person),
+ :posts_per_blog => 2)
+ assert_equal 2, newsletter.posts.count
+ end
+
+ should 'include all posts before today' do
+ environment = fast_create Environment
+ community = fast_create(Community, :environment_id => environment.id)
+ blog = fast_create(Blog, :profile_id => community.id)
+
+ post1 = fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 1',
+ :published_at => DateTime.parse("2015-01-01"))
+ post2 = fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 2',
+ :published_at => DateTime.parse("2015-01-09"))
+
+ Date.stubs(:today).returns(DateTime.parse("2015-01-10").to_date)
+
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :blog_ids => [blog.id],
+ :person => fast_create(Person))
+
+ newsletter_posts = newsletter.posts
+ assert_includes newsletter_posts, post1
+ assert_includes newsletter_posts, post2
+ end
+
+ should 'not include posts already sent' do
+ environment = fast_create Environment
+ community = fast_create(Community, :environment_id => environment.id)
+ blog = fast_create(Blog, :profile_id => community.id)
+
+ post1 = fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 1',
+ :published_at => DateTime.parse("2015-01-01"))
+ post2 = fast_create(TextArticle, :parent_id => blog.id, :name => 'the last news 2',
+ :published_at => DateTime.parse("2015-01-09"))
+
+ Date.stubs(:today).returns(DateTime.parse("2015-01-10").to_date)
+
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :blog_ids => [blog.id],
+ :person => fast_create(Person))
+ newsletter.stubs(:last_send_at).returns(DateTime.parse("2015-01-05"))
+
+ newsletter_posts = newsletter.posts
+ assert_not_includes newsletter_posts, post1
+ assert_includes newsletter_posts, post2
+ end
+
+ should 'sanitize tags <p> from news lead' do
+ environment = fast_create Environment
+ community = fast_create(Community, :environment_id => environment.id)
+ blog = fast_create(Blog, :profile_id => community.id)
+ post = fast_create(TextArticle, :parent_id => blog.id,
+ :name => 'the last news 1',
+ :profile_id => community.id,
+ :body => "<p>paragraph of news</p>")
+
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :blog_ids => [blog.id],
+ :person => fast_create(Person))
+
+ assert_match /<p>paragraph of news<\/p>/, post.body
+ assert_not_match /<p>paragraph of news<\/p>/, newsletter.body
+ end
+
+ should 'filter posts when listing posts for newsletter' do
+ person = fast_create(Person)
+ blog = fast_create(Blog, profile_id: person.id)
+
+ post_1 = fast_create(TextileArticle, :name => 'First post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
+ post_2 = fast_create(TextileArticle, :name => 'Second post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
+ post_3 = fast_create(TextileArticle, :name => 'Third post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
+
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => person.environment,
+ :blog_ids => [blog.id],
+ :person => person)
+
+ assert_equivalent [post_2.id, post_3.id], newsletter.posts({post_ids: [post_2.id.to_s, post_3.id.to_s]}).map(&:id)
+ end
+
+ should 'filter posts in body for newsletter' do
+ person = fast_create(Person)
+ blog = fast_create(Blog, profile_id: person.id)
+
+ post_1 = fast_create(TextileArticle, :name => 'First post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
+ post_2 = fast_create(TextileArticle, :name => 'Second post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
+ post_3 = fast_create(TextileArticle, :name => 'Third post', :profile_id => person.id, :parent_id => blog.id, :body => 'Test')
+
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => person.environment,
+ :blog_ids => [blog.id],
+ :person => person)
+
+ assert_match /First post/, NewsletterPlugin::Newsletter.last.body({post_ids: [post_1.id.to_s, post_3.id.to_s]})
+ assert_not_match /Second post/, NewsletterPlugin::Newsletter.last.body({post_ids: [post_1.id.to_s, post_3.id.to_s]})
+ assert_match /Third post/, NewsletterPlugin::Newsletter.last.body({post_ids: [post_1.id.to_s, post_3.id.to_s]})
+ end
+
+ should 'add email to unsubscribers list' do
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => fast_create(Environment),
+ :person => fast_create(Person)
+ )
+ newsletter.unsubscribe("ze at localhost.localdomain")
+ assert_includes newsletter.unsubscribers, "ze at localhost.localdomain"
+ end
+
+ should 'not add same email twice to unsubscribers list' do
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => fast_create(Environment),
+ :person => fast_create(Person)
+ )
+ newsletter.unsubscribe("ze at localhost.localdomain")
+ newsletter.unsubscribe("ze at localhost.localdomain")
+ assert_equal ["ze at localhost.localdomain"], newsletter.unsubscribers
+ end
+
+ should "filter newsletter's recipients using unsubscribers list" do
+ environment = fast_create Environment
+ p1 = create_user("person1", :environment_id => environment.id).person
+ p2 = create_user("person2", :environment_id => environment.id).person
+ p3 = create_user("person3", :environment_id => environment.id).person
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :person => fast_create(Person)
+ )
+ newsletter.unsubscribe(p2.email)
+ assert_equivalent [p1, p3], newsletter.people
+ end
+
+ should "no filter newsletter's recipients if unsubscribers list empty" do
+ environment = fast_create Environment
+ p1 = create_user("person1", :environment_id => environment.id).person
+ p2 = create_user("person2", :environment_id => environment.id).person
+ p3 = create_user("person3", :environment_id => environment.id).person
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => environment,
+ :person => fast_create(Person)
+ )
+ assert_equivalent [p1, p2, p3], newsletter.people
+ end
+
+end
=====================================
plugins/newsletter/test/unit/newsletter_plugin_test.rb
=====================================
--- /dev/null
+++ b/plugins/newsletter/test/unit/newsletter_plugin_test.rb
@@ -0,0 +1,113 @@
+require 'test_helper'
+
+class NewsletterPluginTest < ActiveSupport::TestCase
+
+ def setup
+ NewsletterPlugin::Newsletter.any_instance.stubs(:must_be_sent_today?).returns(true)
+ NewsletterPlugin::Newsletter.any_instance.stubs(:has_posts_in_the_period?).returns(true)
+ end
+
+ should 'update newsletter send date only for enabled newsletters' do
+ newsletter_enabled = NewsletterPlugin::Newsletter.create!(
+ :environment => fast_create(Environment),
+ :enabled => true,
+ :subject => 'newsletter test',
+ :person => fast_create(Person))
+
+ newsletter_disabled = NewsletterPlugin::Newsletter.create!(
+ :environment => fast_create(Environment),
+ :enabled => false,
+ :subject => 'newsletter test',
+ :person => fast_create(Person))
+
+ NewsletterPlugin.compile_and_send_newsletters
+
+ newsletter_enabled.reload
+ newsletter_disabled.reload
+
+ assert_not_nil newsletter_enabled.last_send_at
+ assert_nil newsletter_disabled.last_send_at
+ end
+
+ should 'create and schedule newsletter mailing if not moderated' do
+ NewsletterPlugin::Newsletter.create!(
+ :environment => fast_create(Environment),
+ :enabled => true,
+ :moderated => false,
+ :subject => 'newsletter test',
+ :person => fast_create(Person))
+
+ assert_difference 'NewsletterPlugin::NewsletterMailing.count', 1 do
+ NewsletterPlugin.compile_and_send_newsletters
+ end
+
+ assert_equal 0, NewsletterPlugin::ModerateNewsletter.count
+ end
+
+ should 'use same environment locale on mailing' do
+ NewsletterPlugin::Newsletter.create!(
+ :environment => fast_create(Environment, :default_language => 'pt_BR'),
+ :enabled => true,
+ :subject => 'newsletter test',
+ :person => fast_create(Person))
+
+ NewsletterPlugin.compile_and_send_newsletters
+ assert_equal 'pt_BR', NewsletterPlugin::NewsletterMailing.last.locale
+ end
+
+ should 'create newsletter moderation task if newsletter is moderated' do
+ adminuser = create_user.person
+ Environment.any_instance.stubs(:admins).returns([adminuser])
+
+ NewsletterPlugin::Newsletter.create!(
+ :environment => fast_create(Environment),
+ :enabled => true,
+ :moderated => true,
+ :subject => 'newsletter test',
+ :person => fast_create(Person))
+
+ assert_difference 'NewsletterPlugin::ModerateNewsletter.count', 1 do
+ NewsletterPlugin.compile_and_send_newsletters
+ end
+
+ assert_equal 0, NewsletterPlugin::NewsletterMailing.count
+ end
+
+ should 'not create mailing if has no posts in the period' do
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => fast_create(Environment),
+ :person => fast_create(Person),
+ :enabled => true
+ )
+ NewsletterPlugin::Newsletter.any_instance.stubs(:must_be_sent_today?).returns(true)
+ NewsletterPlugin::Newsletter.any_instance.stubs(:has_posts_in_the_period?).returns(false)
+ assert_no_difference 'NewsletterPlugin::NewsletterMailing.count' do
+ NewsletterPlugin.compile_and_send_newsletters
+ end
+ end
+
+ should 'not create mailing if doesnt must be sent today' do
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => fast_create(Environment),
+ :person => fast_create(Person),
+ :enabled => true
+ )
+ NewsletterPlugin::Newsletter.any_instance.stubs(:must_be_sent_today?).returns(false)
+ NewsletterPlugin::Newsletter.any_instance.stubs(:has_posts_in_the_period?).returns(true)
+ assert_no_difference 'NewsletterPlugin::NewsletterMailing.count' do
+ NewsletterPlugin.compile_and_send_newsletters
+ end
+ end
+
+ should 'create mailing' do
+ newsletter = NewsletterPlugin::Newsletter.create!(
+ :environment => fast_create(Environment),
+ :person => fast_create(Person),
+ :enabled => true
+ )
+ assert_difference 'NewsletterPlugin::NewsletterMailing.count' do
+ NewsletterPlugin.compile_and_send_newsletters
+ end
+ end
+
+end
=====================================
plugins/newsletter/views/newsletter_plugin/mailing_not_found.html.erb
=====================================
--- /dev/null
+++ b/plugins/newsletter/views/newsletter_plugin/mailing_not_found.html.erb
@@ -0,0 +1,3 @@
+<h2><%= _("Mailing not found") %></h2>
+
+<p><%= _("There is no mailing with id #%s") % params[:id] %></p>
=====================================
plugins/newsletter/views/newsletter_plugin/unsubscribe.html.erb
=====================================
--- /dev/null
+++ b/plugins/newsletter/views/newsletter_plugin/unsubscribe.html.erb
@@ -0,0 +1,11 @@
+<h1><%= _('Cancel newsletter subscription') %></h1>
+
+<h4>
+<%= _("I don't want to receive future newsletter emails from this network.") %>
+</h4>
+
+<%= _('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')}") %>
+
+<% button_bar do %>
+ <%= button :ok, _('Confirm unsubscription'), {:action => 'confirm_unsubscription'}, :method => 'post' %>
+<% end %>
=====================================
plugins/newsletter/views/newsletter_plugin_admin/index.html.erb
=====================================
--- /dev/null
+++ b/plugins/newsletter/views/newsletter_plugin_admin/index.html.erb
@@ -0,0 +1,92 @@
+<h1><%= _('Newsletter settings') %></h1>
+
+<%= render :file => 'shared/tiny_mce' %>
+
+<%= error_messages_for :newsletter %>
+
+<%= form_for(:newsletter, html: { multipart: true }) do |f| %>
+ <%= labelled_form_field(
+ content_tag('h3', hidden_field_tag('newsletter[enabled]', false) +
+ f.check_box('enabled') +
+ _('Enable send of newsletter to members on this environment'), :id => 'newsletter-enabled-field'),
+ nil)
+ %>
+
+ <%= labelled_form_field(
+ content_tag('span', hidden_field_tag('newsletter[moderated]', false) + f.check_box('moderated') + _('Moderate newsletter each time before sending to users.')), nil)
+ %>
+
+ <h2>
+ <%= _('Content') %>
+ </h2>
+
+ <%= labelled_form_field(
+ _('Period (in days) for news compilation'), f.number_field(:periodicity, min: '0'))
+ %>
+
+ <%= labelled_form_field(
+ _('Number of posts compiled per blog (choose 0 for all posts since last newsletter)'), f.number_field(:posts_per_blog, min: '0'))
+ %>
+
+ <p><%= _('Blogs from which news will be compiled') %></p>
+ <% search_action = url_for(:action => 'search_communities') %>
+ <% selected_blogs = @blogs.map { |blog| {:id => blog.id, :name => _("%s in %s") % [blog.name, blog.profile.name]} } %>
+ <%= token_input_field_tag(
+ 'newsletter[blog_ids]', 'search-communities', search_action,
+ { hint_text: _('Type in the communities\' or blogs\' names'),
+ focus: false, pre_populate: selected_blogs }) %>
+
+ <br/>
+
+ <h2>
+ <%= _('Recipients') %>
+ </h2>
+
+ <p>
+ <%= _('You can follow the link below to see which e-mails are currently being used as additional recipients for this newsletter.') %>
+ </p>
+ <p>
+ <%= link_to 'Currently set additional recipients', {action: :recipients}, target: '_blank' %>
+ </p>
+
+ <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>
+
+ <%= labelled_form_field(
+ _('Additional recipients for newsletter'), file_field_tag('file[recipients]', accept: '.csv'))
+ %>
+
+ <div id='newsletter-file-options'>
+ <%= labelled_form_field(
+ content_tag('span', check_box_tag('file[headers]', 1, false, disabled: true) + _('The CSV file contains a header row')), nil)
+ %>
+
+ <%= labelled_form_field(
+ _('Number of colunm with name field'), number_field_tag('file[name]', '1', min: '1', disabled: true))
+ %>
+
+ <%= labelled_form_field(
+ _('Number of colunm with email field'), number_field_tag('file[email]', '2', min: '1', disabled: true))
+ %>
+ </div>
+
+ <h2>
+ <%= _('Layout') %>
+ </h2>
+
+ <%= f.fields_for :image_builder, @newsletter.image do |i| %>
+ <%= file_field_or_thumbnail(_('Header image (images with 640px width):'), @newsletter.image, i) %>
+ <% end %>
+
+ <%= labelled_form_field(
+ content_tag('h3', ui_icon('ui-icon-triangle-1-s') +
+ _('Newsletter footer'), :class => 'newsletter-toggle-link', :element_id => '#newsletter-footer-field'),
+ content_tag('div',
+ f.text_area(:footer, :style => 'width: 100%', :class => 'mceEditor'),
+ :id => 'newsletter-footer-field'
+ ))
+ %>
+ <% button_bar do %>
+ <%= submit_button :save, _('Save') %>
+ <%= submit_button :save, _('Save and visualize'), :name => "visualize", :cancel => {:controller => 'plugins'} %>
+ <% end %>
+<% end %>
=====================================
plugins/newsletter/views/newsletter_plugin_admin/recipients.html.erb
=====================================
--- /dev/null
+++ b/plugins/newsletter/views/newsletter_plugin_admin/recipients.html.erb
@@ -0,0 +1,21 @@
+<h1><%= _('Additional recipients') %></h1>
+
+<% if @additional_recipients.present? %>
+ <table border="0" cellspacing="5" cellpadding="5">
+ <tr>
+ <th><%= _('Name') %></th>
+ <th><%= _('E-mail') %></th>
+ </tr>
+ <% @additional_recipients.each do |recipient| %>
+ <tr>
+ <td><%= recipient[:name] %></td>
+ <td><%= recipient[:email] %></td>
+ </tr>
+ <% end %>
+ </table>
+<% else %>
+ <p><%= _('There are no additional recipients.') %></p>
+<% end %>
+
+<br/>
+<%= button :back, _('Back'), action: :index %>
=====================================
plugins/newsletter/views/tasks/newsletter_plugin/_moderate_newsletter_accept_details.html.erb
=====================================
--- /dev/null
+++ b/plugins/newsletter/views/tasks/newsletter_plugin/_moderate_newsletter_accept_details.html.erb
@@ -0,0 +1,17 @@
+<% newsletter = NewsletterPlugin::Newsletter.find(task.newsletter_id) %>
+
+<h1><%= _('Check posts you want to include') %></h1>
+
+<div id='newsletter-moderation-preview'>
+ <% newsletter_content = newsletter.body.gsub(/width: 640px;/,'').sub(/#{NewsletterPlugin::Newsletter::CSS['breaking-news-wrap']}/, '') %>
+
+ <% newsletter.posts.each do |post| %>
+ <% input_name = "tasks[#{task.id}][task][post_ids][]" %>
+ <% post_check_box = hidden_field_tag(input_name, '0') +check_box_tag(input_name, post.id, true) %>
+
+ <% newsletter_content.gsub!(/<span id="#{post.id}"/, post_check_box+ '<span') %>
+ <% newsletter_content.gsub!(/<img id="#{post.id}"/, post_check_box+ '<img') %>
+ <% end %>
+
+ <%= newsletter_content %>
+</div>
=====================================
script/sample-articles
=====================================
--- a/script/sample-articles
+++ b/script/sample-articles
@@ -65,3 +65,22 @@ for subject in EVENT_SUBJECTS
end
end
done
+
+print "Creating some posts: "
+for subject in SUBJECTS
+ rand(20).times do |i|
+ profile = profiles.sample
+ name = "%s #{subject}" % profile.name
+ article = TinyMceArticle.new(
+ :name => name,
+ :body => name,
+ :tag_list => [TAGS.sample, TAGS.sample],
+ :profile => profile,
+ :parent_id => profile.blog.id
+ )
+ save article do
+ article.add_category categories.sample
+ end
+ end
+end
+done
View it on GitLab: https://gitlab.com/noosfero/noosfero/compare/ee4ad0411d760cc2a598747439e6b4a86204cf11...1d587511ce773f8686ed050f05ef96ac79c0d7fc
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listas.softwarelivre.org/pipermail/noosfero-dev/attachments/20150911/850188c4/attachment-0001.html>
More information about the Noosfero-dev
mailing list