From f24ef96b5e31111336817652222605c0b862debb Mon Sep 17 00:00:00 2001 From: Yann Lugrin Date: Mon, 9 Feb 2009 15:03:48 +0100 Subject: [PATCH] ActionItem910: Integration of feature "invite a friend" --- app/controllers/my_profile/friends_controller.rb | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/controllers/public/account_controller.rb | 6 ++++++ app/models/environment.rb | 15 +++++++++++++++ app/models/invite_friend.rb | 43 +++++++++++++++++++++++++++++++++++++++++++ app/models/task_mailer.rb | 13 +++++++++++++ app/views/account/_signup_form.rhtml | 2 ++ app/views/friends/index.rhtml | 1 + app/views/friends/invite.rhtml | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/views/profile/friends.rhtml | 3 +++ app/views/task_mailer/invitation_notification.rhtml | 1 + doc/README_FOR_APP.en | 1 + test/functional/friends_controller_test.rb | 28 ++++++++++++++++++++++++++++ test/functional/profile_controller_test.rb | 12 ++++++++++++ test/unit/environment_test.rb | 13 +++++++++++++ test/unit/invite_friend_test.rb | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/unit/task_mailer_test.rb | 29 +++++++++++++++++++++++++++++ 16 files changed, 454 insertions(+), 0 deletions(-) create mode 100644 app/models/invite_friend.rb create mode 100644 app/views/friends/invite.rhtml create mode 100644 app/views/task_mailer/invitation_notification.rhtml create mode 100644 test/unit/invite_friend_test.rb diff --git a/app/controllers/my_profile/friends_controller.rb b/app/controllers/my_profile/friends_controller.rb index 67b804c..2bb30cc 100644 --- a/app/controllers/my_profile/friends_controller.rb +++ b/app/controllers/my_profile/friends_controller.rb @@ -1,3 +1,5 @@ +require "contacts" + class FriendsController < MyProfileController protect 'manage_friends', :profile @@ -26,4 +28,69 @@ class FriendsController < MyProfileController end end + def invite + + if request.post? && params[:import] + begin + case params[:import_from] + when "gmail" + @friends = Contacts::Gmail.new(params[:login], params[:password]).contacts + when "yahoo" + @friends = Contacts::Yahoo.new(params[:login], params[:password]).contacts + when "hotmail" + @friends = Contacts::Hotmail.new(params[:login], params[:password]).contacts + else + @friends = [] + end + @friends.map! {|friend| friend + ["#{friend[0]} <#{friend[1]}>"]} + rescue + @login = params[:login] + flash.now[:notice] = __('There was an error while looking for your contact list. Did you enter correct login and password?') + end + + elsif request.post? && params[:confirmation] + friends_to_invite = [] + friends_to_invite += (params[:manual_import_addresses].is_a?(Array) ? params[:manual_import_addresses] : params[:manual_import_addresses].split("\r\n")) if params[:manual_import_addresses] + friends_to_invite += (params[:webmail_import_addresses].is_a?(Array) ? params[:webmail_import_addresses] : params[:webmail_import_addresses].split("\r\n")) if params[:webmail_import_addresses] + + if !params[:message].match(//) + flash.now[:notice] = __('<url> is needed in invitation message.') + elsif !friends_to_invite.empty? + friends_to_invite.each do |friend_to_invite| + next if friend_to_invite == __("Firstname Lastname ") + + friend_to_invite.strip! + if match = friend_to_invite.match(/(.*)<(.*)>/) and match[2].match(Noosfero::Constants::EMAIL_FORMAT) + friend_name = match[1].strip + friend_email = match[2] + elsif match = friend_to_invite.strip.match(Noosfero::Constants::EMAIL_FORMAT) + friend_name = "" + friend_email = match[0] + else + next + end + + friend = User.find_by_email(friend_email) + if !friend.nil? && friend.person.person? + InviteFriend.create(:person => profile, :friend => friend.person) + else + InviteFriend.create(:person => profile, :friend_name => friend_name, :friend_email => friend_email, :message => params[:message]) + end + end + + flash[:notice] = __('Your invitations have been sent.') + redirect_to :action => 'index' + else + flash.now[:notice] = __('Please enter a valid email address.') + end + + @friends = params[:webmail_friends] ? params[:webmail_friends].map {|e| YAML.load(e)} : [] + @manual_import_addresses = params[:manual_import_addresses] || "" + @webmail_import_addresses = params[:webmail_import_addresses] || [] + end + + @import_from = params[:import_from] || "manual" + @message = params[:message] || environment.message_for_friend_invitation + end + end diff --git a/app/controllers/public/account_controller.rb b/app/controllers/public/account_controller.rb index 9691667..2f4a0f8 100644 --- a/app/controllers/public/account_controller.rb +++ b/app/controllers/public/account_controller.rb @@ -42,6 +42,7 @@ class AccountController < ApplicationController # action to register an user to the application def signup + @invitation_code = params[:invitation_code] begin @user = User.new(params[:user]) @user.terms_of_use = environment.terms_of_use @@ -54,6 +55,11 @@ class AccountController < ApplicationController self.current_user = @user owner_role = Role.find_by_name('owner') @user.person.affiliate(@user.person, [owner_role]) if owner_role + invitation = Task.find_by_code(@invitation_code) + if invitation + invitation.update_attributes!({:friend => @user.person}) + invitation.finish + end go_to_user_initial_page if redirect? flash[:notice] = _("Thanks for signing up!") end diff --git a/app/models/environment.rb b/app/models/environment.rb index 14378f4..73d22b1 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -280,6 +280,21 @@ class Environment < ActiveRecord::Base signup_fields end + # Default message send to friend when user use invite a friend feature + def message_for_friend_invitation + self.settings['message_for_friend_invitation'] || [ + _('Hello ,'), + _(' is inviting you to participate on %{environment}.') % { :environment => self.name }, + _('To accept the invitation, please follow this link:') + "\n" + '', + "--\n#{self.name}", + '' + ].join("\n\n") + end + + def message_for_friend_invitation=(value) + self.settings['message_for_friend_invitation'] = value + end + def custom_enterprise_fields self.settings[:custom_enterprise_fields].nil? ? {} : self.settings[:custom_enterprise_fields] end diff --git a/app/models/invite_friend.rb b/app/models/invite_friend.rb new file mode 100644 index 0000000..0dda893 --- /dev/null +++ b/app/models/invite_friend.rb @@ -0,0 +1,43 @@ +class InviteFriend < Task + + acts_as_having_settings :group_for_person, :group_for_friend, :message, :friend_name, :friend_email, :field => :data + + validates_presence_of :requestor_id + + validates_presence_of :target_id, :if => Proc.new{|invite| invite.friend_email.blank? } + + validates_presence_of :friend_email, :if => Proc.new{|invite| invite.target_id.blank? } + validates_format_of :friend_email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => Proc.new{|invite| invite.target_id.blank? } + + validates_presence_of :message, :if => Proc.new{|invite| invite.target_id.blank? } + validates_format_of :message, :with => //, :if => Proc.new{|invite| invite.target_id.blank? } + + alias :person :requestor + alias :person= :requestor= + + alias :friend :target + alias :friend= :target= + + after_create do |task| + TaskMailer.deliver_invitation_notification(task) unless task.friend + end + + def perform + requestor.add_friend(target, group_for_person) + target.add_friend(requestor, group_for_friend) + end + + # Returns false. Adding friends by itself does not trigger e-mail + # sending. + def sends_email? + false + end + + def description + _('%s wants to be your friend') % [requestor.name] + end + + def permission + :manage_friends + end +end diff --git a/app/models/task_mailer.rb b/app/models/task_mailer.rb index 6e6bcc5..7d26e5e 100644 --- a/app/models/task_mailer.rb +++ b/app/models/task_mailer.rb @@ -24,6 +24,19 @@ class TaskMailer < ActionMailer::Base :tasks_url => url_for(task.target.url.merge(:controller => 'tasks', :action => 'index')) end + def invitation_notification(task) + msg = task.message + msg = msg.gsub(//, task.requestor.name) + msg = msg.gsub(//, task.friend_name) + msg = msg.gsub(//, url_for(:host => task.requestor.environment.default_hostname, :controller => 'account', :action => 'signup', :invitation_code => task.code)) + + recipients task.friend_email + + from self.class.generate_from(task) + subject '[%s] %s' % [ task.requestor.environment.name, task.description ] + body :message => msg + end + protected def extract_message(message) diff --git a/app/views/account/_signup_form.rhtml b/app/views/account/_signup_form.rhtml index 9d7fae8..54eca21 100644 --- a/app/views/account/_signup_form.rhtml +++ b/app/views/account/_signup_form.rhtml @@ -14,6 +14,8 @@ in this environment.') % [environment.name, __('communities'), __('enterprises') :html => { :help=>_('Fill all this fields to join in this environment.

If you forgot your password, do not create a new account, click on the "I forgot my password!" link. ;-)'), :id => 'profile-data' } do |f| -%> +<%= hidden_field_tag :invitation_code, @invitation_code %> + <%= required_fields_message %> <%= required f.text_field(:login, diff --git a/app/views/friends/index.rhtml b/app/views/friends/index.rhtml index d0439cd..e595152 100644 --- a/app/views/friends/index.rhtml +++ b/app/views/friends/index.rhtml @@ -37,6 +37,7 @@ <% button_bar do %> <%= button(:back, _('Go back'), :controller => 'profile_editor') %> <%= button(:search, _('Find people'), :controller => 'search', :action => 'assets', :asset => 'people') %> + <%= button(:search, _('Invite friends from my e-mail contacts'), :action => 'invite') %> <% end %> diff --git a/app/views/friends/invite.rhtml b/app/views/friends/invite.rhtml new file mode 100644 index 0000000..fa55e94 --- /dev/null +++ b/app/views/friends/invite.rhtml @@ -0,0 +1,83 @@ +

<%= __('Invite your friends') %>

+ + +<% unless @friends %> + +

<%= __('Step 1 of 1: Accessing your contact list') %>

+ +

+ <%= __('Choose your webmail provider and enter your login and password so we can fetch your contact list. If you only want to invite specific people by listing their e-mail addresses, select the "Enter e-mail addresses manually" option.') %> +

+ + <% form_tag do %> + <%= hidden_field_tag(:import, 1) %> + + <%= labelled_form_field(_('Select your e-mail provider:'), [ + radio_button_tag(:import_from, "gmail", @import_from == "gmail", :onclick => 'show_invite_friend_login_password()') + content_tag('label', 'Gmail', :for => 'import_from_gmail'), + radio_button_tag(:import_from, "yahoo", @import_from == "yahoo", :onclick => 'show_invite_friend_login_password()') + content_tag('label', 'Yahoo', :for => "import_from_yahoo"), + radio_button_tag(:import_from, "hotmail", @import_from == "hotmail", :onclick => 'show_invite_friend_login_password()') + content_tag('label', 'Hotmail', :for => "import_from_hotmail"), + radio_button_tag(:import_from, "manual", @import_from == "manual", :onclick => 'hide_invite_friend_login_password()') + content_tag('label', __('Enter e-mail addresses manually'), :for => "import_from_manual") + ].join("\n
\n")) %> + + +
> + <%= labelled_form_field(__("Login:"), text_field_tag(:login, @login)) %> + <%= labelled_form_field(__("Password:"), password_field_tag(:password)) %> +
+ + <% button_bar do %> + <%= submit_button(:forward, __("Next")) %> + <% end %> +

<%= __("We won't store your password or contact anyone without your permission.")%>

+ + <% end %> + +<% else %> + +

<%= __('Step 2 of 2: Selecting Friends') %>

+

+ <%= __('Indicate which friends you want to invite.') %> +

+ + <% form_tag do %> + <%= hidden_field_tag(:confirmation, 1) %> + <%= hidden_field_tag(:import_from, @import_from) %> + +
+ <%= __("Enter one e-mail address per line, following the example below.")%> + <%= labelled_form_field(__('E-mail addresses'), text_area_tag(:manual_import_addresses, (@manual_import_addresses || __("Firstname Lastname ")), :cols => 72, :rows => 5)) %> +
+ <% if @import_from != 'manual' %> +
+ <%= link_to_function __('Check all'), "$$('input.friend_to_invite').each(function(checkbox) { checkbox.checked = true; });" %> + <%= link_to_function __('Uncheck all'), "$$('input.friend_to_invite').each(function(checkbox) { checkbox.checked = false; });" %> + <% friend_pos = 0 %> + <% @friends.each do |friend| %> + <% friend_pos += 1 %> +

+ <%= hidden_field_tag("webmail_friends[]", friend.to_yaml) %> + <%= check_box_tag("webmail_import_addresses[]", friend[2], (!@webmail_import_addresses || @webmail_import_addresses.include?(friend[2])), :id => "friends_to_invite_#{friend_pos}", :class => "friend_to_invite" ) %> +

+ <% end %> +
+ <% end -%> + +
+ <%= h __("Now enter an invitation message. You must keep the code in your invitation message. When your friends receive the invitation e-mail, will be replaced by a link that they need to click to activate their account. and codes will be replaced by your name and friend name, but they are optional.") %> + <%= labelled_form_field(__('Invitation message'), text_area_tag(:message, @message, :cols => 72, :rows => 8)) %> +
+ + <% button_bar do %> + <%= submit_button(:ok, __("Invite my friends!")) %> + <% end %> + <% end %> + +<% end %> diff --git a/app/views/profile/friends.rhtml b/app/views/profile/friends.rhtml index 00bb1e0..ab10b05 100644 --- a/app/views/profile/friends.rhtml +++ b/app/views/profile/friends.rhtml @@ -10,6 +10,9 @@ <% button_bar do %> + <% if user == profile %> + <%= button :edit, _('Manage my friends'), :controller => 'friends', :action => 'index', :profile => profile.identifier %> + <% end %> <%= button :back, _('Go back'), { :controller => 'profile' }, :help => _('Back to the page where you come from.') %> <% end %> diff --git a/app/views/task_mailer/invitation_notification.rhtml b/app/views/task_mailer/invitation_notification.rhtml new file mode 100644 index 0000000..17a3d7c --- /dev/null +++ b/app/views/task_mailer/invitation_notification.rhtml @@ -0,0 +1 @@ +<%= @message %> \ No newline at end of file diff --git a/doc/README_FOR_APP.en b/doc/README_FOR_APP.en index bf8bbc8..b3f920e 100644 --- a/doc/README_FOR_APP.en +++ b/doc/README_FOR_APP.en @@ -20,6 +20,7 @@ You need to have a Subversion client (svn) installed, as well as: * RedCloth: http://whytheluckystiff.net/ruby/redcloth/ * Ruby Locale: http://rubyforge.org/projects/locale/ * will_paginate: http://github.com/mislav/will_paginate/wikis +* contacts: http://github.com/cardmagic/contacts/tree/master There are Debian packages available for all of them but ferret. Try: diff --git a/test/functional/friends_controller_test.rb b/test/functional/friends_controller_test.rb index 65d39de..b64101a 100644 --- a/test/functional/friends_controller_test.rb +++ b/test/functional/friends_controller_test.rb @@ -73,4 +73,32 @@ class FriendsControllerTest < Test::Unit::TestCase get :index, :profile => 'testuser' assert_tag :tag => 'a', :content => 'Find people', :attributes => { :href => '/assets/people' } end + + should 'display invitation page' do + get :invite + assert_response :success + assert_template 'invite' + end + + should 'actualy add invite' do + assert_difference InviteFriend, :count, 1 do + post :invite, :manual_import_addresses => "Test Name ", :import_from => "manual", :message => "click: ", :confirmation => 1 + assert_redirected_to :action => 'index' + end + + assert_difference InviteFriend, :count, 1 do + post :invite, :manual_import_addresses => "test@test.com", :import_from => "manual", :message => "click: ", :confirmation => 1 + assert_redirected_to :action => 'index' + end + + assert_difference InviteFriend, :count, 1 do + post :invite, :manual_import_addresses => "test@test.cz.com", :import_from => "manual", :message => "click: ", :confirmation => 1 + assert_redirected_to :action => 'index' + end + + assert_difference InviteFriend, :count, 1 do + post :invite, :manual_import_addresses => "#{friend.name} <#{friend.email}>", :import_from => "manual", :message => "click: ", :confirmation => 1 + assert_redirected_to :action => 'index' + end + end end diff --git a/test/functional/profile_controller_test.rb b/test/functional/profile_controller_test.rb index 6811be1..c3a6b4c 100644 --- a/test/functional/profile_controller_test.rb +++ b/test/functional/profile_controller_test.rb @@ -31,6 +31,18 @@ class ProfileControllerTest < Test::Unit::TestCase assert_kind_of Array, assigns(:friends) end + should 'point to manage friends in user is seeing his own friends' do + login_as('testuser') + get :friends + assert_tag :tag => 'a', :attributes => { :href => '/myprofile/testuser/friends' } + end + + should 'not point to manage friends of other users' do + login_as('ze') + get :friends + assert_no_tag :tag => 'a', :attributes => { :href => '/myprofile/testuser/friends' } + end + should 'list communities' do get :communities diff --git a/test/unit/environment_test.rb b/test/unit/environment_test.rb index 8505e27..32f8072 100644 --- a/test/unit/environment_test.rb +++ b/test/unit/environment_test.rb @@ -584,6 +584,19 @@ class EnvironmentTest < Test::Unit::TestCase assert_equal ['birth_date'], env.required_person_fields end + should 'provide a default invitation message' do + env = Environment.create!(:name => 'test environment') + message = [ + 'Hello ,', + " is inviting you to participate on #{env.name}.", + 'To accept the invitation, please follow this link:' + "\n" + '', + "--\n#{env.name}", + '' + ].join("\n\n") + + assert_equal message, env.message_for_friend_invitation + end + should 'set custom_enterprises_fields' do env = Environment.new env.custom_enterprise_fields = {'contact_person' => {'required' => 'true', 'active' => 'true'},'contact_email'=> {'required' => 'true', 'active' => 'true'}} diff --git a/test/unit/invite_friend_test.rb b/test/unit/invite_friend_test.rb new file mode 100644 index 0000000..5988e72 --- /dev/null +++ b/test/unit/invite_friend_test.rb @@ -0,0 +1,137 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class InviteFriendTest < ActiveSupport::TestCase + + should 'be a task' do + ok { InviteFriend.new.kind_of?(Task) } + end + + should 'actually create friendships (two way) when confirmed' do + p1 = create_user('testuser1').person + p2 = create_user('testuser2').person + + task = InviteFriend.create!(:person => p1, :friend => p2) + + assert_difference Friendship, :count, 2 do + task.finish + end + + ok('p1 should have p2 as friend') { p1.friends.include?(p2) } + ok('p2 should have p1 as friend') { p2.friends.include?(p1) } + end + + should 'require requestor (person inviting other as friend)' do + task = InviteFriend.new + task.valid? + + ok('must not validate with empty requestor') { task.errors.invalid?(:requestor_id) } + + task.requestor = create_user('testuser2').person + task.valid? + ok('must validate when requestor is given') { !task.errors.invalid?(:requestor_id)} + end + + should 'require friend email if no target given (person being invited)' do + task = InviteFriend.new + task.valid? + + ok('must not validate with empty target email') { task.errors.invalid?(:friend_email) } + + task.friend_email = 'test@test.com' + task.valid? + ok('must validate when target email is given') { !task.errors.invalid?(:friend_email)} + end + + should 'dont require friend email if target given (person being invited)' do + task = InviteFriend.new(:target => create_user('testuser2').person) + task.valid? + + ok('must validate with empty target email') { !task.errors.invalid?(:friend_email) } + end + + should 'require target (person being invited) if no friend email given' do + task = InviteFriend.new + task.valid? + + ok('must not validate with no target') { task.errors.invalid?(:target_id) } + + task.target = create_user('testuser2').person + task.valid? + ok('must validate when target is given') { !task.errors.invalid?(:target_id)} + end + + should 'dont require target (person being invited) if friend email given' do + task = InviteFriend.new(:friend_email => "test@test.com") + task.valid? + + ok('must validate with no target') { !task.errors.invalid?(:target_id) } + end + + should 'require message with tag if no target given' do + task = InviteFriend.new + task.valid? + + ok('must not validate with no message') { task.errors.invalid?(:message) } + + task.message = 'a simple message' + task.valid? + ok('must not validate with no tag in message') { task.errors.invalid?(:message) } + + task.message = 'a simple message with ' + task.valid? + ok('must validate when message is given with tag') { !task.errors.invalid?(:message)} + end + + should 'dont require message if target given (person being invited)' do + task = InviteFriend.new(:target => create_user('testuser2').person) + task.valid? + + ok('must validate with no target') { !task.errors.invalid?(:message) } + end + + should 'not send e-mails to requestor' do + p1 = create_user('testuser1').person + p2 = create_user('testuser2').person + + TaskMailer.expects(:deliver_task_finished).never + TaskMailer.expects(:deliver_task_created).never + + task = InviteFriend.create!(:person => p1, :friend => p2) + task.finish + end + + should 'send e-mails to friend if friend_email given' do + p1 = create_user('testuser1').person + + TaskMailer.expects(:deliver_invitation_notification).once + + task = InviteFriend.create!(:person => p1, :friend_email => 'test@test.com', :message => '') + end + + should 'not send e-mails to friend if target given (person being invited)' do + p1 = create_user('testuser1').person + p2 = create_user('testuser2').person + + TaskMailer.expects(:deliver_invitation_notification).never + + task = InviteFriend.create!(:person => p1, :friend => p2) + end + + should 'provide proper description' do + p1 = create_user('testuser1').person + p2 = create_user('testuser2').person + + TaskMailer.expects(:deliver_task_finished).never + TaskMailer.expects(:deliver_task_created).never + + task = InviteFriend.create!(:person => p1, :friend => p2) + + assert_equal 'testuser1 wants to be your friend', task.description + end + + should 'has permission to manage friends' do + t = InviteFriend.new + assert_equal :manage_friends, t.permission + end + +end diff --git a/test/unit/task_mailer_test.rb b/test/unit/task_mailer_test.rb index ec24d23..e2fb58e 100644 --- a/test/unit/task_mailer_test.rb +++ b/test/unit/task_mailer_test.rb @@ -105,6 +105,35 @@ class TaskMailerTest < Test::Unit::TestCase assert !ActionMailer::Base.deliveries.empty? end + should 'be able to send a "invitatiom notification" message' do + + task = InviteFriend.new + task.expects(:description).returns('the task') + task.expects(:code).returns('123456') + + task.expects(:message).returns('Hello , invite you, please follow this link: ') + task.expects(:friend_email).returns('friend@exemple.com') + task.expects(:friend_name).returns('friend name') + + requestor = mock() + requestor.expects(:name).returns('my name') + + environment = mock() + environment.expects(:contact_email).returns('sender@example.com') + environment.expects(:default_hostname).returns('example.com') + environment.expects(:name).returns('example').at_least_once + + task.expects(:requestor).returns(requestor).at_least_once + requestor.expects(:environment).returns(environment).at_least_once + + mail = TaskMailer.create_invitation_notification(task) + + assert_equal "Hello friend name, my name invite you, please follow this link: http://example.com/account/signup?invitation_code=123456", mail.body + + TaskMailer.deliver(mail) + assert !ActionMailer::Base.deliveries.empty? + end + should 'use environment name and contact email' do task = mock requestor = mock -- libgit2 0.21.2