<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>app/views/notification/admin_mail.text.plain.erb</filename>
    </added>
    <added>
      <filename>app/views/notification/conference_invitation.text.plain.erb</filename>
    </added>
    <added>
      <filename>app/views/people/invite.html.erb</filename>
    </added>
    <added>
      <filename>app/views/people_adm/mass_mail.html.erb</filename>
    </added>
    <added>
      <filename>db/migrate/011_email_opt_in_fields.rb</filename>
    </added>
    <added>
      <filename>lib/rfc822.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -2,8 +2,11 @@ class PeopleAdmController &lt; Admin
   before_filter :get_person, :only =&gt; [:show, :destroy]
 
   Menu = [[_('Registered people list'), :list],
-          [_('Administrative tasks'), :by_task]]
+          [_('Administrative tasks'), :by_task],
+          [_('Massive mailing'), :mass_mail]]
 
+  ############################################################
+  # Generic CRUD functions for people
   def index
     redirect_to :action =&gt; 'list'
   end
@@ -17,10 +20,6 @@ class PeopleAdmController &lt; Admin
                                 :page =&gt; params[:page])
   end
 
-  def by_task
-    @tasks = AdminTask.find(:all, :include =&gt; :people)
-  end
-
   def new
     @person = Person.new
   end
@@ -45,7 +44,7 @@ class PeopleAdmController &lt; Admin
         # case one of the associations fail, the request continues to
         # be carried out (and chicken out only if it is on an
         # unforseen situation)
-        conference_ids = params[:person].delete(:conference_ids)
+        conference_ids = params[:person].delete(:conference_ids) || []
 
         @person.update_attributes(params[:person])
 
@@ -101,6 +100,39 @@ class PeopleAdmController &lt; Admin
     redirect_to :action =&gt; 'list'
   end
 
+  ############################################################
+  # List of all people with administrative privileges
+  def by_task
+    @tasks = AdminTask.find(:all, :include =&gt; :people)
+  end
+
+  ############################################################
+  # Mass-mailing
+  def mass_mail
+    @recipients = Person.mailable
+    return true unless request.post?
+    if params[:title].nil? or params[:title].empty? or 
+        params[:body].nil? or params[:body].empty?
+      flash[:error] &lt;&lt; _('You must specify both email title and body')
+      return false
+    end
+
+    @recipients.each do |rcpt|
+      begin
+        Notification.deliver_admin_mail(@user, rcpt, 
+                                        params[:title], params[:body])
+      rescue Notification::InvalidEmail
+        flash[:warning] &lt;&lt; _('User %s (ID: %s) is registered with an ' +
+                              'invalid email address (%s). Skipping...') %
+          [rcpt.name, rcpt.id, rcpt.email]
+      end
+    end
+
+    flash[:notice] &lt;&lt; _('The %d requested mails have been sent.') % 
+      @recipients.size
+    redirect_to :action =&gt; 'list'
+  end
+
   private
   def get_person
     return true if params[:id] and @person = Person.find_by_id(params[:id])</diff>
      <filename>app/controllers/people_adm_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -169,6 +169,28 @@ class PeopleController &lt; ApplicationController
     end
   end
 
+  # Invite a friend (to a specific conference)
+  def invite
+    @my_confs = @user.upcoming_conferences
+    return true unless request.post?
+
+    begin
+      conf = Conference.find_by_id(params[:dest_conf_id])
+      Notification.deliver_conference_invitation(@user, params[:email], 
+                                                 conf, params[:body])
+
+      flash[:notice] &lt;&lt; _('The requested e-mail was successfully sent')
+      redirect_to :action =&gt; 'account'
+    rescue ActiveRecord::RecordNotFound
+      flash[:error] &lt;&lt; _('Invalid conference requested')
+    rescue Notification::MustSupplyBody
+      flash[:error] &lt;&lt; _('You must supply a body for your mail')
+    rescue Notification::InvalidEmail
+      flash[:error] &lt;&lt; _('The specified e-mail address (%s) is not valid') %
+        params[:email]
+    end
+  end
+
   ############################################################
   # Internal use...
   protected</diff>
      <filename>app/controllers/people_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -116,6 +116,18 @@ module ApplicationHelper
   end
 
   ############################################################
+  # From rows (for regular layout, whether we use ComasFormBuilder or not)
+  def form_row(title, input, note=nil)
+    res = ['&lt;div class=&quot;form-row&quot;&gt;',
+           '&lt;span class=&quot;comas-form-prompt&quot;&gt;%s&lt;/span&gt;' % title]
+    res &lt;&lt; '&lt;span class=&quot;comas-form-note&quot;&gt;%s&lt;/span&gt;' % note if note
+    res &lt;&lt; '&lt;span class=&quot;comas-form-input&quot;&gt;%s&lt;/span&gt;' % input
+    res &lt;&lt; '&lt;/div&gt;'
+
+    res.join(&quot;\n&quot;)
+  end
+
+  ############################################################
   # Show a translation-friendly pagination header (similar to WillPaginate's
   # page_entries_info - in fact, derived from it)
   def pagination_header(collection)
@@ -278,19 +290,15 @@ module ApplicationHelper
 
     private
     def with_format(title, body, note=nil)
-      [before_elem(title, note), body, after_elem].join(&quot;\n&quot;)
-    end
-
-    def before_elem(title, note=nil)
-      ['&lt;div class=&quot;form-row&quot;&gt;',
-       %Q(&lt;span class=&quot;comas-form-prompt&quot;&gt;#{_ title}&lt;/span&gt;),
-       (note ? %Q(&lt;span class=&quot;comas-form-note&quot;&gt;#{_ note}&lt;/span&gt;) : ''),
-       '&lt;span class=&quot;comas-form-input&quot;&gt;'
-      ].join(&quot;\n&quot;)
-    end
-
-    def after_elem
-      '&lt;/span&gt;&lt;/div&gt;'
+      # Ugh... Straight copied from form_row above. How to call
+      # this function from this very same module!?
+      res = ['&lt;div class=&quot;form-row&quot;&gt;',
+             '&lt;span class=&quot;comas-form-prompt&quot;&gt;%s&lt;/span&gt;' % _(title)]
+      res &lt;&lt; '&lt;span class=&quot;comas-form-note&quot;&gt;%s&lt;/span&gt;' % _(note) if note
+      res &lt;&lt; '&lt;span class=&quot;comas-form-input&quot;&gt;%s&lt;/span&gt;' % body
+      res &lt;&lt; '&lt;/div&gt;'
+      
+      res.join(&quot;\n&quot;)
     end
 
     def info_elem(info)</diff>
      <filename>app/helpers/application_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -149,6 +149,12 @@ class Conference &lt; ActiveRecord::Base
     self.proposals.select {|p| p.people.include? person}
   end
 
+  # List of people who are registered for this conference and who have
+  # accepted the &quot;ok_conf_mails&quot; boolean
+  def people_for_mailing
+    self.people.select {|p| p.ok_conf_mails?}
+  end
+
   private
   # Verify the submitted dates are coherent (i.e. none of the periods
   # we care about finishes before it begins)</diff>
      <filename>app/models/conference.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,6 @@
 class Notification &lt; ActionMailer::Base
+  class InvalidEmail &lt; Exception; end
+  class MustSupplyBody &lt; Exception; end
   def welcome(person)
     sys_name = SysConf.value_for('title_text')
     recipients person.name_and_email
@@ -47,4 +49,46 @@ class Notification &lt; ActionMailer::Base
                                :controller =&gt; 'people',
                                :action =&gt; 'login')
   end
+
+  def conference_invitation(sender, dest, conference, invitation_text)
+    dest =~ RFC822::EmailAddress or raise InvalidEmail
+    raise MustSupplyBody if invitation_text.nil? or invitation_text.empty?
+
+    recipients dest
+    from SysConf.value_for('mail_from')
+    subject _('Invitation to %s, sent by %s') % [conference.name, sender.name]
+    body :conference =&gt; conference.name,
+         :sender_name =&gt; sender.name,
+         :sender_email =&gt; sender.email,
+         :conference_url =&gt; url_for(:only_path =&gt; false,
+                                    :controller =&gt; 'conferences',
+                                    :action =&gt; 'show',
+                                    :id =&gt; conference),
+         :invitation_text =&gt; invitation_text
+  end
+
+  def admin_mail(sender, dest, title, mail_body)
+    dest.email =~ RFC822::EmailAddress or raise InvalidEmail
+    raise MustSupplyBody if mail_body.nil? or mail_body.empty?
+
+    cms_title = SysConf.value_for('title_text')
+
+    recipients dest.name_and_email
+    from cms_mail_from
+    subject '[cms_title] title' % [cms_title, title]
+    body :comas_title =&gt; cms_title,
+         :mail_title =&gt; title,
+         :mail_body =&gt; mail_body,
+         :admin_name =&gt; sender.name,
+         :admin_mail =&gt; sender.email,
+         :login_url =&gt; url_for(:only_path =&gt; false,
+                                    :controller =&gt; 'people',
+                                    :action =&gt; 'login')
+  end
+
+  private
+  def cms_mail_from
+    '%s &lt;%s&gt;' % [SysConf.value_for('title_text'), 
+                 SysConf.value_for('mail_from')]
+  end
 end</diff>
      <filename>app/models/notification.rb</filename>
    </modified>
    <modified>
      <diff>@@ -13,7 +13,11 @@ class Person &lt; ActiveRecord::Base
   validates_presence_of :famname
   validates_presence_of :login
   validates_presence_of :passwd
+  validates_presence_of :email
   validates_uniqueness_of :login
+  validates_format_of(:email,
+                      :with =&gt; RFC822::EmailAddress,
+                      :message =&gt; _('A valid e-mail address is required'))
 
   def self.ck_login(given_login, given_passwd)
     person = Person.find_by_login(given_login)
@@ -45,13 +49,27 @@ class Person &lt; ActiveRecord::Base
     extra_listable_attributes.select {|a| a.name =~ /^pub_/}
   end
 
+  # List of users which accepted to receive general information mails
+  def self.mailable
+    self.find(:all, :conditions =&gt; 'ok_general_mails')
+  end
+
+  # Performs a (paginated) search. If no parameters are specified, it
+  # just returns the first page of a paginated full listing, ordered
+  # by ID. A string can be specified as a firstname - it will be
+  # searched in firstname, famname and login. Any additional
+  # parameters for the search can be specified in the second parameter
+  # (as a hash).
   def self.pag_search(name=nil, params={})
     params.merge!(:conditions=&gt;['firstname ~* ? or famname ~* ? or login ~* ?',
                                 name, name, name]) unless name.nil?
+    params[:order] = 'id' if params[:order].nil?
     params[:page] = 1 if params[:page].nil?
     self.paginate(params)
   end
 
+  # Sets the encrypted password - Regenerates the random salt and
+  # computes a MD5 for the supplied plaintext password.
   def passwd= plain
     # Don't accept empty passwords!
     return nil if plain.blank? or /^\s*$/.match(plain)</diff>
      <filename>app/models/person.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,5 @@
 &lt;% # Ordered list of fields we will present to the user 
-   fields = %w(login passwd firstname famname admin_tasks conferences 
+   fields = %w(login passwd firstname famname email admin_tasks conferences 
      ).concat @person.extra_listable_attributes.map {|fld| fld.name} %&gt;
 &lt;% comas_form_for(:person, @person, 
                   :url =&gt; {:action =&gt; action, :id =&gt; @person}) do |f| %&gt;</diff>
      <filename>app/views/people_adm/_form.html.erb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>6c16bd21771de50f581a50c877c6ce0d9c20169c</id>
    </parent>
    <parent>
      <id>701744d998fa5c4a62bb18696d6521149882e0c9</id>
    </parent>
  </parents>
  <author>
    <name>Gunnar Wolf</name>
    <email>gwolf@gwolf.org</email>
  </author>
  <url>http://github.com/gwolf/comas/commit/067dc555f94d6a491686a4fac03f955f66ccb030</url>
  <id>067dc555f94d6a491686a4fac03f955f66ccb030</id>
  <committed-date>2009-02-24T11:14:06-08:00</committed-date>
  <authored-date>2009-02-24T11:14:06-08:00</authored-date>
  <message>Merge branch 'people_adm_mailing'</message>
  <tree>08d92d2e70e33a417c500c47aea74f6569626348</tree>
  <committer>
    <name>Gunnar Wolf</name>
    <email>gwolf@gwolf.org</email>
  </committer>
</commit>
