liquid_feedback_frontend

changeset 1248:c0fd12b97d65

Changes on notifications system, newsletter support added
author bsw
date Tue Apr 05 20:40:37 2016 +0200 (2016-04-05)
parents 9578cef4018a
children 62f7e7d4f9ec
files app/main/_prefork/10_init.lua app/main/admin/_action/newsletter_update.lua app/main/admin/index.lua app/main/admin/newsletter_edit.lua app/main/admin/newsletter_list.lua app/main/index/_index_member.lua app/main/index/_notifications.lua app/main/index/_sidebar_notifications.lua app/main/member/_action/update_notify_level.lua app/main/member/settings_notification.lua model/event.lua model/initiative_for_notification.lua model/newsletter.lua model/newsletter_to_send.lua model/scheduled_notification_to_send.lua
line diff
     1.1 --- a/app/main/_prefork/10_init.lua	Wed Jan 27 11:16:26 2016 +0100
     1.2 +++ b/app/main/_prefork/10_init.lua	Tue Apr 05 20:40:37 2016 +0200
     1.3 @@ -134,8 +134,20 @@
     1.4      proto = "interval",
     1.5      name  = "send_pending_notifications",
     1.6      delay = 5,
     1.7 -    handler = function() 
     1.8 -      Event:send_pending_notifications()
     1.9 +    handler = function()
    1.10 +      while true do
    1.11 +        local did_work = false
    1.12 +        local tmp
    1.13 +        tmp = Newsletter:send_next_newsletter()
    1.14 +        if tmp then did_work = true end
    1.15 +        tmp = Event:send_next_notification()
    1.16 +        if tmp then did_work = true end
    1.17 +        tmp = InitiativeForNotification:notify_next_member()
    1.18 +        if tmp then did_work = true end
    1.19 +        if not did_work then
    1.20 +          break
    1.21 +        end
    1.22 +      end
    1.23      end
    1.24    },
    1.25    min_fork = 1,
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/app/main/admin/_action/newsletter_update.lua	Tue Apr 05 20:40:37 2016 +0200
     2.3 @@ -0,0 +1,29 @@
     2.4 +local id = param.get_id()
     2.5 +
     2.6 +local newsletter
     2.7 +
     2.8 +if id then
     2.9 +  newsletter = Newsletter:by_id(id)
    2.10 +  if newsletter.sent then
    2.11 +    slot.select("error", function()
    2.12 +      ui.tag{ content = _"Newsletter has already been sent out" }
    2.13 +    end)
    2.14 +    return false
    2.15 +  end
    2.16 +else
    2.17 +  newsletter = Newsletter:new()
    2.18 +end
    2.19 +
    2.20 +newsletter.published = param.get("published")
    2.21 +if newsletter.published == nil or newsletter.published == "" then
    2.22 +  newsletter.published = "now"
    2.23 +end
    2.24 +newsletter.unit_id = param.get("unit_id", atom.integer)
    2.25 +if newsletter.unit_id == 0 then
    2.26 +  newsletter.unit_id = nil
    2.27 +end
    2.28 +newsletter.include_all_members = param.get("include_all_members", atom.boolean)
    2.29 +newsletter.subject = param.get("subject")
    2.30 +newsletter.content = param.get("content")
    2.31 +
    2.32 +newsletter:save()
     3.1 --- a/app/main/admin/index.lua	Wed Jan 27 11:16:26 2016 +0100
     3.2 +++ b/app/main/admin/index.lua	Tue Apr 05 20:40:37 2016 +0200
     3.3 @@ -38,6 +38,27 @@
     3.4  
     3.5  ui.sidebar( "tab-whatcanido", function()
     3.6    ui.sidebarHead( function()
     3.7 +    ui.heading { level = 2, content = _"Newsletter" }
     3.8 +  end )
     3.9 +  
    3.10 +  ui.sidebarSection( "moreLink", function()
    3.11 +    ui.link{
    3.12 +      text = _"Create a newsletter",
    3.13 +      module = "admin",
    3.14 +      view = "newsletter_edit"
    3.15 +    }
    3.16 +  end )
    3.17 +  ui.sidebarSection( "moreLink", function()
    3.18 +    ui.link{
    3.19 +      text = _"Manage newsletters",
    3.20 +      module = "admin",
    3.21 +      view = "newsletter_list"
    3.22 +    }
    3.23 +  end )
    3.24 +end )
    3.25 +
    3.26 +ui.sidebar( "tab-whatcanido", function()
    3.27 +  ui.sidebarHead( function()
    3.28      ui.heading { level = 2, content = _"Cancel issue" }
    3.29    end )
    3.30    
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/app/main/admin/newsletter_edit.lua	Tue Apr 05 20:40:37 2016 +0200
     4.3 @@ -0,0 +1,59 @@
     4.4 +local id = param.get_id()
     4.5 +
     4.6 +local newsletter = {}
     4.7 +
     4.8 +if id then
     4.9 +  newsletter = Newsletter:by_id(id)
    4.10 +end
    4.11 +
    4.12 +ui.titleAdmin(_"Newsletter")
    4.13 +
    4.14 +ui.form{
    4.15 +  attr = { class = "vertical section" },
    4.16 +  module = "admin",
    4.17 +  action = "newsletter_update",
    4.18 +  id = newsletter and newsletter.id,
    4.19 +  record = newsletter,
    4.20 +  routing = {
    4.21 +    default = {
    4.22 +      mode = "redirect",
    4.23 +      modules = "admin",
    4.24 +      view = "newsletter_list"
    4.25 +    }
    4.26 +  },
    4.27 +  content = function()
    4.28 +
    4.29 +    ui.sectionHead( function()
    4.30 +      ui.heading { level = 1, content = newsletter and (newsletter.subject) or _"New newsletter" }
    4.31 +    end )
    4.32 +  
    4.33 +    ui.sectionRow( function()
    4.34 +      local units = { 
    4.35 +        { id = 0, name = _"All members" },
    4.36 +        { id = "_", name = _"" },
    4.37 +      }
    4.38 +      for i, unit in ipairs(Unit:get_flattened_tree()) do
    4.39 +        units[#units+1] = unit
    4.40 +      end
    4.41 +      ui.field.text{ label = _"Date", name = "published" }
    4.42 +      ui.field.select{
    4.43 +        label = "Recipient",
    4.44 +        name = "unit_id",
    4.45 +        foreign_records = units,
    4.46 +        foreign_id = "id",
    4.47 +        foreign_name = "name",
    4.48 +        disabled_records = { ["_"] = true },
    4.49 +        value = newsletter.unit_id
    4.50 +      }
    4.51 +      ui.field.boolean{  label = _"Override disable notifications?", name = "include_all_members" }
    4.52 +      slot.put("<br />")
    4.53 +      ui.field.text{     label = _"Subject", name = "subject" }
    4.54 +      ui.field.text{     label = _"Content", name = "content", multiline = true, attr = { rows = "20" } }
    4.55 +
    4.56 +      ui.submit{         text  = _"create newsletter" }
    4.57 +      slot.put(" ")
    4.58 +      ui.link { module = "admin", view = "index", content = _"cancel" }
    4.59 +    end )
    4.60 +  end
    4.61 +}
    4.62 +
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/app/main/admin/newsletter_list.lua	Tue Apr 05 20:40:37 2016 +0200
     5.3 @@ -0,0 +1,32 @@
     5.4 +local newsletter = Newsletter:new_selector()
     5.5 +  :add_order_by("published DESC")
     5.6 +  :exec()
     5.7 +
     5.8 +ui.titleAdmin(_"Newsletter")
     5.9 +
    5.10 +ui.section( function()
    5.11 +
    5.12 +  ui.sectionHead( function()
    5.13 +    ui.heading { level = 1, content = _"Newsletter list" }
    5.14 +  end )
    5.15 +
    5.16 +  ui.sectionRow( function ()
    5.17 +
    5.18 +    ui.list{
    5.19 +      records = newsletter,
    5.20 +      columns = {
    5.21 +        { label = _"Unit", content = function(r) ui.tag{ content = r.unit and r.unit.name or _"All members" } end },
    5.22 +        { name = "published", label = _"Published" },
    5.23 +        { name = "subject", label = _"Subject" },
    5.24 +        { label = _"sent", content = function(r) 
    5.25 +          if not r.sent then 
    5.26 +            ui.link{ text = _"Edit", module = "admin", view = "newsletter_edit", id = r.id } 
    5.27 +          else
    5.28 +            ui.tag{ content = format.timestamp(r.sent) }
    5.29 +          end 
    5.30 +        end }
    5.31 +      }
    5.32 +    }
    5.33 +
    5.34 +  end)
    5.35 +end)
     6.1 --- a/app/main/index/_index_member.lua	Wed Jan 27 11:16:26 2016 +0100
     6.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.3 @@ -1,66 +0,0 @@
     6.4 -
     6.5 -local tabs = {
     6.6 -  module = "index",
     6.7 -  view = "index"
     6.8 -}
     6.9 -
    6.10 -tabs[#tabs+1] = {
    6.11 -  name = "areas",
    6.12 -  label = _"Home",
    6.13 -  icon = { static = "icons/16/package.png" },
    6.14 -  module = "index",
    6.15 -  view = "_member_home",
    6.16 -  params = { member = app.session.member }
    6.17 -}
    6.18 -
    6.19 -tabs[#tabs+1] = {
    6.20 -  name = "timeline",
    6.21 -  label = _"Latest events",
    6.22 -  module = "event",
    6.23 -  view = "_list",
    6.24 -  params = { }
    6.25 -}
    6.26 -
    6.27 -
    6.28 -tabs[#tabs+1] = {
    6.29 -  name = "open",
    6.30 -  label = _"Open issues",
    6.31 -  module = "issue",
    6.32 -  view = "_list",
    6.33 -  params = {
    6.34 -    for_state = "open",
    6.35 -    issues_selector = Issue:new_selector()
    6.36 -      :add_where("issue.closed ISNULL")
    6.37 -      :add_order_by("coalesce(issue.fully_frozen + issue.voting_time, issue.half_frozen + issue.verification_time, issue.accepted + issue.discussion_time, issue.created + issue.max_admission_time) - now()")
    6.38 -  }
    6.39 -}
    6.40 -
    6.41 -tabs[#tabs+1] = {
    6.42 -  name = "closed",
    6.43 -  label = _"Closed issues",
    6.44 -  module = "issue",
    6.45 -  view = "_list",
    6.46 -  params = {
    6.47 -    for_state = "closed",
    6.48 -    issues_selector = Issue:new_selector()
    6.49 -      :add_where("issue.closed NOTNULL")
    6.50 -      :add_order_by("issue.closed DESC")
    6.51 -
    6.52 -  }
    6.53 -}
    6.54 -
    6.55 -tabs[#tabs+1] = {
    6.56 -  name = "members",
    6.57 -  label = _"Members",
    6.58 -  module = 'member',
    6.59 -  view   = '_list',
    6.60 -  params = { members_selector = Member:new_selector():add_where("active") }
    6.61 -}
    6.62 -
    6.63 -if not param.get("tab") then
    6.64 -  execute.view{
    6.65 -    module = "index", view = "_notifications"
    6.66 -  }
    6.67 -end
    6.68 -
    6.69 -ui.tabs(tabs)
     7.1 --- a/app/main/index/_notifications.lua	Wed Jan 27 11:16:26 2016 +0100
     7.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.3 @@ -1,103 +0,0 @@
     7.4 -local notification_links = {}
     7.5 -
     7.6 -if app.session.member.notify_email_unconfirmed then
     7.7 -  notification_links[#notification_links+1] = {
     7.8 -    module = "index", view = "email_unconfirmed",
     7.9 -    text = _"Please confirm your email address"
    7.10 -  }
    7.11 -end
    7.12 -
    7.13 -if app.session.member.notify_level == nil then
    7.14 -  notification_links[#notification_links+1] = {
    7.15 -    module = "member", view = "settings_notification",
    7.16 -    text = _"Please select your preferred notification level"
    7.17 -  }
    7.18 -end
    7.19 -
    7.20 -if config.check_delegations_interval_soft then
    7.21 -  local member = Member:new_selector()
    7.22 -    :add_where({ "id = ?", app.session.member_id })
    7.23 -    :add_field({ "now() > COALESCE(last_delegation_check, activated) + ?::interval", config.check_delegations_interval_soft }, "needs_delegation_check_soft")
    7.24 -    :single_object_mode()
    7.25 -    :exec()
    7.26 -    
    7.27 -
    7.28 -  if member.needs_delegation_check_soft then
    7.29 -
    7.30 -    local delegations = Delegation:delegations_to_check_for_member_id(member.id)
    7.31 -    
    7.32 -    if #delegations > 0 then
    7.33 -      notification_links[#notification_links+1] = {
    7.34 -        module = "index", view = "check_delegations", 
    7.35 -        text = _"Check your delegations!"
    7.36 -      }
    7.37 -    end
    7.38 -    
    7.39 -  end
    7.40 -end
    7.41 -
    7.42 -local broken_delegations_count = Delegation:selector_for_broken(app.session.member_id):count()
    7.43 -
    7.44 -if broken_delegations_count > 0 then
    7.45 -  notification_links[#notification_links+1] = {
    7.46 -    module = "index", view = "broken_delegations",
    7.47 -    text = _("#{count} of your outgoing delegation(s) are broken", { count = broken_delegations_count })
    7.48 -  }
    7.49 -  
    7.50 -end
    7.51 -
    7.52 -local selector = Issue:new_selector()
    7.53 -  :join("area", nil, "area.id = issue.area_id")
    7.54 -  :join("privilege", nil, { "privilege.unit_id = area.unit_id AND privilege.member_id = ? AND privilege.voting_right", app.session.member_id })
    7.55 -  :left_join("direct_voter", nil, { "direct_voter.issue_id = issue.id AND direct_voter.member_id = ?", app.session.member.id })
    7.56 -  :left_join("non_voter", nil, { "non_voter.issue_id = issue.id AND non_voter.member_id = ?", app.session.member.id })
    7.57 -  :left_join("interest", nil, { "interest.issue_id = issue.id AND interest.member_id = ?", app.session.member.id })
    7.58 -  :add_where{ "direct_voter.member_id ISNULL" }
    7.59 -  :add_where{ "non_voter.member_id ISNULL" }
    7.60 -  :add_where{ "interest.member_id NOTNULL" }
    7.61 -  :add_where{ "issue.fully_frozen NOTNULL" }
    7.62 -  :add_where{ "issue.closed ISNULL" }
    7.63 -  :add_order_by{ "issue.fully_frozen + issue.voting_time ASC" }
    7.64 -  
    7.65 -local issues_to_vote_count = selector:count()
    7.66 -if issues_to_vote_count > 0 then
    7.67 -    notification_links[#notification_links+1] = {
    7.68 -    module = "index", view = "index",
    7.69 -    params = {
    7.70 -      tab = "open", filter = "frozen", filter_interest = "issue", filter_delegation = "direct", filter_voting = "not_voted"
    7.71 -    },
    7.72 -    text = _("You have not voted #{count} issue(s) you were interested in", { count = issues_to_vote_count })
    7.73 -  }
    7.74 -end
    7.75 -
    7.76 -local initiator_invites_count = Initiator:selector_for_invites(app.session.member_id):count()
    7.77 -  
    7.78 -if initiator_invites_count > 0 then
    7.79 -  notification_links[#notification_links+1] = {
    7.80 -    module = "index", view = "initiator_invites",
    7.81 -    text = _("You are invited to #{count} initiative(s)", { count = initiator_invites_count })
    7.82 -  }
    7.83 -end
    7.84 -
    7.85 -updated_drafts_count = Initiative:selector_for_updated_drafts(app.session.member_id):count()
    7.86 -
    7.87 -if updated_drafts_count > 0 then
    7.88 -  notification_links[#notification_links+1] = {
    7.89 -    module = "index", view = "updated_drafts",
    7.90 -    text = _("New drafts for #{count} initiative(s) you are supporting", { count = updated_drafts_count })
    7.91 -  }
    7.92 -end
    7.93 -
    7.94 -if #notification_links > 0 then
    7.95 -  ui.container{ attr = { class = "notifications" }, content = function()
    7.96 -    ui.tag{ tag = "ul", attr = { class = "notifications" }, content = function()
    7.97 -      for i, notification_link in ipairs(notification_links) do
    7.98 -        ui.tag{ tag = "li", content = function()
    7.99 -          ui.link(notification_link)
   7.100 -        end }
   7.101 -      end
   7.102 -    end }
   7.103 -  end }
   7.104 -end
   7.105 -
   7.106 -
     8.1 --- a/app/main/index/_sidebar_notifications.lua	Wed Jan 27 11:16:26 2016 +0100
     8.2 +++ b/app/main/index/_sidebar_notifications.lua	Tue Apr 05 20:40:37 2016 +0200
     8.3 @@ -7,13 +7,6 @@
     8.4    }
     8.5  end
     8.6  
     8.7 -if app.session.member.notify_level == nil then
     8.8 -  notification_links[#notification_links+1] = {
     8.9 -    module = "member", view = "settings_notification",
    8.10 -    text = _"Select a notification level"
    8.11 -  }
    8.12 -end
    8.13 -
    8.14  if config.check_delegations_interval_soft then
    8.15    local member = Member:new_selector()
    8.16      :add_where({ "id = ?", app.session.member_id })
     9.1 --- a/app/main/member/_action/update_notify_level.lua	Wed Jan 27 11:16:26 2016 +0100
     9.2 +++ b/app/main/member/_action/update_notify_level.lua	Tue Apr 05 20:40:37 2016 +0200
     9.3 @@ -1,2 +1,2 @@
     9.4 -app.session.member.notify_level = param.get("notify_level")
     9.5 +app.session.member.disable_notifications = param.get("disable_notifications") == "true" and true or false
     9.6  app.session.member:save()
    10.1 --- a/app/main/member/settings_notification.lua	Wed Jan 27 11:16:26 2016 +0100
    10.2 +++ b/app/main/member/settings_notification.lua	Tue Apr 05 20:40:37 2016 +0200
    10.3 @@ -36,8 +36,8 @@
    10.4              tag = "input", 
    10.5              attr = {
    10.6                id = "notify_level_all",
    10.7 -              type = "radio", name = "notify_level", value = "all",
    10.8 -              checked = app.session.member.notify_level == 'all' and "checked" or nil
    10.9 +              type = "radio", name = "disable_notifications", value = "false",
   10.10 +              checked = not app.session.member.disable_notifications and "checked" or nil
   10.11              }
   10.12            }
   10.13            ui.tag{
   10.14 @@ -52,60 +52,9 @@
   10.15            ui.tag{
   10.16              tag = "input", 
   10.17              attr = {
   10.18 -              id = "notify_level_discussion",
   10.19 -              type = "radio", name = "notify_level", value = "discussion",
   10.20 -              checked = app.session.member.notify_level == 'discussion' and "checked" or nil
   10.21 -            }
   10.22 -          }
   10.23 -          ui.tag{
   10.24 -            tag = "label", attr = { ['for'] = "notify_level_discussion" },
   10.25 -            content = _"Only for issues reaching the discussion phase"
   10.26 -          }
   10.27 -        end }
   10.28 -
   10.29 -        slot.put("<br />")
   10.30 -
   10.31 -        ui.container{ content = function()
   10.32 -          ui.tag{
   10.33 -            tag = "input", 
   10.34 -            attr = {
   10.35 -              id = "notify_level_verification",
   10.36 -              type = "radio", name = "notify_level", value = "verification",
   10.37 -              checked = app.session.member.notify_level == 'verification' and "checked" or nil
   10.38 -            }
   10.39 -          }
   10.40 -          ui.tag{
   10.41 -            tag = "label", attr = { ['for'] = "notify_level_verification" },
   10.42 -            content = _"Only for issues reaching the verification phase"
   10.43 -          }
   10.44 -        end }
   10.45 -        
   10.46 -        slot.put("<br />")
   10.47 -
   10.48 -        ui.container{ content = function()
   10.49 -          ui.tag{
   10.50 -            tag = "input", 
   10.51 -            attr = {
   10.52 -              id = "notify_level_voting",
   10.53 -              type = "radio", name = "notify_level", value = "voting",
   10.54 -              checked = app.session.member.notify_level == 'voting' and "checked" or nil
   10.55 -            }
   10.56 -          }
   10.57 -          ui.tag{
   10.58 -            tag = "label", attr = { ['for'] = "notify_level_voting" },
   10.59 -            content = _"Only for issues reaching the voting phase"
   10.60 -          }
   10.61 -        end }
   10.62 -
   10.63 -        slot.put("<br />")
   10.64 -
   10.65 -        ui.container{ content = function()
   10.66 -          ui.tag{
   10.67 -            tag = "input", 
   10.68 -            attr = {
   10.69                id = "notify_level_none",
   10.70 -              type = "radio", name = "notify_level", value = "none",
   10.71 -              checked = app.session.member.notify_level == 'none' and "checked" or nil
   10.72 +              type = "radio", name = "disable_notifications", value = "true",
   10.73 +              checked = app.session.member.disable_notifications and "checked" or nil
   10.74              }
   10.75            }
   10.76            ui.tag{
   10.77 @@ -116,11 +65,6 @@
   10.78          
   10.79          slot.put("<br />")
   10.80        
   10.81 -        ui.container { content = _"Notifications are only send to you about events in the subject areas you subscribed, the issues you are interested in and the initiatives you are supporting." }
   10.82 -
   10.83 -
   10.84 -        slot.put("<br />")
   10.85 -        
   10.86          ui.tag{
   10.87            tag = "input",
   10.88            attr = {
    11.1 --- a/model/event.lua	Wed Jan 27 11:16:26 2016 +0100
    11.2 +++ b/model/event.lua	Tue Apr 05 20:40:37 2016 +0200
    11.3 @@ -51,12 +51,12 @@
    11.4  function Event.object:send_notification()
    11.5    
    11.6    local members_to_notify = Member:new_selector()
    11.7 -    :join("event_seen_by_member", nil, { "event_seen_by_member.seen_by_member_id = member.id AND event_seen_by_member.notify_level <= member.notify_level AND event_seen_by_member.id = ?", self.id } )
    11.8 -    :add_where("member.activated NOTNULL AND member.notify_email NOTNULL")
    11.9 +    :join("event_for_notification", nil, { "event_for_notification.recipient_id = member.id AND event_for_notification.id = ?", self.id } )
   11.10 +    --:add_where("member.activated NOTNULL AND member.notify_email NOTNULL")
   11.11      -- SAFETY FIRST, NEVER send notifications for events more then 3 days in past or future
   11.12 -    :add_where("now() - event_seen_by_member.occurrence BETWEEN '-3 days'::interval AND '3 days'::interval")
   11.13 +    :add_where("now() - event_for_notification.occurrence BETWEEN '-3 days'::interval AND '3 days'::interval")
   11.14      -- do not notify a member about the events caused by the member
   11.15 -    :add_where("event_seen_by_member.member_id ISNULL OR event_seen_by_member.member_id != member.id")
   11.16 +    :add_where("event_for_notification.member_id ISNULL OR event_for_notification.member_id != member.id")
   11.17      :exec()
   11.18      
   11.19    io.stderr:write("Sending notifications for event " .. self.id .. " to " .. (#members_to_notify) .. " members\n")
   11.20 @@ -71,32 +71,22 @@
   11.21        { lang = member.lang or config.default_lang or 'en' },
   11.22        function()
   11.23  
   11.24 -	local suggestion
   11.25 -        if self.suggestion_id then
   11.26 -	  suggestion = Suggestion:by_id(self.suggestion_id)
   11.27 -	  if not suggestion then
   11.28 -	    return
   11.29 -	  end
   11.30 -	end
   11.31 -
   11.32 -	subject = config.mail_subject_prefix .. " " .. self.event_name
   11.33          body = body .. _("[event mail]      Unit: #{name}", { name = self.issue.area.unit.name }) .. "\n"
   11.34          body = body .. _("[event mail]      Area: #{name}", { name = self.issue.area.name }) .. "\n"
   11.35          body = body .. _("[event mail]     Issue: ##{id}", { id = self.issue_id }) .. "\n\n"
   11.36          body = body .. _("[event mail]    Policy: #{policy}", { policy = self.issue.policy.name }) .. "\n\n"
   11.37 -        body = body .. _("[event mail]     Event: #{event}", { event = self.event_name }) .. "\n\n"
   11.38          body = body .. _("[event mail]     Phase: #{phase}", { phase = self.state_name }) .. "\n\n"
   11.39  
   11.40          if self.initiative_id then
   11.41            url = request.get_absolute_baseurl() .. "initiative/show/" .. self.initiative_id .. ".html"
   11.42 -        elseif self.suggestion_id then
   11.43 -          url = request.get_absolute_baseurl() .. "suggestion/show/" .. self.suggestion_id .. ".html"
   11.44          else
   11.45            url = request.get_absolute_baseurl() .. "issue/show/" .. self.issue_id .. ".html"
   11.46          end
   11.47          
   11.48          body = body .. _("[event mail]       URL: #{url}", { url = url }) .. "\n\n"
   11.49          
   11.50 +        local leading_initiative
   11.51 +        
   11.52          if self.initiative_id then
   11.53            local initiative = Initiative:by_id(self.initiative_id)
   11.54            body = body .. _("i#{id}: #{name}", { id = initiative.id, name = initiative.name }) .. "\n\n"
   11.55 @@ -110,6 +100,9 @@
   11.56              :limit(3)
   11.57              :exec()
   11.58            for i, initiative in ipairs(initiatives) do
   11.59 +            if i == 1 then
   11.60 +              leading_initiative = initiative
   11.61 +            end
   11.62              body = body .. _("i#{id}: #{name}", { id = initiative.id, name = initiative.name }) .. "\n"
   11.63            end
   11.64            if initiative_count - 3 > 0 then
   11.65 @@ -118,10 +111,14 @@
   11.66            body = body .. "\n"
   11.67          end
   11.68          
   11.69 -        if suggestion then
   11.70 -          body = body .. _("#{name}\n\n", { name = suggestion.name })
   11.71 +        subject = config.mail_subject_prefix
   11.72 +        
   11.73 +        if self.event == "issue_state_changed" then
   11.74 +          subject = subject .. _("State of #{issue} changed to #{state} / Leading: #{initiative}", { issue = self.issue.name, state = Issue:get_state_name_for_state(self.state), initiative = leading_initiative.display_name })
   11.75 +        elseif self.event == "initiative_revoked" then
   11.76 +          subject = subject .. _("Initiative revoked: #{initiative_name}", { initiative_name = self.initiative.display_name })
   11.77          end
   11.78 -  
   11.79 +      
   11.80          local success = net.send_mail{
   11.81            envelope_from = config.mail_envelope_from,
   11.82            from          = config.mail_from,
   11.83 @@ -175,23 +172,3 @@
   11.84    end
   11.85  
   11.86  end
   11.87 -
   11.88 -function Event:send_pending_notifications()
   11.89 -  while true do
   11.90 -    if not Event:send_next_notification() then
   11.91 -      break 
   11.92 -    end
   11.93 -  end
   11.94 -end
   11.95 -
   11.96 -function Event:send_notifications_loop()
   11.97 -
   11.98 -  while true do
   11.99 -    local did_work = Event:send_next_notification()
  11.100 -    if not did_work then
  11.101 -      print "Sleeping 5 second"
  11.102 -      os.execute("sleep 5")
  11.103 -    end
  11.104 -  end
  11.105 -  
  11.106 -end
  11.107 \ No newline at end of file
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/model/initiative_for_notification.lua	Tue Apr 05 20:40:37 2016 +0200
    12.3 @@ -0,0 +1,178 @@
    12.4 +InitiativeForNotification = mondelefant.new_class()
    12.5 +InitiativeForNotification.table = 'initiative_for_notification'
    12.6 +
    12.7 +InitiativeForNotification:add_reference{
    12.8 +  mode          = 'm1',
    12.9 +  to            = "Initiative",
   12.10 +  this_key      = 'initiative_id',
   12.11 +  that_key      = 'id',
   12.12 +  ref           = 'initiative',
   12.13 +}
   12.14 +
   12.15 +
   12.16 +function InitiativeForNotification:notify_member_id(member_id)
   12.17 +  
   12.18 +  db:query("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ")
   12.19 +  
   12.20 +  local member = Member:by_id(member_id)
   12.21 +  locale.set{ lang = member.lang or config.default_lang }
   12.22 + 
   12.23 +  io.stderr:write("Sending digest #" .. member.notification_counter .. " to " .. member.notify_email .. "\n")
   12.24 +  
   12.25 +  if not member.notify_email then
   12.26 +    return
   12.27 +  end
   12.28 +  
   12.29 +  local selector = db:new_selector()
   12.30 +    :add_field("*")
   12.31 +    :from({ "get_initiatives_for_notification(?)", member_id }, "initiative_for_notification")
   12.32 +    :join("initiative", nil, "initiative.id = initiative_for_notification.initiative_id")
   12.33 +    :join("issue", nil, "issue.id = initiative.issue_id")
   12.34 +    :join("member", nil, "member.id = initiative_for_notification.recipient_id")
   12.35 +    :add_order_by("md5(initiative_for_notification.recipient_id || '-' || member.notification_counter || '-' || issue.area_id)")
   12.36 +    :add_order_by("md5(initiative_for_notification.recipient_id || '-' || member.notification_counter || '-' || issue.id)")
   12.37 +  selector._class = self
   12.38 +
   12.39 +  local initiatives_for_notification = selector:exec()
   12.40 +
   12.41 +  if #initiatives_for_notification < 1 then
   12.42 +    return
   12.43 +  end
   12.44 +  
   12.45 +  local initiatives = initiatives_for_notification:load("initiative")
   12.46 +  local issues = initiatives:load("issue")
   12.47 +  issues:load("area")
   12.48 +  issues:load("policy")
   12.49 +
   12.50 +  local last_area_id
   12.51 +  local last_issue_id
   12.52 +  
   12.53 +  local draft_count = 0
   12.54 +  local suggestion_count = 0
   12.55 +  
   12.56 +  local ms = {}
   12.57 +  
   12.58 +  for i, entry in ipairs(initiatives_for_notification) do
   12.59 +    local initiative = entry.initiative
   12.60 +    local issue = initiative.issue
   12.61 +    local area = issue.area
   12.62 +    local policy = issue.policy
   12.63 +    
   12.64 +    local m = {}
   12.65 +    if last_area_id ~= area.id then
   12.66 +      m[#m+1] = "*** " .. area.name .. " ***"
   12.67 +      m[#m+1] = ""
   12.68 +      last_area_id = area.id
   12.69 +    end
   12.70 +    if last_issue_id ~= issue.id then
   12.71 +      local state_time_text
   12.72 +      if string.sub(issue.state_time_left, 1, 1) == "-" then
   12.73 +        state_time_text = _"Phase ends soon"
   12.74 +      else
   12.75 +        state_time_text = _( "#{interval} left", {
   12.76 +          interval = format.interval_text(issue.state_time_left)
   12.77 +        })
   12.78 +      end
   12.79 +      m[#m+1] = "---"
   12.80 +      m[#m+1] = policy.name .. " #" .. issue.id .. " - " .. issue.state_name .. " - " .. state_time_text
   12.81 +      m[#m+1] = ""
   12.82 +      last_issue_id = issue.id
   12.83 +    end
   12.84 +    m[#m+1] = initiative.display_name
   12.85 +    local source
   12.86 +    if entry.supported then
   12.87 +      source = _"has my support"
   12.88 +      local draft_info
   12.89 +      if entry.new_draft then
   12.90 +        draft_info = _"draft updated"
   12.91 +        draft_count = draft_count + 1
   12.92 +      end
   12.93 +      if draft_info then
   12.94 +        source = source .. ", " .. draft_info
   12.95 +      end
   12.96 +      local info
   12.97 +      if entry.new_suggestion_count then
   12.98 +        if entry.new_suggestion_count == 1 then
   12.99 +          info = _"new suggestion added"
  12.100 +        elseif entry.new_suggestion_count > 1 then
  12.101 +          info = _("#{count} suggestions added", { count = entry.new_suggestion_count })
  12.102 +        end
  12.103 +        suggestion_count = suggestion_count + entry.new_suggestion_count
  12.104 +      end
  12.105 +      if info then
  12.106 +        source = source .. ", " .. info
  12.107 +      end
  12.108 +    elseif entry.featured then
  12.109 +      source = _"featured"
  12.110 +    end
  12.111 +    if entry.leading then
  12.112 +      source = source and source .. ", " or ""
  12.113 +      source = source .. "currently leading"
  12.114 +    end
  12.115 +    m[#m+1] = "(" .. source .. ")"
  12.116 +    m[#m+1] = ""
  12.117 +    if not initiatives_for_notification[i+1] or initiatives_for_notification[i+1].issue_id ~= issue.id then
  12.118 +      m[#m+1] = _("more: #{url}", { url = request.get_absolute_baseurl() .. "issue/show/" .. issue.id .. ".html" })
  12.119 +      m[#m+1] = ""
  12.120 +    end
  12.121 +    ms[#ms+1] = table.concat(m, "\n")
  12.122 +  end
  12.123 +
  12.124 +  local info = {}
  12.125 +    
  12.126 +  if draft_count > 0 then
  12.127 +    if draft_count == 1 then
  12.128 +      info[#info+1] = _"draft updated for " .. initiatives_for_notification[1].initiative.display_name
  12.129 +    else
  12.130 +      info[#info+1] = _("drafts of #{draft_count} initiatives updated", { draft_count = draft_count })
  12.131 +    end
  12.132 +  end
  12.133 +  
  12.134 +  if suggestion_count > 0 then
  12.135 +    if suggestion_count == 1 then
  12.136 +      info[#info+1] = _"new suggestion for " .. initiatives_for_notification[1].initiative.display_name
  12.137 +    else
  12.138 +      info[#info+1] = _("#{suggestion_count} suggestions added", { suggestion_count = suggestion_count })
  12.139 +    end
  12.140 +  end
  12.141 +  
  12.142 +  if draft_count == 0 and suggestion_count == 0 then
  12.143 +    info[#info+1] = _"featured initiatives"
  12.144 +  end
  12.145 +  
  12.146 +  local subject = _("Digest #{id}: #{info}", {
  12.147 +    id = member.notification_counter, info = table.concat(info, ", ")
  12.148 +  })
  12.149 +
  12.150 +  
  12.151 +  local template = config.notification_digest_template
  12.152 +  
  12.153 +  local message = _(template, {
  12.154 +    name = member.name,
  12.155 +    digest = table.concat(ms, "\n")
  12.156 +  })
  12.157 +  
  12.158 +  local success = net.send_mail{
  12.159 +    envelope_from = config.mail_envelope_from,
  12.160 +    from          = config.mail_from,
  12.161 +    reply_to      = config.mail_reply_to,
  12.162 +    to            = member.notify_email,
  12.163 +    subject       = subject,
  12.164 +    content_type  = "text/plain; charset=UTF-8",
  12.165 +    content       = message
  12.166 +  }
  12.167 +
  12.168 +  db:query("COMMIT")
  12.169 +  
  12.170 +end
  12.171 +
  12.172 +function InitiativeForNotification:notify_next_member()
  12.173 +  local scheduled_notification_to_send = ScheduledNotificationToSend:get_next()
  12.174 +  if not scheduled_notification_to_send then
  12.175 +    return false
  12.176 +  end
  12.177 +  InitiativeForNotification:notify_member_id(scheduled_notification_to_send.recipient_id)
  12.178 +  return true
  12.179 +end
  12.180 +
  12.181 +ScheduledNotificationToSend:get_next()
  12.182 \ No newline at end of file
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/model/newsletter.lua	Tue Apr 05 20:40:37 2016 +0200
    13.3 @@ -0,0 +1,41 @@
    13.4 +Newsletter = mondelefant.new_class()
    13.5 +Newsletter.table = 'newsletter'
    13.6 +
    13.7 +function Newsletter:send_next_newsletter()
    13.8 +  local newsletter_to_send = NewsletterToSend:get_next()
    13.9 +  if not newsletter_to_send then
   13.10 +    return false
   13.11 +  end
   13.12 +  local newsletter = newsletter_to_send.newsletter
   13.13 +  
   13.14 +  local newsletter_to_send = NewsletterToSend:by_newsletter_id(newsletter.id)
   13.15 +  newsletter_to_send:load("member")
   13.16 +  
   13.17 +  newsletter.sent = "now"
   13.18 +  newsletter:save()
   13.19 +
   13.20 +  io.stderr:write("Sending newsletter " .. newsletter.id .. " to " .. (#newsletter_to_send) .. " members\n")
   13.21 +  
   13.22 +  for i, n in ipairs(newsletter_to_send) do
   13.23 +    
   13.24 +    local member = newsletter_to_send.member
   13.25 +    
   13.26 +    if not member.notify_email then
   13.27 +      return
   13.28 +    end
   13.29 +
   13.30 +    io.stderr:write("Sending newsletter " .. newsletter.id .. " to " .. member.notify_email .. "\n")
   13.31 +    
   13.32 +    local success = net.send_mail{
   13.33 +      envelope_from = config.mail_envelope_from,
   13.34 +      from          = config.mail_from,
   13.35 +      reply_to      = config.mail_reply_to,
   13.36 +      to            = member.notify_email,
   13.37 +      subject       = newsletter.subject,
   13.38 +      content_type  = "text/plain; charset=UTF-8",
   13.39 +      content       = newsletter.content
   13.40 +    }
   13.41 +
   13.42 +  end
   13.43 +  
   13.44 +end
   13.45 \ No newline at end of file
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/model/newsletter_to_send.lua	Tue Apr 05 20:40:37 2016 +0200
    14.3 @@ -0,0 +1,34 @@
    14.4 +NewsletterToSend = mondelefant.new_class()
    14.5 +NewsletterToSend.table = 'newsletter_to_send'
    14.6 +
    14.7 +NewsletterToSend:add_reference{
    14.8 +  mode          = 'm1',
    14.9 +  to            = "Newsletter",
   14.10 +  this_key      = 'newsletter_id',
   14.11 +  that_key      = 'id',
   14.12 +  ref           = 'newsletter',
   14.13 +}
   14.14 +
   14.15 +NewsletterToSend:add_reference{
   14.16 +  mode          = 'm1',
   14.17 +  to            = "Member",
   14.18 +  this_key      = 'recipient_id',
   14.19 +  that_key      = 'id',
   14.20 +  ref           = 'member',
   14.21 +}
   14.22 +
   14.23 +function NewsletterToSend:get_next()
   14.24 +  return NewsletterToSend:new_selector()
   14.25 +    :set_distinct("newsletter_id")
   14.26 +    :add_order_by("newsletter_id")
   14.27 +    :limit(1)
   14.28 +    :optional_object_mode()
   14.29 +    :exec()
   14.30 +end
   14.31 +
   14.32 +function NewsletterToSend:by_newsletter_id(id)
   14.33 +  return NewsletterToSend:new_selector()
   14.34 +    :add_where{ "newsletter_id = ?", id }
   14.35 +    :optional_object_mode()
   14.36 +    :exec()
   14.37 +end
   14.38 \ No newline at end of file
    15.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.2 +++ b/model/scheduled_notification_to_send.lua	Tue Apr 05 20:40:37 2016 +0200
    15.3 @@ -0,0 +1,10 @@
    15.4 +ScheduledNotificationToSend = mondelefant.new_class()
    15.5 +ScheduledNotificationToSend.table = 'scheduled_notification_to_send'
    15.6 +
    15.7 +function ScheduledNotificationToSend:get_next()
    15.8 +  return self:new_selector()
    15.9 +    :add_order_by("pending DESC")
   15.10 +    :limit(1)
   15.11 +    :optional_object_mode()
   15.12 +    :exec()
   15.13 +end
   15.14 \ No newline at end of file

Impressum / About Us