Commit 2cdcd813d955c0e5f5d50d0e5acd907a3b087979

Authored by Evandro Junior
2 parents 3a14efd3 9db1b539

Merge branch 'production' of gitlab.com:participa/noosfero into production

* 'production' of gitlab.com:participa/noosfero: (173 commits)
  update submodule
  update submodule
  add rubygems source
  fixed tasks_controller_test
  added random as a possible value for make_order_with_parameters
  added sql injection protection to method make_order_with_parameters
  HOTFIX: removing wrong comment
  HOTFIX: fix tests
  fix new content url in not found content
  submodule: updating submodule
  Adapting views and helpers to datime format
  Remove admins.map that generate a SQL query for each user causing performance problems.
  Translated using Weblate (Armenian)
  scheduler: scope into Noosfero
  change event start date and ende date to datetime
  search: realign search box and search button
  Avoid unecessary user save after update person
  scrap_test: destroy actiontracker to stabilize #last
  Remove letfover selectordie files
  Update proposals discussion plugin
  ...
Showing 185 changed files with 64518 additions and 62530 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 185 files displayed.

.gitlab-ci.yml
1 1 before_script:
2 2 - mkdir -p tmp/pids log
3 3 - bundle check || bundle install
  4 +# workaround for plugins with Gemfile
  5 + - perl -pi -e 's/--local //' script/noosfero-plugins
4 6 - script/noosfero-plugins disableall
5 7 - bundle exec rake makemo &>/dev/null
6 8 # database
... ...
.gitmodules
... ... @@ -28,3 +28,9 @@
28 28 [submodule "public/proposal-app"]
29 29 path = public/proposal-app
30 30 url = https://gitlab.com/participa/proposal-app.git
  31 +[submodule "plugins/gravatar-provider"]
  32 + path = plugins/gravatar-provider
  33 + url = https://gitlab.com/noosfero-plugins/gravatar-provider.git
  34 +[submodule "plugins/juventude"]
  35 + path = plugins/juventude
  36 + url = https://gitlab.com/noosfero-plugins/juventude.git
... ...
.travis.yml
... ... @@ -2,7 +2,6 @@ language: ruby
2 2 rvm:
3 3 # for 2.2 support we need to upgrade the pg gem
4 4 - 2.1.6
5   -cache: bundler
6 5  
7 6 sudo: false
8 7 addons:
... ... @@ -19,9 +18,20 @@ addons:
19 18 - libsqlite3-dev
20 19 - libxslt1-dev
21 20  
  21 +before_install:
  22 + - gem env
  23 +
  24 +# workaround for https://github.com/travis-ci/travis-ci/issues/4536
  25 +before_install:
  26 + - export GEM_HOME=$PWD/vendor/bundle/ruby/2.1.0
  27 + - gem install bundler
  28 +cache: bundler
  29 +
22 30 before_script:
23 31 - mkdir -p tmp/pids log
24   - - bundle check || bundle install
  32 +# workaround for plugins with Gemfile
  33 + - perl -pi -e 's/cat .+ > \$gemfile/echo "source \\"https:\/\/rubygems.org\\"" > \$gemfile && cat \$source\/Gemfile >> \$gemfile/' script/noosfero-plugins
  34 + - perl -pi -e 's/--local --quiet/install --jobs=3 --retry=3/' script/noosfero-plugins
25 35 - script/noosfero-plugins disableall
26 36 - bundle exec rake makemo &>/dev/null
27 37 # database
... ...
Gemfile
... ... @@ -28,6 +28,11 @@ gem 'grape-entity'
28 28 gem 'grape_logging', :git => 'https://github.com/aceunreal/grape_logging.git', :ref => '100091b'
29 29 gem 'rack-cors'
30 30 gem 'rack-contrib'
  31 +gem 'liquid', '~> 3.0.3'
  32 +#gem 'grape-swagger-rails'
  33 +
  34 +# FIXME list here all actual dependencies (i.e. the ones in debian/control),
  35 +# with their GEM names (not the Debian package names)
31 36  
32 37 gem 'api-pagination', '~> 4.1.1'
33 38  
... ...
app/controllers/admin/environment_email_templates_controller.rb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +class EnvironmentEmailTemplatesController < EmailTemplatesController
  2 +
  3 + protect 'manage_email_templates', :environment
  4 +
  5 + protected
  6 +
  7 + def owner
  8 + environment
  9 + end
  10 +
  11 + before_filter :only => :index do
  12 + @back_to = url_for(:controller => :admin_panel)
  13 + end
  14 +
  15 +end
... ...
app/controllers/email_templates_controller.rb 0 → 100644
... ... @@ -0,0 +1,62 @@
  1 +class EmailTemplatesController < ApplicationController
  2 +
  3 + def index
  4 + @email_templates = owner.email_templates
  5 + end
  6 +
  7 + def show
  8 + @email_template = owner.email_templates.find(params[:id])
  9 +
  10 + respond_to do |format|
  11 + format.html # show.html.erb
  12 + format.json { render json: @email_template }
  13 + end
  14 + end
  15 +
  16 + def show_parsed
  17 + @email_template = owner.email_templates.find(params[:id])
  18 + template_params = {:profile => owner, :environment => environment}
  19 + render json: {:parsed_body => @email_template.parsed_body(template_params), :parsed_subject => @email_template.parsed_subject(template_params)}
  20 + end
  21 +
  22 + def new
  23 + @email_template = owner.email_templates.build(:owner => owner)
  24 + end
  25 +
  26 + def edit
  27 + @email_template = owner.email_templates.find(params[:id])
  28 + end
  29 +
  30 + def create
  31 + @email_template = owner.email_templates.build(params[:email_template])
  32 + @email_template.owner = owner
  33 +
  34 + if @email_template.save
  35 + session[:notice] = _('Email template was successfully created.')
  36 + redirect_to url_for(:action => :index)
  37 + else
  38 + render action: "new"
  39 + end
  40 + end
  41 +
  42 + def update
  43 + @email_template = owner.email_templates.find(params[:id])
  44 +
  45 + if @email_template.update_attributes(params[:email_template])
  46 + session[:notice] = _('Email template was successfully updated.')
  47 + redirect_to url_for(:action => :index)
  48 + else
  49 + render action: "edit"
  50 + end
  51 + end
  52 +
  53 + def destroy
  54 + @email_template = owner.email_templates.find(params[:id])
  55 + @email_template.destroy
  56 +
  57 + respond_to do |format|
  58 + format.html { redirect_to url_for(:action => :index)}
  59 + format.json { head :no_content }
  60 + end
  61 + end
  62 +end
... ...
app/controllers/my_profile/manage_products_controller.rb
... ... @@ -206,7 +206,8 @@ class ManageProductsController &lt; ApplicationController
206 206 end
207 207  
208 208 def certifiers_for_selection
209   - @qualifier = Qualifier.exists?(params[:id]) ? Qualifier.find(params[:id]) : nil
  209 + # updated to use hash as argument to exists? to avoid sql injection vunerabillity (http://brakemanscanner.org/docs/warning_types/sql_injection/)
  210 + @qualifier = Qualifier.exists?(:id => params[:id]) ? Qualifier.find(params[:id]) : nil
210 211 render :update do |page|
211 212 page.replace_html params[:certifier_area], :partial => 'certifiers_for_selection'
212 213 end
... ...
app/controllers/my_profile/profile_email_templates_controller.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +class ProfileEmailTemplatesController < EmailTemplatesController
  2 +
  3 + needs_profile
  4 + protect 'manage_email_templates', :profile
  5 +
  6 + protected
  7 +
  8 + def owner
  9 + profile
  10 + end
  11 +
  12 + before_filter :only => :index do
  13 + @back_to = url_for(:controller => :profile_editor)
  14 + end
  15 +
  16 +end
... ...
app/controllers/my_profile/tasks_controller.rb
1 1 class TasksController < MyProfileController
2 2  
3 3 protect [:perform_task, :view_tasks], :profile, :only => [:index, :save_tags, :search_tags]
4   - protect :perform_task, :profile, :except => [:index, :save_tags, :search_tags]
  4 + protect :perform_task, :profile, :only => [:processed, :change_responsible, :close, :new, :list_requested, :ticket_details, :search_tags]
5 5  
6 6 def index
  7 + @rejection_email_templates = profile.email_templates.find_all_by_template_type(:task_rejection)
  8 + @acceptance_email_templates = profile.email_templates.find_all_by_template_type(:task_acceptance)
  9 +
7 10 @filter_type = params[:filter_type].presence
8 11 @filter_text = params[:filter_text].presence
9 12 @filter_responsible = params[:filter_responsible]
... ... @@ -19,13 +22,17 @@ class TasksController &lt; MyProfileController
19 22  
20 23 @failed = params ? params[:failed] : {}
21 24  
22   - @responsible_candidates = profile.members.by_role(profile.roles.reject {|r| !r.has_permission?('perform_task')}) if profile.organization?
  25 + @responsible_candidates = profile.members.by_role(profile.roles.reject {|r| !r.has_permission?('perform_task') && !r.has_permission?('view_tasks')}) if profile.organization?
23 26  
24 27 @view_only = !current_person.has_permission?(:perform_task, profile)
25 28 end
26 29  
27 30 def processed
28   - @tasks = Task.to(profile).without_spam.closed.sort_by(&:created_at)
  31 + @tasks = Task.to(profile).without_spam.closed.order('tasks.created_at DESC')
  32 + @filter = params[:filter] || {}
  33 + @tasks = filter_tasks(@filter, @tasks)
  34 + @tasks = @tasks.paginate(:per_page => Task.per_page, :page => params[:page])
  35 + @task_types = Task.closed_types_for(profile)
29 36 end
30 37  
31 38 def change_responsible
... ... @@ -78,6 +85,7 @@ class TasksController &lt; MyProfileController
78 85 end
79 86  
80 87 url = { :action => 'index' }
  88 +
81 89 if failed.blank?
82 90 session[:notice] = _("All decisions were applied successfully.")
83 91 else
... ... @@ -109,9 +117,9 @@ class TasksController &lt; MyProfileController
109 117 end
110 118  
111 119 def search_tasks
112   -
113   - params[:filter_type] = params[:filter_type].blank? ? nil : params[:filter_type]
114   - result = Task.pending_all(profile,params)
  120 + filter_type = params[:filter_type].presence
  121 + filter_text = params[:filter_text].presence
  122 + result = Task.pending_all(profile,filter_type, filter_text)
115 123  
116 124 render :json => result.map { |task| {:label => task.data[:name], :value => task.data[:name]} }
117 125 end
... ... @@ -125,10 +133,9 @@ class TasksController &lt; MyProfileController
125 133  
126 134 ActsAsTaggableOn.remove_unused_tags = true
127 135  
128   - task = Task.to(profile).find_by_id params[:task_id]
129   - save = user.tag(task, with: params[:tag_list], on: :tags)
130   -
131   - if save
  136 + task = profile.tasks.find_by_id(params[:task_id])
  137 +
  138 + if task && task.update_attributes(:tag_list => params[:tag_list])
132 139 result[:success] = true
133 140 end
134 141 end
... ... @@ -149,7 +156,39 @@ class TasksController &lt; MyProfileController
149 156 result = ActsAsTaggableOn::Tag.find(:all, :conditions => ['LOWER(name) LIKE ?', "%#{arg}%"])
150 157  
151 158 render :text => prepare_to_token_input_by_label(result).to_json, :content_type => 'application/json'
  159 + end
  160 +
  161 + protected
  162 +
  163 + def filter_by_closed_date(filter, tasks)
  164 + filter[:closed_from] = Date.parse(filter[:closed_from]) unless filter[:closed_from].blank?
  165 + filter[:closed_until] = Date.parse(filter[:closed_until]) unless filter[:closed_until].blank?
  166 +
  167 + tasks = tasks.where('tasks.end_date >= ?', filter[:closed_from].beginning_of_day) unless filter[:closed_from].blank?
  168 + tasks = tasks.where('tasks.end_date <= ?', filter[:closed_until].end_of_day) unless filter[:closed_until].blank?
  169 + tasks
  170 + end
  171 +
  172 + def filter_by_creation_date(filter, tasks)
  173 + filter[:created_from] = Date.parse(filter[:created_from]) unless filter[:created_from].blank?
  174 + filter[:created_until] = Date.parse(filter[:created_until]) unless filter[:created_until].blank?
  175 +
  176 + tasks = tasks.where('tasks.created_at >= ?', filter[:created_from].beginning_of_day) unless filter[:created_from].blank?
  177 + tasks = tasks.where('tasks.created_at <= ?', filter[:created_until].end_of_day) unless filter[:created_until].blank?
  178 + tasks
  179 + end
152 180  
  181 + def filter_tasks(filter, tasks)
  182 + tasks = tasks.includes(:requestor, :closed_by)
  183 + tasks = tasks.of(filter[:type].presence)
  184 + tasks = tasks.where(:status => filter[:status]) unless filter[:status].blank?
  185 + tasks = filter_by_creation_date(filter, tasks)
  186 + tasks = filter_by_closed_date(filter, tasks)
  187 +
  188 + tasks = tasks.like('profiles.name', filter[:requestor]) unless filter[:requestor].blank?
  189 + tasks = tasks.like('closed_bies_tasks.name', filter[:closed_by]) unless filter[:closed_by].blank?
  190 + tasks = tasks.like('tasks.data', filter[:text]) unless filter[:text].blank?
  191 + tasks
153 192 end
154 193  
155 194 end
... ...
app/controllers/public/contact_controller.rb
... ... @@ -6,8 +6,9 @@ class ContactController &lt; PublicController
6 6 def new
7 7 @contact = build_contact
8 8 if request.post? && params[:confirm] == 'true'
9   - @contact.city = (!params[:city].blank? && City.exists?(params[:city])) ? City.find(params[:city]).name : nil
10   - @contact.state = (!params[:state].blank? && State.exists?(params[:state])) ? State.find(params[:state]).name : nil
  9 + # updated to use hash as argument to exists? to avoid sql injection vunerabillity (http://brakemanscanner.org/docs/warning_types/sql_injection/)
  10 + @contact.city = (!params[:city].blank? && City.exists?(:id => params[:city])) ? City.find(params[:city]).name : nil
  11 + @contact.state = (!params[:state].blank? && State.exists?(:id => params[:state])) ? State.find(params[:state]).name : nil
11 12 if @contact.deliver
12 13 session[:notice] = _('Contact successfully sent')
13 14 redirect_to :action => 'new'
... ...
app/controllers/public/profile_controller.rb
... ... @@ -356,6 +356,7 @@ class ProfileController &lt; PublicController
356 356  
357 357 def send_mail
358 358 @mailing = profile.mailings.build(params[:mailing])
  359 + @email_templates = profile.email_templates.find_all_by_template_type(:organization_members)
359 360 if request.post?
360 361 @mailing.locale = locale
361 362 @mailing.person = user
... ...
app/controllers/public/search_controller.rb
... ... @@ -95,10 +95,10 @@ class SearchController &lt; PublicController
95 95  
96 96 def events
97 97 if params[:year].blank? && params[:year].blank? && params[:day].blank?
98   - @date = Date.today
  98 + @date = DateTime.now
99 99 else
100   - year = (params[:year] ? params[:year].to_i : Date.today.year)
101   - month = (params[:month] ? params[:month].to_i : Date.today.month)
  100 + year = (params[:year] ? params[:year].to_i : DateTime.now.year)
  101 + month = (params[:month] ? params[:month].to_i : DateTime.now.month)
102 102 day = (params[:day] ? params[:day].to_i : 1)
103 103 @date = build_date(year, month, day)
104 104 end
... ... @@ -109,9 +109,7 @@ class SearchController &lt; PublicController
109 109 @events = @category ?
110 110 environment.events.by_day(@date).in_category(Category.find(@category_id)).paginate(:per_page => per_page, :page => params[:page]) :
111 111 environment.events.by_day(@date).paginate(:per_page => per_page, :page => params[:page])
112   - end
113   -
114   - if params[:year] || params[:month]
  112 + elsif params[:year] || params[:month]
115 113 @events = @category ?
116 114 environment.events.by_month(@date).in_category(Category.find(@category_id)).paginate(:per_page => per_page, :page => params[:page]) :
117 115 environment.events.by_month(@date).paginate(:per_page => per_page, :page => params[:page])
... ...
app/helpers/application_helper.rb
... ... @@ -44,6 +44,8 @@ module ApplicationHelper
44 44  
45 45 include PluginsHelper
46 46  
  47 + include TaskHelper
  48 +
47 49 def locale
48 50 (@page && !@page.language.blank?) ? @page.language : FastGettext.locale
49 51 end
... ... @@ -606,24 +608,14 @@ module ApplicationHelper
606 608 trigger_class = 'enterprise-trigger'
607 609 end
608 610 end
609   -
610   - extra_info_tag = ''
611   - img_class = 'profile-image'
612   -
613   - if extra_info.is_a? Hash
614   - extra_info_tag = content_tag( 'span', extra_info[:value], :class => 'extra_info '+extra_info[:class])
615   - img_class +=' '+extra_info[:class]
616   - else
617   - extra_info_tag = content_tag( 'span', extra_info, :class => 'extra_info' )
618   - end
619   - #extra_info = extra_info.nil? ? '' : content_tag( 'span', extra_info, :class => 'extra_info' )
  611 + extra_info = extra_info.nil? ? '' : content_tag( 'span', extra_info, :class => 'extra_info' )
620 612 links = links_for_balloon(profile)
621 613 content_tag('div', content_tag(tag,
622 614 (environment.enabled?(:show_balloon_with_profile_links_when_clicked) ? popover_menu(_('Profile links'),profile.short_name,links,{:class => trigger_class, :url => url}) : "") +
623 615 link_to(
624   - content_tag( 'span', profile_image( profile, size ), :class => img_class ) +
  616 + content_tag( 'span', profile_image( profile, size ), :class => 'profile-image' ) +
625 617 content_tag( 'span', h(name), :class => ( profile.class == Person ? 'fn' : 'org' ) ) +
626   - extra_info_tag + profile_sex_icon( profile ) + profile_cat_icons( profile ),
  618 + extra_info + profile_sex_icon( profile ) + profile_cat_icons( profile ),
627 619 profile.url,
628 620 :class => 'profile_link url',
629 621 :help => _('Click on this icon to go to the <b>%s</b>\'s home page') % profile.name,
... ...
app/helpers/content_viewer_helper.rb
... ... @@ -51,7 +51,7 @@ module ContentViewerHelper
51 51 elsif date_format == 'past_time'
52 52 left_time = true
53 53 end
54   - content_tag('span', show_date(article.published_at, use_numbers , year, left_time), :class => 'date')
  54 + content_tag('span', show_time(article.published_at, use_numbers , year, left_time), :class => 'date')
55 55 end
56 56  
57 57 def link_to_comments(article, args = {})
... ...
app/helpers/dates_helper.rb
... ... @@ -43,9 +43,14 @@ module DatesHelper
43 43 end
44 44  
45 45 # formats a datetime for displaying.
46   - def show_time(time)
47   - if time
48   - _('%{day} %{month} %{year}, %{hour}:%{minutes}') % { :year => time.year, :month => month_name(time.month), :day => time.day, :hour => time.hour, :minutes => time.strftime("%M") }
  46 + def show_time(time, use_numbers = false, year = true, left_time = false)
  47 + if time && use_numbers
  48 + _('%{month}/%{day}/%{year}, %{hour}:%{minutes}') % { :year => (year ? time.year : ''), :month => time.month, :day => time.day, :hour => time.hour, :minutes => time.strftime("%M") }
  49 + elsif time && left_time
  50 + date_format = time_ago_in_words(time)
  51 + elsif time
  52 + date_format = year ? _('%{month_name} %{day}, %{year} %{hour}:%{minutes}') : _('%{month_name} %{day} %{hour}:%{minutes}')
  53 + date_format % { :day => time.day, :month_name => month_name(time.month), :year => time.year, :hour => time.hour, :minutes => time.strftime("%M") }
49 54 else
50 55 ''
51 56 end
... ... @@ -53,7 +58,7 @@ module DatesHelper
53 58  
54 59 def show_period(date1, date2 = nil, use_numbers = false)
55 60 if (date1 == date2) || (date2.nil?)
56   - show_date(date1, use_numbers)
  61 + show_time(date1, use_numbers)
57 62 else
58 63 if date1.year == date2.year
59 64 if date1.month == date2.month
... ... @@ -72,8 +77,8 @@ module DatesHelper
72 77 end
73 78 else
74 79 _('from %{date1} to %{date2}') % {
75   - :date1 => show_date(date1, use_numbers),
76   - :date2 => show_date(date2, use_numbers)
  80 + :date1 => show_time(date1, use_numbers),
  81 + :date2 => show_time(date2, use_numbers)
77 82 }
78 83 end
79 84 end
... ... @@ -106,18 +111,18 @@ module DatesHelper
106 111  
107 112 def build_date(year, month, day = 1)
108 113 if year.blank? and month.blank? and day.blank?
109   - Date.today
  114 + DateTime.now
110 115 else
111 116 if year.blank?
112   - year = Date.today.year
  117 + year = DateTime.now.year
113 118 end
114 119 if month.blank?
115   - month = Date.today.month
  120 + month = DateTime.now.month
116 121 end
117 122 if day.blank?
118 123 day = 1
119 124 end
120   - Date.new(year.to_i, month.to_i, day.to_i)
  125 + DateTime.new(year.to_i, month.to_i, day.to_i)
121 126 end
122 127 end
123 128  
... ...
app/helpers/email_template_helper.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +module EmailTemplateHelper
  2 +
  3 + def mail_with_template(params={})
  4 + if params[:email_template].present?
  5 + params[:body] = params[:email_template].parsed_body(params[:template_params])
  6 + params[:subject] = params[:email_template].parsed_subject(params[:template_params])
  7 + params[:content_type] = "text/html"
  8 + end
  9 + mail(params.except(:email_template))
  10 + end
  11 +
  12 +end
... ...
app/helpers/events_helper.rb
... ... @@ -16,7 +16,7 @@ module EventsHelper
16 16  
17 17 content_tag( 'tr',
18 18 content_tag('td',
19   - content_tag('div', show_date(article.start_date) + ( article.end_date.nil? ? '' : (_(" to ") + show_date(article.end_date))),:class => 'event-date' ) +
  19 + content_tag('div', show_time(article.start_date) + ( article.end_date.nil? ? '' : (_(" to ") + show_time(article.end_date))),:class => 'event-date' ) +
20 20 content_tag('div',link_to(article.name,article.url),:class => 'event-title') +
21 21 content_tag('div',(article.address.nil? or article.address == '') ? '' : (_('Place: ') + article.address),:class => 'event-place')
22 22 )
... ... @@ -30,7 +30,7 @@ module EventsHelper
30 30 # the day itself
31 31 date,
32 32 # is there any events in this date?
33   - events.any? {|event| event.date_range.include?(date)},
  33 + events.any? {|event| event.date_range.cover?(date)},
34 34 # is this date in the current month?
35 35 true
36 36 ]
... ...
app/helpers/forms_helper.rb
... ... @@ -151,7 +151,7 @@ module FormsHelper
151 151 datepicker_options[:close_text] ||= _('Done')
152 152 datepicker_options[:constrain_input] ||= true
153 153 datepicker_options[:current_text] ||= _('Today')
154   - datepicker_options[:date_format] ||= 'mm/dd/yy'
  154 + datepicker_options[:date_format] ||= 'yy/mm/dd'
155 155 datepicker_options[:day_names] ||= [_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday')]
156 156 datepicker_options[:day_names_min] ||= [_('Su'), _('Mo'), _('Tu'), _('We'), _('Th'), _('Fr'), _('Sa')]
157 157 datepicker_options[:day_names_short] ||= [_('Sun'), _('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'), _('Sat')]
... ... @@ -236,7 +236,7 @@ module FormsHelper
236 236 weekHeader: #{datepicker_options[:week_header].to_json},
237 237 yearRange: #{datepicker_options[:year_range].to_json},
238 238 yearSuffix: #{datepicker_options[:year_suffix].to_json}
239   - })
  239 + }).datepicker('setDate', new Date('#{value}'))
240 240 </script>
241 241 ".html_safe
242 242 result
... ...
app/helpers/task_helper.rb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +module TaskHelper
  2 +
  3 + def task_email_template(description, email_templates, task, include_blank=true)
  4 + return '' unless email_templates.present?
  5 +
  6 + content_tag(
  7 + :div,
  8 + labelled_form_field(description, select_tag("tasks[#{task.id}][task][email_template_id]", options_from_collection_for_select(email_templates, :id, :name), :include_blank => include_blank, 'data-url' => url_for(:controller => 'profile_email_templates', :action => 'show_parsed', :profile => profile.identifier))),
  9 + :class => 'template-selection'
  10 + )
  11 + end
  12 +
  13 + def task_action action
  14 + base_url = { action: action }
  15 + url_for(base_url.merge(filter_params))
  16 + end
  17 +
  18 + def filter_params
  19 + filter_fields = ['filter_type', 'filter_text', 'filter_responsible', 'filter_tags']
  20 + params.select {|filter| filter if filter_fields.include? filter }
  21 + end
  22 +
  23 +end
... ...
app/mailers/task_mailer.rb
1 1 class TaskMailer < ActionMailer::Base
2 2  
  3 + include EmailTemplateHelper
  4 +
3 5 def target_notification(task, message)
4 6 @message = extract_message(message)
5 7 @target = task.target.name
... ... @@ -34,10 +36,12 @@ class TaskMailer &lt; ActionMailer::Base
34 36 @environment = task.requestor.environment.name
35 37 @url = url_for(:host => task.requestor.environment.default_hostname, :controller => 'home')
36 38  
37   - mail(
  39 + mail_with_template(
38 40 to: task.requestor.notification_emails,
39 41 from: self.class.generate_from(task),
40   - subject: '[%s] %s' % [task.requestor.environment.name, task.target_notification_description]
  42 + subject: '[%s] %s' % [task.requestor.environment.name, task.target_notification_description],
  43 + email_template: task.email_template,
  44 + template_params: {:environment => task.requestor.environment, :task => task, :message => @message, :url => @url, :requestor => task.requestor}
41 45 )
42 46 end
43 47  
... ...
app/mailers/user_mailer.rb
1 1 class UserMailer < ActionMailer::Base
  2 +
  3 + include EmailTemplateHelper
  4 +
2 5 def activation_email_notify(user)
3 6 user_email = "#{user.login}@#{user.email_domain}"
4 7 @name = user.name
... ... @@ -22,10 +25,12 @@ class UserMailer &lt; ActionMailer::Base
22 25 @redirection = (true if user.return_to)
23 26 @join = (user.community_to_join if user.community_to_join)
24 27  
25   - mail(
  28 + mail_with_template(
26 29 from: "#{user.environment.name} <#{user.environment.contact_email}>",
27 30 to: user.email,
28 31 subject: _("[%s] Activate your account") % [user.environment.name],
  32 + template_params: {:environment => user.environment, :activation_code => @activation_code, :redirection => @redirection, :join => @join},
  33 + email_template: user.environment.email_templates.find_by_template_type(:user_activation),
29 34 )
30 35 end
31 36  
... ...
app/models/article.rb
... ... @@ -645,6 +645,14 @@ class Article &lt; ActiveRecord::Base
645 645 can_display_hits? && display_hits
646 646 end
647 647  
  648 + def display_media_panel?
  649 + can_display_media_panel? && environment.enabled?('media_panel')
  650 + end
  651 +
  652 + def can_display_media_panel?
  653 + false
  654 + end
  655 +
648 656 def image?
649 657 false
650 658 end
... ... @@ -813,6 +821,10 @@ class Article &lt; ActiveRecord::Base
813 821 "content_viewer/view_page"
814 822 end
815 823  
  824 + def to_liquid
  825 + HashWithIndifferentAccess.new :name => name, :abstract => abstract, :body => body, :id => id, :parent_id => parent_id, :author => author
  826 + end
  827 +
816 828 private
817 829  
818 830 def sanitize_tag_list
... ...
app/models/change_password.rb
... ... @@ -28,6 +28,13 @@ class ChangePassword &lt; Task
28 28 validates_presence_of :password_confirmation, :on => :update, :if => lambda { |change| change.status != Task::Status::CANCELLED }
29 29 validates_confirmation_of :password, :if => lambda { |change| change.status != Task::Status::CANCELLED }
30 30  
  31 + before_save :set_email_template
  32 +
  33 + def set_email_template
  34 + template = environment.email_templates.find_by_template_type(:user_change_password)
  35 + data[:email_template_id] = template.id unless template.nil?
  36 + end
  37 +
31 38 def environment
32 39 requestor.environment unless requestor.nil?
33 40 end
... ...
app/models/email_template.rb 0 → 100644
... ... @@ -0,0 +1,50 @@
  1 +class EmailTemplate < ActiveRecord::Base
  2 +
  3 + belongs_to :owner, :polymorphic => true
  4 +
  5 + attr_accessible :template_type, :subject, :body, :owner, :name
  6 +
  7 + validates_presence_of :name
  8 +
  9 + validates :name, uniqueness: { scope: [:owner_type, :owner_id] }
  10 +
  11 + validates :template_type, uniqueness: { scope: [:owner_type, :owner_id] }, if: :unique_by_type?
  12 +
  13 + def parsed_body(params)
  14 + @parsed_body ||= parse(body, params)
  15 + end
  16 +
  17 + def parsed_subject(params)
  18 + @parsed_subject ||= parse(subject, params)
  19 + end
  20 +
  21 + def self.available_types
  22 + {
  23 + :task_rejection => {:description => _('Task Rejection'), :owner_type => Profile},
  24 + :task_acceptance => {:description => _('Task Acceptance'), :owner_type => Profile},
  25 + :organization_members => {:description => _('Organization Members'), :owner_type => Profile},
  26 + :user_activation => {:description => _('User Activation'), :unique => true, :owner_type => Environment},
  27 + :user_change_password => {:description => _('Change User Password'), :unique => true, :owner_type => Environment}
  28 + }
  29 + end
  30 +
  31 + def available_types
  32 + HashWithIndifferentAccess.new EmailTemplate.available_types.select {|k, v| owner.kind_of?(v[:owner_type])}
  33 + end
  34 +
  35 + def type_description
  36 + available_types.fetch(template_type, {})[:description]
  37 + end
  38 +
  39 + def unique_by_type?
  40 + available_types.fetch(template_type, {})[:unique]
  41 + end
  42 +
  43 + protected
  44 +
  45 + def parse(source, params)
  46 + template = Liquid::Template.parse(source)
  47 + template.render(HashWithIndifferentAccess.new(params))
  48 + end
  49 +
  50 +end
... ...
app/models/enterprise_homepage.rb
... ... @@ -35,4 +35,8 @@ class EnterpriseHomepage &lt; Article
35 35 false
36 36 end
37 37  
  38 + def can_display_media_panel?
  39 + true
  40 + end
  41 +
38 42 end
... ...
app/models/environment.rb
... ... @@ -21,6 +21,7 @@ class Environment &lt; ActiveRecord::Base
21 21  
22 22 has_many :tasks, :dependent => :destroy, :as => 'target'
23 23 has_many :search_terms, :as => :context
  24 + has_many :email_templates, :foreign_key => :owner_id
24 25  
25 26 IDENTIFY_SCRIPTS = /(php[0-9s]?|[sp]htm[l]?|pl|py|cgi|rb)/
26 27  
... ... @@ -50,6 +51,7 @@ class Environment &lt; ActiveRecord::Base
50 51 'manage_environment_licenses' => N_('Manage environment licenses'),
51 52 'manage_environment_trusted_sites' => N_('Manage environment trusted sites'),
52 53 'edit_appearance' => N_('Edit appearance'),
  54 + 'manage_email_templates' => N_('Manage Email Templates'),
53 55 }
54 56  
55 57 module Roles
... ... @@ -985,6 +987,10 @@ class Environment &lt; ActiveRecord::Base
985 987 self.licenses.any?
986 988 end
987 989  
  990 + def to_liquid
  991 + HashWithIndifferentAccess.new :name => name
  992 + end
  993 +
988 994 private
989 995  
990 996 def default_language_available
... ...
app/models/event.rb
... ... @@ -3,13 +3,14 @@ require &#39;builder&#39;
3 3  
4 4 class Event < Article
5 5  
6   - attr_accessible :start_date, :end_date, :link, :address
  6 + attr_accessible :start_date, :end_date, :link, :address, :presenter
7 7  
8 8 def self.type_name
9 9 _('Event')
10 10 end
11 11  
12 12 settings_items :address, :type => :string
  13 + settings_items :presenter, :type => :string
13 14  
14 15 def link=(value)
15 16 self.setting[:link] = maybe_add_http(value)
... ... @@ -23,7 +24,7 @@ class Event &lt; Article
23 24  
24 25 def initialize(*args)
25 26 super(*args)
26   - self.start_date ||= Date.today
  27 + self.start_date ||= DateTime.now
27 28 end
28 29  
29 30 validates_presence_of :title, :start_date
... ... @@ -35,7 +36,7 @@ class Event &lt; Article
35 36 end
36 37  
37 38 scope :by_day, lambda { |date|
38   - { :conditions => ['start_date = :date AND end_date IS NULL OR (start_date <= :date AND end_date >= :date)', {:date => date}],
  39 + { :conditions => [' start_date >= :start_date AND start_date <= :end_date AND end_date IS NULL OR (start_date <= :end_date AND end_date >= :start_date)', {:start_date => date.beginning_of_day, :end_date => date.end_of_day}],
39 40 :order => 'start_date ASC'
40 41 }
41 42 }
... ... @@ -80,7 +81,7 @@ class Event &lt; Article
80 81  
81 82 def self.date_range(year, month)
82 83 if year.nil? || month.nil?
83   - today = Date.today
  84 + today = DateTime.now
84 85 year = today.year
85 86 month = today.month
86 87 else
... ... @@ -88,7 +89,7 @@ class Event &lt; Article
88 89 month = month.to_i
89 90 end
90 91  
91   - first_day = Date.new(year, month, 1)
  92 + first_day = DateTime.new(year, month, 1)
92 93 last_day = first_day + 1.month - 1.day
93 94  
94 95 first_day..last_day
... ... @@ -134,6 +135,10 @@ class Event &lt; Article
134 135 true
135 136 end
136 137  
  138 + def can_display_media_panel?
  139 + true
  140 + end
  141 +
137 142 include Noosfero::TranslatableContent
138 143 include MaybeAddHttp
139 144  
... ...
app/models/organization.rb
... ... @@ -178,7 +178,11 @@ class Organization &lt; Profile
178 178 end
179 179  
180 180 def notification_emails
  181 + # TODO: Add performance improvement here!
  182 + #emails = [contact_email].select(&:present?) + admins([:user]).pluck(:email)
  183 + # Revert change to make the tests pass
181 184 emails = [contact_email].select(&:present?) + admins.map(&:email)
  185 +
182 186 if emails.empty?
183 187 emails << environment.contact_email
184 188 end
... ...
app/models/person.rb
... ... @@ -19,12 +19,13 @@ class Person &lt; Profile
19 19 acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)}
20 20 acts_as_accessor
21 21  
22   - acts_as_tagger
23 22  
24   - scope :members_of, lambda { |resources|
  23 + scope :members_of, lambda { |resources, extra_joins = nil|
25 24 resources = [resources] if !resources.kind_of?(Array)
26 25 conditions = resources.map {|resource| "role_assignments.resource_type = '#{resource.class.base_class.name}' AND role_assignments.resource_id = #{resource.id || -1}"}.join(' OR ')
27   - { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] }
  26 + joins = [:role_assignments]
  27 + joins += extra_joins if extra_joins.is_a? Array
  28 + { :select => 'DISTINCT profiles.*', :joins => joins, :conditions => [conditions] }
28 29 }
29 30  
30 31 scope :not_members_of, lambda { |resources|
... ... @@ -33,10 +34,11 @@ class Person &lt; Profile
33 34 { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "role_assignments" ON "role_assignments"."accessor_id" = "profiles"."id" AND "role_assignments"."accessor_type" = (\'Profile\') WHERE "profiles"."type" IN (\'Person\') AND (%s))' % conditions] }
34 35 }
35 36  
36   - scope :by_role, lambda { |roles|
  37 + scope :by_role, lambda { |roles, extra_joins = nil|
37 38 roles = [roles] unless roles.kind_of?(Array)
38   - { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.role_id IN (?)',
39   -roles] }
  39 + joins = [:role_assignments]
  40 + joins += extra_joins if extra_joins.is_a? Array
  41 + { :select => 'DISTINCT profiles.*', :joins => joins, :conditions => ['role_assignments.role_id IN (?)',roles] }
40 42 }
41 43  
42 44 scope :not_friends_of, lambda { |resources|
... ... @@ -216,7 +218,7 @@ roles] }
216 218 contact_informatioin
217 219 ]
218 220  
219   - xss_terminate :only => [ :custom_footer, :custom_header, :description, :preferred_domain, :nickname, :sex, :nationality, :country, :state, :city, :district, :zip_code, :address, :address_reference, :cell_phone, :comercial_phone, :personal_website, :jabber_id, :schooling, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization, :organization_website, :contact_phone, :contact_information ], :with => 'white_list'
  221 + xss_terminate :only => [ :custom_footer, :custom_header, :description, :nickname, :sex, :nationality, :country, :state, :city, :district, :zip_code, :address, :address_reference, :cell_phone, :comercial_phone, :personal_website, :jabber_id, :schooling, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization, :organization_website, :contact_phone, :contact_information ], :with => 'white_list'
220 222  
221 223 validates_multiparameter_assignments
222 224  
... ... @@ -310,7 +312,7 @@ roles] }
310 312 end
311 313  
312 314 after_update do |person|
313   - person.user.save!
  315 + person.user.save! unless person.user.changes.blank?
314 316 end
315 317  
316 318 def is_admin?(environment = nil)
... ...
app/models/product_category.rb
... ... @@ -13,8 +13,11 @@ class ProductCategory &lt; Category
13 13 scope :by_environment, lambda { |environment| {
14 14 :conditions => ['environment_id = ?', environment.id]
15 15 }}
  16 +
  17 + # updated scope method to avoid sql injection vunerabillity (http://brakemanscanner.org/docs/warning_types/sql_injection/)
  18 + # explicited to_i on level argument
16 19 scope :unique_by_level, lambda { |level| {
17   - :select => "DISTINCT ON (filtered_category) split_part(path, '/', #{level}) AS filtered_category, categories.*"
  20 + :select => "DISTINCT ON (filtered_category) split_part(path, '/', #{level.to_i}) AS filtered_category, categories.*"
18 21 }}
19 22  
20 23 def all_products
... ...
app/models/profile.rb
... ... @@ -3,7 +3,7 @@
3 3 # which by default is the one returned by Environment:default.
4 4 class Profile < ActiveRecord::Base
5 5  
6   - attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time, :redirection_after_login, :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :custom_fields
  6 + attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time, :redirection_after_login, :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :custom_fields, :administrator_mail_notification, :region
7 7  
8 8 # use for internationalizable human type names in search facets
9 9 # reimplement on subclasses
... ... @@ -107,6 +107,7 @@ class Profile &lt; ActiveRecord::Base
107 107 'invite_members' => N_('Invite members'),
108 108 'send_mail_to_members' => N_('Send e-Mail to members'),
109 109 'manage_custom_roles' => N_('Manage custom roles'),
  110 + 'manage_email_templates' => N_('Manage Email Templates'),
110 111 }
111 112  
112 113 acts_as_accessible
... ... @@ -191,8 +192,8 @@ class Profile &lt; ActiveRecord::Base
191 192 alias_method_chain :count, :distinct
192 193 end
193 194  
194   - def members_by_role(roles)
195   - Person.members_of(self).by_role(roles)
  195 + def members_by_role(roles, with_entities = nil)
  196 + Person.members_of(self, with_entities).by_role(roles, with_entities)
196 197 end
197 198  
198 199 acts_as_having_boxes
... ... @@ -223,6 +224,8 @@ class Profile &lt; ActiveRecord::Base
223 224  
224 225 has_many :comments_received, :class_name => 'Comment', :through => :articles, :source => :comments
225 226  
  227 + has_many :email_templates, :foreign_key => :owner_id
  228 +
226 229 # Although this should be a has_one relation, there are no non-silly names for
227 230 # a foreign key on article to reference the template to which it is
228 231 # welcome_page... =P
... ... @@ -250,6 +253,7 @@ class Profile &lt; ActiveRecord::Base
250 253 settings_items :description
251 254 settings_items :fields_privacy, :type => :hash, :default => {}
252 255 settings_items :email_suggestions, :type => :boolean, :default => false
  256 + settings_items :administrator_mail_notification, :type => :boolean, :default => true
253 257  
254 258 validates_length_of :description, :maximum => 550, :allow_nil => true
255 259  
... ... @@ -543,6 +547,10 @@ class Profile &lt; ActiveRecord::Base
543 547 self.articles.find(:all, options)
544 548 end
545 549  
  550 + def to_liquid
  551 + HashWithIndifferentAccess.new :name => name, :identifier => identifier
  552 + end
  553 +
546 554 class << self
547 555  
548 556 # finds a profile by its identifier. This method is a shortcut to
... ... @@ -685,15 +693,15 @@ private :generate_url, :url_options
685 693 after_create :insert_default_article_set
686 694 def insert_default_article_set
687 695 if template
688   - copy_articles_from template
  696 + self.save! if copy_articles_from template
689 697 else
690 698 default_set_of_articles.each do |article|
691 699 article.profile = self
692 700 article.advertise = false
693 701 article.save!
694 702 end
  703 + self.save!
695 704 end
696   - self.save!
697 705 end
698 706  
699 707 # Override this method in subclasses of Profile to create a default article
... ... @@ -714,10 +722,12 @@ private :generate_url, :url_options
714 722 end
715 723  
716 724 def copy_articles_from other
  725 + return false if other.top_level_articles.empty?
717 726 other.top_level_articles.each do |a|
718 727 copy_article_tree a
719 728 end
720 729 self.articles.reload
  730 + true
721 731 end
722 732  
723 733 def copy_article_tree(article, parent=nil)
... ... @@ -762,6 +772,29 @@ private :generate_url, :url_options
762 772 end
763 773 end
764 774  
  775 + # Adds many people to profile by id's
  776 + def add_members_by_id(people_ids)
  777 +
  778 + unless people_ids.nil? && people_ids.empty?
  779 +
  780 + people = Person.where(id: people_ids)
  781 + people.each do |person|
  782 +
  783 + add_member(person) unless person.is_member_of?(self)
  784 + end
  785 + end
  786 + end
  787 +
  788 + # Adds many people to profile by email's
  789 + def add_members_by_email(people_emails)
  790 +
  791 + people = User.where(email: people_emails)
  792 + people.each do |user|
  793 +
  794 + add_member(user.person) unless user.person.is_member_of?(self)
  795 + end
  796 + end
  797 +
765 798 def remove_member(person)
766 799 self.disaffiliate(person, Profile::Roles.all_roles(environment.id))
767 800 end
... ... @@ -893,11 +926,11 @@ private :generate_url, :url_options
893 926 self.forums.count.nonzero?
894 927 end
895 928  
896   - def admins
  929 + def admins(with_entities = nil)
897 930 return [] if environment.blank?
898 931 admin_role = Profile::Roles.admin(environment.id)
899 932 return [] if admin_role.blank?
900   - self.members_by_role(admin_role)
  933 + self.members_by_role(admin_role, with_entities)
901 934 end
902 935  
903 936 def enable_contact?
... ...
app/models/task.rb
... ... @@ -42,6 +42,8 @@ class Task &lt; ActiveRecord::Base
42 42  
43 43 attr_protected :status
44 44  
  45 + settings_items :email_template_id, :type => :integer
  46 +
45 47 def initialize(*args)
46 48 super
47 49 self.status = (args.first ? args.first[:status] : nil) || Task::Status::ACTIVE
... ... @@ -68,14 +70,28 @@ class Task &lt; ActiveRecord::Base
68 70 begin
69 71 target_msg = task.target_notification_message
70 72 if target_msg && task.target && !task.target.notification_emails.empty?
71   - TaskMailer.target_notification(task, target_msg).deliver
  73 + if target_profile_accepts_notification?(task)
  74 + TaskMailer.target_notification(task, target_msg).deliver
  75 + end
72 76 end
73   - rescue NotImplementedError => ex
  77 + rescue Exception => ex
74 78 Rails.logger.info ex.to_s
75 79 end
76 80 end
77 81 end
78 82  
  83 + def target_profile_accepts_notification?(task)
  84 + if target_is_profile?(task)
  85 + return task.target.administrator_mail_notification
  86 + else
  87 + true
  88 + end
  89 + end
  90 +
  91 + def target_is_profile?(task)
  92 + task.target.kind_of? Profile
  93 + end
  94 +
79 95 # this method finished the task. It calls #perform, which must be overriden
80 96 # by subclasses. At the end a message (as returned by #finish_message) is
81 97 # sent to the requestor with #notify_requestor.
... ... @@ -282,15 +298,56 @@ class Task &lt; ActiveRecord::Base
282 298 end
283 299 end
284 300  
  301 + def email_template
  302 + @email_template ||= email_template_id.present? ? EmailTemplate.find_by_id(email_template_id) : nil
  303 + end
  304 +
  305 + def to_liquid
  306 + HashWithIndifferentAccess.new({
  307 + :requestor => requestor,
  308 + :reject_explanation => reject_explanation,
  309 + :code => code
  310 + })
  311 + end
  312 +
285 313 scope :pending, :conditions => { :status => Task::Status::ACTIVE }
286 314 scope :hidden, :conditions => { :status => Task::Status::HIDDEN }
287 315 scope :finished, :conditions => { :status => Task::Status::FINISHED }
288 316 scope :canceled, :conditions => { :status => Task::Status::CANCELLED }
289 317 scope :closed, :conditions => { :status => [Task::Status::CANCELLED, Task::Status::FINISHED] }
290 318 scope :opened, :conditions => { :status => [Task::Status::ACTIVE, Task::Status::HIDDEN] }
291   - scope :of, lambda { |type| conditions = type ? "type LIKE '#{type}'" : "1=1"; {:conditions => [conditions]} }
  319 +
  320 + # # updated scope method to avoid sql injection vunerabillity (http://brakemanscanner.org/docs/warning_types/sql_injection/)
  321 + # def self.of type
  322 + # if type
  323 + # where "type LIKE ?", type
  324 + # else
  325 + # all
  326 + # end
  327 + # end
  328 + #
  329 + # # updated scope method to avoid sql injection vunerabillity (http://brakemanscanner.org/docs/warning_types/sql_injection/)
  330 + # def self.order_by attribute_name, sort_order
  331 + # if Task.column_names.include? attribute_name
  332 + # # TODO future versions of rails accepts a hash as param to order method
  333 + # # which helps to prevent sql injection in an shorter way
  334 + # sort_order_filtered = ("ASC".eql? "#{sort_order}".upcase) ? 'asc' : 'desc'
  335 + # sort_expression = Task.column_names.collect {|column_name| "#{column_name} #{sort_order_filtered}" if column_name.eql? attribute_name}
  336 + # order(sort_expression.join) unless sort_expression.join.empty?
  337 + # end
  338 + # end
  339 + #
  340 + # # updated scope method to avoid sql injection vunerabillity (http://brakemanscanner.org/docs/warning_types/sql_injection/)
  341 + # def self.like field, value
  342 + # if value and Tasks.column_names.include? field
  343 + # where("LOWER(?) LIKE ?", "#{field}", "%#{value.downcase}%")
  344 + # end
  345 + # end
  346 +
  347 + scope :of, lambda { |type| conditions = type ? "tasks.type LIKE '#{type}'" : "1=1"; {:conditions => [conditions]} }
292 348 scope :order_by, lambda { |attribute, ord| {:order => "#{attribute} #{ord}"} }
293 349 scope :like, lambda { |field, value| where("LOWER(#{field}) LIKE ?", "%#{value.downcase}%") if value}
  350 +
294 351 scope :pending_all, lambda { |profile, filter_type, filter_text|
295 352 self.to(profile).without_spam.pending.of(filter_type).like('data', filter_text)
296 353 }
... ... @@ -310,6 +367,10 @@ class Task &lt; ActiveRecord::Base
310 367 Task.to(profile).pending.select('distinct type').map { |t| [t.class.name, t.title] }
311 368 end
312 369  
  370 + def self.closed_types_for(profile)
  371 + Task.to(profile).closed.select('distinct type').map { |t| [t.class.name, t.title] }
  372 + end
  373 +
313 374 def opened?
314 375 status == Task::Status::ACTIVE || status == Task::Status::HIDDEN
315 376 end
... ...
app/models/textile_article.rb
... ... @@ -24,6 +24,10 @@ class TextileArticle &lt; TextArticle
24 24 true
25 25 end
26 26  
  27 + def can_display_media_panel?
  28 + true
  29 + end
  30 +
27 31 protected
28 32  
29 33 def convert_to_html(textile)
... ...
app/models/tiny_mce_article.rb
... ... @@ -28,4 +28,8 @@ class TinyMceArticle &lt; TextArticle
28 28 true
29 29 end
30 30  
  31 + def can_display_media_panel?
  32 + true
  33 + end
  34 +
31 35 end
... ...
app/models/user.rb
... ... @@ -102,18 +102,20 @@ class User &lt; ActiveRecord::Base
102 102 # Virtual attribute for the unencrypted password
103 103 attr_accessor :password, :name
104 104  
105   - validates_presence_of :login, :email
106   - validates_format_of :login, :with => Profile::IDENTIFIER_FORMAT, :if => (lambda {|user| !user.login.blank?})
  105 + validates_presence_of :login
  106 + validates_presence_of :email
  107 + validates_format_of :login, :message => _('incorrect format'), :with => Profile::IDENTIFIER_FORMAT, :if => (lambda {|user| !user.login.blank?})
107 108 validates_presence_of :password, :if => :password_required?
108 109 validates_presence_of :password_confirmation, :if => :password_required?
109   - validates_length_of :password, :within => 4..40, :if => :password_required?
  110 + validates_length_of :password, :message => _('length must be within 4 to 40 characters'), :within => 4..40, :if => :password_required?
110 111 validates_confirmation_of :password, :if => :password_required?
111   - validates_length_of :login, :within => 2..40, :if => (lambda {|user| !user.login.blank?})
112   - validates_length_of :email, :within => 3..100, :if => (lambda {|user| !user.email.blank?})
113   - validates_uniqueness_of :login, :email, :case_sensitive => false, :scope => :environment_id
  112 + validates_length_of :login, :message => _('length must be within 2 to 40 characters'), :within => 2..40, :if => (lambda {|user| !user.login.blank?})
  113 + validates_length_of :email, :message => _('length must be within 3 to 100 characters'),:within => 3..100, :if => (lambda {|user| !user.email.blank?})
  114 + validates_uniqueness_of :login, :case_sensitive => false, :scope => :environment_id
  115 + validates_uniqueness_of :email, :case_sensitive => false, :scope => :environment_id
114 116 before_save :encrypt_password
115 117 before_save :normalize_email, if: proc{ |u| u.email.present? }
116   - validates_format_of :email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda {|user| !user.email.blank?})
  118 + validates_format_of :email, :message => _('incorrect format'), :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda {|user| !user.email.blank?})
117 119  
118 120 validates_inclusion_of :terms_accepted, :in => [ '1' ], :if => lambda { |u| ! u.terms_of_use.blank? }, :message => N_('{fn} must be checked in order to signup.').fix_i18n
119 121  
... ... @@ -392,7 +394,7 @@ class User &lt; ActiveRecord::Base
392 394  
393 395 def deliver_activation_code
394 396 return if person.is_template?
395   - UserMailer.activation_code(self).deliver unless self.activation_code.blank?
  397 + Delayed::Job.enqueue(UserMailer::Job.new(self, :activation_code)) unless self.activation_code.blank?
396 398 end
397 399  
398 400 def delay_activation_check
... ...
app/views/admin_panel/index.html.erb
... ... @@ -12,6 +12,7 @@
12 12 <tr><td><%= link_to _('Licenses'), :controller =>'licenses' %></td></tr>
13 13 <tr><td><%= link_to _('Trusted sites'), :controller =>'trusted_sites' %></td></tr>
14 14 <tr><td><%= link_to _('Blocks'), :controller => 'features', :action => 'manage_blocks' %></td></tr>
  15 + <tr><td><%= link_to _('Email templates'), :controller =>'environment_email_templates' %></td></tr>
15 16 </table>
16 17  
17 18 <h2><%= _('Profiles') %></h2>
... ...
app/views/cms/_event.html.erb
... ... @@ -8,9 +8,9 @@
8 8 <%= render :partial => 'general_fields' %>
9 9 <%= render :partial => 'translatable' %>
10 10  
11   -<%= labelled_form_field(_('Start date'), pick_date(:article, :start_date)) %>
  11 +<%= date_range_field('article[start_date]', 'article[end_date]', @article.start_date, @article.end_date, _('%Y-%m-%d %H:%M'), {:time => true}, {:id => 'article_start_date'} ) %>
12 12  
13   -<%= labelled_form_field(_('End date'), pick_date(:article, :end_date)) %>
  13 +<%= labelled_form_field(_('Presenter:'), text_field(:article, :presenter)) %>
14 14  
15 15 <%= labelled_form_field(_('Event website:'), text_field(:article, :link)) %>
16 16  
... ...
app/views/cms/edit.html.erb
1 1 <%= error_messages_for 'article' %>
2 2  
3   -<% show_media_panel = environment.enabled?('media_panel') && [TinyMceArticle, TextileArticle, Event, EnterpriseHomepage, ProposalsDiscussionPlugin::Topic, ProposalsDiscussionPlugin::Discussion].any?{|klass| @article.kind_of?(klass)} %>
4   -
5   -<div class='<%= (show_media_panel ? 'with_media_panel' : 'no_media_panel') %>'>
  3 +<div class='<%= (@article.display_media_panel? ? 'with_media_panel' : 'no_media_panel') %>'>
6 4 <%= labelled_form_for 'article', :html => { :multipart => true, :class => @type } do |f| %>
7 5  
8 6 <%= hidden_field_tag("type", @type) if @type %>
... ... @@ -68,7 +66,7 @@
68 66 <% end %>
69 67 </div>
70 68  
71   -<% if show_media_panel %>
  69 +<% if @article.display_media_panel? %>
72 70 <%= render :partial => 'text_editor_sidebar' %>
73 71 <% end %>
74 72  
... ...
app/views/content_viewer/_publishing_info.html.erb
1 1 <span class="publishing-info">
2 2 <span class="date">
3   - <%= show_date(@page.published_at) %>
  3 + <%= show_time(@page.published_at) %>
4 4 </span>
5 5 <span class="author">
6 6 <%= _(", by %s") % (@page.author ? link_to(@page.author_name, @page.author_url) : @page.author_name) %>
... ...
app/views/email_templates/_form.html.erb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +<%= form_for(@email_template, :url => {:action => @email_template.persisted? ? :update : :create, :id => @email_template.id}) do |f| %>
  2 +
  3 + <%= error_messages_for :email_template if @email_template.errors.any? %>
  4 +
  5 + <div class="template-fields">
  6 + <div class="header-fields">
  7 + <%= labelled_form_field(_('Template Name:'), f.text_field(:name)) %>
  8 + <%= labelled_form_field(_('Template Type:'), f.select(:template_type, @email_template.available_types.map {|k,v| [v[:description], k]}, :include_blank => true)) %>
  9 + <%= labelled_form_field(_('Subject:'), f.text_field(:subject)) %>
  10 + </div>
  11 + <div class="available-params">
  12 + <div class="reference">
  13 + <a target="_blank" href="https://github.com/Shopify/liquid/wiki/Liquid-for-Designers"><%= _('Template language reference') %></a>
  14 + </div>
  15 + <div class="label">
  16 + <%= _('The following parameters may be used in subject and body:') %>
  17 + </div>
  18 + <div class="values">
  19 + {{profile.name}}, {{profile.identifier}}, {{environment.name}}
  20 + </div>
  21 + </div>
  22 + <%= render :file => 'shared/tiny_mce' %>
  23 + <%= labelled_form_field(_('Body:'), f.text_area(:body, :class => 'mceEditor')) %>
  24 + </div>
  25 +
  26 + <div class="actions">
  27 + <%= submit_button(:save, _('Save')) %>
  28 + <%= button(:back, _('Back'), :action => :index) %>
  29 + </div>
  30 +
  31 +<% end %>
... ...
app/views/email_templates/edit.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<h1><%= _('Editing Email Template') %></h1>
  2 +
  3 +<%= render 'form' %>
... ...
app/views/email_templates/index.html.erb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +<div class="email-templates">
  2 + <h1><%= _('Email Templates') %></h1>
  3 +
  4 + <table>
  5 + <tr>
  6 + <th><%= _('Name') %></th>
  7 + <th><%= _('Type') %></th>
  8 + <th><%= _('Actions') %></th>
  9 + </tr>
  10 +
  11 + <% @email_templates.each do |email_template| %>
  12 + <tr>
  13 + <td><%= email_template.name %></td>
  14 + <td><%= email_template.type_description %></td>
  15 + <td>
  16 + <%= button_without_text(:edit, _('Edit'), {:action => :edit, :id => email_template.id}) %>
  17 + <%= button_without_text(:remove, _('Remove'), {:action => :destroy, :id => email_template.id}, method: :delete, data: { confirm: 'Are you sure?' }) %>
  18 + </td>
  19 + </tr>
  20 + <% end %>
  21 + </table>
  22 +
  23 + <br />
  24 +
  25 + <%= button(:new, _('New template'), :action => :new) %>
  26 + <%= button(:back, _('Back'), @back_to) %>
  27 +</div>
... ...
app/views/email_templates/new.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<h1><%= _('New Email Template') %></h1>
  2 +
  3 +<%= render 'form' %>
... ...
app/views/email_templates/show.html.erb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<p id="notice"><%= notice %></p>
  2 +
  3 +
  4 +<%= link_to 'Edit', url_for(:action => :edit, :id => @email_template.id) %> |
  5 +<%= link_to 'Back', url_for(:action => :index) %>
... ...
app/views/profile/send_mail.html.erb
... ... @@ -4,6 +4,12 @@
4 4  
5 5 <%= error_messages_for :mailing %>
6 6  
  7 +<% if @email_templates.present? %>
  8 + <div class="template-selection">
  9 + <%= labelled_form_field(_('Select a template:'), select_tag(:template, options_from_collection_for_select(@email_templates, :id, :name), :include_blank => true, 'data-url' => url_for(:controller => 'profile_email_templates', :action => 'show_parsed'))) %>
  10 + </div>
  11 +<% end %>
  12 +
7 13 <%= form_for :mailing, :url => {:action => 'send_mail'}, :html => {:id => 'mailing-form'} do |f| %>
8 14  
9 15 <%= labelled_form_field(_('Subject:'), f.text_field(:subject)) %>
... ...
app/views/profile_editor/_moderation.html.erb
1 1 <h2><%= _('Moderation options') %></h2>
2 2 <% if profile.community? %>
3 3 <div style='margin-bottom: 1em'>
  4 + <h4><%= _('Email Configuration:')%></h4>
  5 + </div>
  6 + <div style='margin-bottom: 0.5em'>
  7 + <%= check_box(:profile_data, :administrator_mail_notification, :style => 'float: left') %>
  8 + <div style='margin-left: 30px'>
  9 + <%= _('Send administrator Email for every task (Default: yes)') %>
  10 + </div>
  11 + </div>
  12 +
  13 + <div style='margin-bottom: 1em'>
4 14 <h4><%= _('Invitation moderation:')%></h4>
5 15 </div>
6 16 <div style='margin-bottom: 0.5em'>
... ...
app/views/profile_editor/index.html.erb
... ... @@ -72,6 +72,8 @@
72 72  
73 73 <%= control_panel_button(_('Edit welcome page'), 'welcome-page', :action => 'welcome_page') if has_welcome_page %>
74 74  
  75 + <%= control_panel_button(_('Email Templates'), 'email-templates', :controller => :profile_email_templates) if profile.organization? %>
  76 +
75 77 <% @plugins.dispatch(:control_panel_buttons).each do |button| %>
76 78 <%= control_panel_button(button[:title], button[:icon], button[:url], button[:html_options]) %>
77 79 <% end %>
... ...
app/views/profile_members/_members_list.html.erb
... ... @@ -20,6 +20,7 @@
20 20 :loading => "jQuery('#members-list').addClass('loading')",
21 21 :success => "jQuery('#tr-#{m.identifier}').show()",
22 22 :complete => "jQuery('#members-list').removeClass('loading')",
  23 + :confirm => _('Are you sure you want to remove this user?'),
23 24 :url => { :id => m }.merge(remove_action)) if m != user %>
24 25 </div>
25 26 </td>
... ...
app/views/shared/not_found.html.erb
1 1 <div id='not-found'>
2   - <h1><%= _('Nonexistent content: %s') % (content_tag('tt', @path)) %></h1>
  2 + <h1><%= _('There is no such page: %s') % (content_tag('tt', @path)) %></h1>
3 3 <p>
4 4 <%= _('.You may have clicked an expired link or mistyped the address.') %>
5   - <%= _('.This page does not exist. Would you like to: Create a new conteúdoou Locate existing content.') %>
  5 + <%= _('.This page does not exist. Would you like to: Create a new content or locate existing content.') %>
6 6 <!-- <%= _('If you clicked a link that was in another site, or was given to you by someone else, it would be nice if you tell them that their link is not valid anymore.') %> -->
7 7 </p>
8 8  
... ... @@ -11,7 +11,7 @@
11 11 <%= button :home, _('Go to the home page'), '/' %>
12 12 <% if logged_in? %>
13 13 <% parent_id = ((@article && @article.allow_children?) ? @article : nil) %>
14   - <%= modal_button('new', _('New content'), :controller => 'cms', :action => 'new', :parent_id => parent_id, :cms => true) %>
  14 + <%= modal_button('new', _('New content'), myprofile_path(:profile => current_person.identifier, :controller => 'cms', :action => 'new', :parent_id => parent_id)) %>
15 15 <% end %>
16 16 <% end %>
17 17 <form action="/search">
... ...
app/views/tasks/_approve_article_accept_details.html.erb
  1 +<%= task_email_template(_('Select an acceptance email template:'), @acceptance_email_templates, task) %>
  2 +
1 3 <%= render :file => 'shared/tiny_mce' %>
2 4  
3 5 <%= labelled_form_field(_('Create a link'), f.check_box(:create_link)) %>
... ...
app/views/tasks/_task_processed.html.erb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +<div class="title">
  2 + <%= task_information(task) %>
  3 +</div>
  4 +<div class="status">
  5 + <%= _(Task::Status.names[task.status]) %>
  6 +</div>
  7 +<div class="dates">
  8 + <span class="created">
  9 + <span class="label"><%= _('Created:') %></span>
  10 + <span class="value"><%= show_date(task.created_at) %></span>
  11 + </span>
  12 + &nbsp; &#151; &nbsp;
  13 + <span class="processed">
  14 + <span class="label"><%= _('Processed:') %></span>
  15 + <span class="value"><%= show_date(task.end_date) %></span>
  16 + </span>
  17 +</div>
  18 +<% if task.closed_by.present? %>
  19 + <div class="closed-by">
  20 + <span class="label"><%= _('Closed by:') %></span>
  21 + <span class="value"><%= link_to(task.closed_by.name, task.closed_by.url) %></span>
  22 + </div>
  23 +<% end %>
... ...
app/views/tasks/_task_reject_details.html.erb
  1 +<%= task_email_template(_('Select a rejection email template:'), @rejection_email_templates, task) %>
  2 +
1 3 <%= labelled_form_field(_('Rejection explanation'), f.text_area(:reject_explanation, :rows => 5)) %>
... ...
app/views/tasks/index.html.erb
... ... @@ -50,7 +50,7 @@
50 50 <em><%= _('No pending tasks for %s') % profile.name %></em>
51 51 </p>
52 52 <% else %>
53   - <%= form_tag :action => 'close' do%>
  53 + <%= form_tag task_action('close') do%>
54 54 <% button_bar(:class => 'task-actions') do %>
55 55 <%# FiXME button(:edit, _('View my requests'), :action => 'list_requested') %>
56 56 <%# FIXME button('menu-mail', _('Send request'), :action => 'new') %>
... ... @@ -67,7 +67,9 @@
67 67 <% end %>
68 68  
69 69 <div class="task_boxes">
70   - <%= render :partial => 'task', :collection => @tasks %>
  70 + <% @tasks.each do |task| %>
  71 + <%= render :partial => partial_for_class(task.class, nil, nil), :locals => {:task => task} %>
  72 + <% end %>
71 73 </div>
72 74  
73 75 <% unless @view_only %>
... ...
app/views/tasks/processed.html.erb
  1 +<%= stylesheet_link_tag 'tasks' %>
  2 +
  3 +<div class="task-processed">
1 4 <h1><%= _("%s's processed tasks") % profile.name %></h1>
2 5  
  6 +<div class="task-processed-filter">
  7 +<%
  8 + type_collection = [[nil, _('All')]] + @task_types
  9 +%>
  10 + <%= form_tag '#', :method => 'get' do %>
  11 + <%= field_set_tag _('Filter'), :class => 'filter_fields' do %>
  12 + <div>
  13 + <%= labelled_select(_('Type of task')+': ', 'filter[type]', :first, :last, @filter[:type], type_collection, {:id => 'filter-type'}) %>
  14 + <%= labelled_select(_('Status:'), 'filter[status]', :last, :first, @filter[:status], [[_('Any'), nil], [_(Task::Status.names[Task::Status::CANCELLED]), 2], [_(Task::Status.names[Task::Status::FINISHED]), 3] ]) %>
  15 + </div>
  16 +
  17 + <div>
  18 + <%= labelled_text_field(_('Text Filter:'), 'filter[text]', @filter[:text]) %>
  19 + </div>
  20 +
  21 + <div>
  22 + <%= labelled_text_field(_('Requestor:'), 'filter[requestor]', @filter[:requestor]) %>
  23 + <%= labelled_text_field(_('Closed by:'), 'filter[closed_by]', @filter[:closed_by]) %>
  24 + </div>
  25 +
  26 + <%= labelled_form_field(_('Creation date'), date_range_field('filter[created_from]', 'filter[created_until]', @filter[:created_from], @filter[:created_until], '%Y-%m-%d', { :change_month => true, :change_year => true, :date_format => 'yy-mm-dd' }, { :size => 14, :from_id => 'filter_created_from', :to_id => 'filter_created_until' })) %>
  27 + <%= labelled_form_field(_('Processed date'), date_range_field('filter[closed_from]', 'filter[closed_until]', @filter[:closed_from], @filter[:closed_until], '%Y-%m-%d', { :change_month => true, :change_year => true, :date_format => 'yy-mm-dd' }, { :size => 14, :from_id => 'filter_closed_from', :to_id => 'filter_closed_until' })) %>
  28 +
  29 + <div class="actions">
  30 + <%= submit_button(:search, _('Search')) %>
  31 + </div>
  32 + <% end %>
  33 + <% end %>
  34 +</div>
  35 +
3 36 <p>
4 37 <% if @tasks.empty? %>
5 38 <em><%= _('No processed tasks.') %></em>
6 39 <% else %>
7   - <ul>
  40 + <ul class="task-list">
8 41 <% @tasks.each do |item| %>
9   - <li>
10   - <strong><%= task_information(item) %></strong> <br/>
11   - <small>
12   - <%= _('Created:') +' '+ show_date(item.created_at) %>
13   - &nbsp; &#151; &nbsp;
14   - <%= _('Processed:') +' '+ show_date(item.end_date) %>
15   - </small>
  42 + <li class="task status-<%= item.status%>">
  43 + <%= render :partial => partial_for_class(item.class, nil, 'processed'), :locals => {:task => item} %>
16 44 </li>
17 45 <% end %>
18 46 </ul>
  47 + <%= pagination_links(@tasks)%>
19 48 <% end %>
20 49 </p>
21 50  
22 51 <% button_bar do %>
23 52 <%= button(:back, _('Back'), :action => 'index') %>
24 53 <% end %>
  54 +
  55 +</div>
... ...
config/application.rb
... ... @@ -62,7 +62,7 @@ module Noosfero
62 62 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
63 63 # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
64 64 # config.time_zone = 'Central Time (US & Canada)'
65   - config.time_zone = 'Brasilia'
  65 + #config.time_zone = 'Brasilia'
66 66  
67 67  
68 68 # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
... ...
config/initializers/eager_load.rb
1   -Rails.application.eager_load!
  1 +Rails.application.eager_load! if ActiveRecord::Base.connection.table_exists? 'categories'
... ...
config/initializers/unicorn.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +require_dependency 'noosfero/scheduler/defer'
  2 +
  3 +if defined? Unicorn
  4 + ObjectSpace.each_object Unicorn::HttpServer do |s|
  5 + s.extend Noosfero::Scheduler::Defer::Unicorn
  6 + end
  7 +end
  8 +
... ...
db/migrate/20150609105354_create_email_template.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class CreateEmailTemplate < ActiveRecord::Migration
  2 +
  3 + def change
  4 + create_table :email_templates do |t|
  5 + t.string :name
  6 + t.string :template_type
  7 + t.string :subject
  8 + t.text :body
  9 + t.references :owner, :polymorphic => true
  10 + t.timestamps
  11 + end
  12 + end
  13 +
  14 +end
... ...
db/migrate/20150617222204_event_period_with_support_to_datetime.rb 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +class EventPeriodWithSupportToDatetime < ActiveRecord::Migration
  2 + def up
  3 + change_table :articles do |t|
  4 + t.change :start_date, :datetime
  5 + end
  6 +
  7 + change_table :articles do |t|
  8 + t.change :end_date, :datetime
  9 + end
  10 +
  11 + change_table :article_versions do |t|
  12 + t.change :start_date, :datetime
  13 + end
  14 +
  15 + change_table :article_versions do |t|
  16 + t.change :end_date, :datetime
  17 + end
  18 + end
  19 +
  20 + def down
  21 + change_table :articles do |t|
  22 + t.change :start_date, :date
  23 + end
  24 +
  25 + change_table :articles do |t|
  26 + t.change :end_date, :date
  27 + end
  28 +
  29 + change_table :article_versions do |t|
  30 + t.change :start_date, :date
  31 + end
  32 +
  33 + change_table :article_versions do |t|
  34 + t.change :end_date, :date
  35 + end
  36 + end
  37 +end
0 38 \ No newline at end of file
... ...
db/migrate/20150712194411_change_task_end_date_from_date_to_date_time.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +class ChangeTaskEndDateFromDateToDateTime < ActiveRecord::Migration
  2 +
  3 + def up
  4 + change_column :tasks, :end_date, :datetime
  5 + end
  6 +
  7 + def down
  8 + change_column :tasks, :end_date, :date
  9 + end
  10 +end
... ...
db/migrate/20150722042714_change_article_date_to_datetime.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +class ChangeArticleDateToDatetime < ActiveRecord::Migration
  2 +
  3 + def up
  4 + change_table :articles do |t|
  5 + t.change :start_date, :datetime
  6 + t.change :end_date, :datetime
  7 + end
  8 +
  9 + change_table :article_versions do |t|
  10 + t.change :start_date, :datetime
  11 + t.change :end_date, :datetime
  12 + end
  13 + end
  14 +
  15 + def down
  16 + change_table :articles do |t|
  17 + t.change :start_date, :date
  18 + t.change :end_date, :date
  19 + end
  20 +
  21 + change_table :article_versions do |t|
  22 + t.change :start_date, :date
  23 + t.change :end_date, :date
  24 + end
  25 + end
  26 +
  27 +end
... ...
features/comment.feature
... ... @@ -37,7 +37,7 @@ Feature: comment
37 37 And I fill in "Title" with "Hey ho, let's go!"
38 38 And I fill in "Enter your comment" with "Hey ho, let's go!"
39 39 When I press "Post comment"
40   - Then I should see "Hey ho, let's go"
  40 + Then I should see "Hey ho, let"
41 41  
42 42 @selenium-fixme
43 43 Scenario: redirect to right place after comment a picture
... ...
lib/activities_counter_cache_job.rb
1 1 class ActivitiesCounterCacheJob
  2 +
  3 + # Changed to prevent sql injection vunerabillity (http://brakemanscanner.org/docs/warning_types/sql_injection/)
2 4 def perform
3   - person_activities_counts = ActiveRecord::Base.connection.execute("SELECT profiles.id, count(action_tracker.id) as count FROM profiles LEFT OUTER JOIN action_tracker ON profiles.id = action_tracker.user_id WHERE (action_tracker.created_at >= '#{ActionTracker::Record::RECENT_DELAY.days.ago.to_s(:db)}') AND ( (profiles.type = 'Person' ) ) GROUP BY profiles.id;")
4   - organization_activities_counts = ActiveRecord::Base.connection.execute("SELECT profiles.id, count(action_tracker.id) as count FROM profiles LEFT OUTER JOIN action_tracker ON profiles.id = action_tracker.target_id WHERE (action_tracker.created_at >= '#{ActionTracker::Record::RECENT_DELAY.days.ago.to_s(:db)}') AND ( (profiles.type = 'Community' OR profiles.type = 'Enterprise' OR profiles.type = 'Organization' ) ) GROUP BY profiles.id;")
  5 + person_activities_counts = ActiveRecord::Base.connection.execute("SELECT profiles.id, count(action_tracker.id) as count FROM profiles LEFT OUTER JOIN action_tracker ON profiles.id = action_tracker.user_id WHERE (action_tracker.created_at >= #{ActiveRecord::Base.connection.quote(ActionTracker::Record::RECENT_DELAY.days.ago.to_s(:db))}) AND ( (profiles.type = 'Person' ) ) GROUP BY profiles.id;")
  6 + organization_activities_counts = ActiveRecord::Base.connection.execute("SELECT profiles.id, count(action_tracker.id) as count FROM profiles LEFT OUTER JOIN action_tracker ON profiles.id = action_tracker.target_id WHERE (action_tracker.created_at >= #{ActiveRecord::Base.connection.quote(ActionTracker::Record::RECENT_DELAY.days.ago.to_s(:db))}) AND ( (profiles.type = 'Community' OR profiles.type = 'Enterprise' OR profiles.type = 'Organization' ) ) GROUP BY profiles.id;")
5 7 activities_counts = person_activities_counts.entries + organization_activities_counts.entries
6 8 activities_counts.each do |count|
7   - ActiveRecord::Base.connection.execute("UPDATE profiles SET activities_count=#{count['count'].to_i} WHERE profiles.id=#{count['id']};")
  9 + update_sql = ActiveRecord::Base.__send__(:sanitize_sql, ["UPDATE profiles SET activities_count=? WHERE profiles.id=?;", count['count'].to_i, count['id'] ], '')
  10 + ActiveRecord::Base.connection.execute(update_sql)
8 11 end
9 12 Delayed::Job.enqueue(ActivitiesCounterCacheJob.new, {:priority => -3, :run_at => 1.day.from_now})
10 13 end
  14 +
11 15 end
... ...
lib/add_members_job.rb
... ... @@ -4,7 +4,12 @@ class AddMembersJob &lt; Struct.new(:people_ids, :profile_id, :locale)
4 4 Noosfero.with_locale(locale) do
5 5  
6 6 profile = Profile.find(profile_id)
7   - profile.add_members people_ids
  7 +
  8 + if people_ids.first =~ /\@/
  9 + profile.add_members_by_email people_ids
  10 + else
  11 + profile.add_members_by_id people_ids
  12 + end
8 13  
9 14 end
10 15 end
... ...
lib/noosfero/api/api.rb
... ... @@ -12,6 +12,8 @@ module Noosfero
12 12 use GrapeLogging::Middleware::RequestLogger, { logger: logger }
13 13  
14 14 rescue_from :all do |e|
  15 + puts e.inspect
  16 + puts e.backtrace.inspect
15 17 logger.error e
16 18 end
17 19  
... ...
lib/noosfero/api/helpers.rb
... ... @@ -160,8 +160,27 @@ require &#39;grape&#39;
160 160 conditions
161 161 end
162 162  
163   - def make_order_with_parameters(params)
164   - params[:order] || "created_at DESC"
  163 + # changing make_order_with_parameters to avoid sql injection
  164 + def make_order_with_parameters(object, method, params)
  165 + order = "created_at DESC"
  166 + unless params[:order].blank?
  167 + if params[:order].include? '\'' or params[:order].include? '"'
  168 + order = "created_at DESC"
  169 + elsif ['RANDOM()', 'RANDOM'].include? params[:order].upcase
  170 + order = 'RANDOM()'
  171 + else
  172 + field_name, direction = params[:order].split(' ')
  173 + assoc = object.class.reflect_on_association(method.to_sym)
  174 + if !field_name.blank? and assoc
  175 + if assoc.klass.attribute_names.include? field_name
  176 + if direction.present? and ['ASC','DESC'].include? direction.upcase
  177 + order = "#{field_name} #{direction.upcase}"
  178 + end
  179 + end
  180 + end
  181 + end
  182 + end
  183 + return order
165 184 end
166 185  
167 186 def by_reference(scope, params)
... ... @@ -176,7 +195,7 @@ require &#39;grape&#39;
176 195  
177 196 def select_filtered_collection_of(object, method, params)
178 197 conditions = make_conditions_with_parameter(params)
179   - order = make_order_with_parameters(params)
  198 + order = make_order_with_parameters(object,method,params)
180 199  
181 200 objects = object.send(method)
182 201 objects = by_reference(objects, params)
... ...
lib/noosfero/api/session.rb
... ... @@ -32,11 +32,10 @@ module Noosfero
32 32 params do
33 33 requires :email, type: String, desc: _("Email")
34 34 requires :login, type: String, desc: _("Login")
35   - requires :password, type: String, desc: _("Password")
36   - requires :password_confirmation, type: String, desc: _("Password confirmation")
  35 + #requires :password, type: String, desc: _("Password")
  36 + #requires :password_confirmation, type: String, desc: _("Password confirmation")
37 37 end
38 38 post "/register" do
39   - unique_attributes! User, [:email, :login]
40 39 attrs = attributes_for_keys [:email, :login, :password, :password_confirmation] + environment.signup_person_fields
41 40 remote_ip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
42 41 # test_captcha will render_api_error! and exit in case of any problem
... ...
lib/noosfero/plugin.rb
... ... @@ -138,11 +138,12 @@ class Noosfero::Plugin
138 138 filters = [filters]
139 139 end
140 140 filters.each do |plugin_filter|
  141 + plugin_filter[:options] ||= {}
  142 + plugin_filter[:options][:if] = -> { environment.plugin_enabled? plugin.module_name }
  143 +
141 144 filter_method = "#{plugin.identifier}_#{plugin_filter[:method_name]}".to_sym
142   - controller_class.send(plugin_filter[:type], filter_method, (plugin_filter[:options] || {}))
143   - controller_class.send(:define_method, filter_method) do
144   - instance_exec(&plugin_filter[:block]) if environment.plugin_enabled?(plugin)
145   - end
  145 + controller_class.send plugin_filter[:type], filter_method, plugin_filter[:options]
  146 + controller_class.send :define_method, filter_method, &plugin_filter[:block]
146 147 end
147 148 end
148 149  
... ...
lib/noosfero/plugin/parent_methods.rb
... ... @@ -11,6 +11,10 @@ class Noosfero::Plugin
11 11 @identifier ||= (if self.parents.first.instance_of? Module then self.parents.first else self end).name.underscore
12 12 end
13 13  
  14 + def module_name
  15 + @name ||= (if self.parents.first != Object then self.parents.first else self end).to_s
  16 + end
  17 +
14 18 def public_name
15 19 @public_name ||= self.identifier.gsub '_plugin', ''
16 20 end
... ...
lib/noosfero/scheduler/defer.rb 0 → 100644
... ... @@ -0,0 +1,97 @@
  1 +# based on https://github.com/discourse/discourse/blob/master/lib/scheduler/defer.rb
  2 +
  3 +module Noosfero
  4 + module Scheduler
  5 + module Deferrable
  6 + def initialize
  7 + # FIXME: do some other way when not using Unicorn
  8 + @async = (not Rails.env.test?) and defined? Unicorn
  9 + @queue = Queue.new
  10 + @mutex = Mutex.new
  11 + @paused = false
  12 + @thread = nil
  13 + end
  14 +
  15 + def pause
  16 + stop!
  17 + @paused = true
  18 + end
  19 +
  20 + def resume
  21 + @paused = false
  22 + end
  23 +
  24 + # for test
  25 + def async= val
  26 + @async = val
  27 + end
  28 +
  29 + def later desc = nil, &blk
  30 + if @async
  31 + start_thread unless (@thread && @thread.alive?) || @paused
  32 + @queue << [blk, desc]
  33 + else
  34 + blk.call
  35 + end
  36 + end
  37 +
  38 + def stop!
  39 + @thread.kill if @thread and @thread.alive?
  40 + @thread = nil
  41 + end
  42 +
  43 + # test only
  44 + def stopped?
  45 + !(@thread and @thread.alive?)
  46 + end
  47 +
  48 + def do_all_work
  49 + while !@queue.empty?
  50 + do_work _non_block=true
  51 + end
  52 + end
  53 +
  54 + private
  55 +
  56 + def start_thread
  57 + @mutex.synchronize do
  58 + return if @thread && @thread.alive?
  59 + @thread = Thread.new do
  60 + while true
  61 + do_work
  62 + end
  63 + end
  64 + @thread.priority = -2
  65 + end
  66 + end
  67 +
  68 + # using non_block to match Ruby #deq
  69 + def do_work non_block=false
  70 + job, desc = @queue.deq non_block
  71 + begin
  72 + job.call
  73 + rescue => ex
  74 + ExceptionNotifier.notify_exception ex, message: "Running deferred code '#{desc}'"
  75 + end
  76 + rescue => ex
  77 + ExceptionNotifier.notify_exception ex, message: "Processing deferred code queue"
  78 + end
  79 + end
  80 +
  81 + class Defer
  82 +
  83 + module Unicorn
  84 + def process_client client
  85 + ::Noosfero::Scheduler::Defer.pause
  86 + super client
  87 + ::Noosfero::Scheduler::Defer.do_all_work
  88 + ::Noosfero::Scheduler::Defer.resume
  89 + end
  90 + end
  91 +
  92 + extend Deferrable
  93 + initialize
  94 + end
  95 +
  96 + end
  97 +end
... ...
plugins/analytics/Gemfile 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +gem 'slim'
  2 +
... ...
plugins/analytics/controllers/profile/analytics_plugin/time_on_page_controller.rb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +class AnalyticsPlugin::TimeOnPageController < ProfileController
  2 +
  3 + before_filter :skip_page_view
  4 +
  5 + def page_load
  6 + # to avoid concurrency problems with the original deferred request, also defer this
  7 + Noosfero::Scheduler::Defer.later do
  8 + page_view = profile.page_views.where(request_id: params[:id]).first
  9 + page_view.request = request
  10 + page_view.page_load!
  11 + end
  12 +
  13 + render nothing: true
  14 + end
  15 +
  16 + def report
  17 + page_view = profile.page_views.where(request_id: params[:id]).first
  18 + page_view.request = request
  19 + page_view.increase_time_on_page!
  20 +
  21 + render nothing: true
  22 + end
  23 +
  24 + protected
  25 +
  26 + def skip_page_view
  27 + @analytics_skip_page_view = true
  28 + end
  29 +
  30 +end
... ...
plugins/analytics/db/migrate/20150715001149_init_analytics_plugin.rb 0 → 100644
... ... @@ -0,0 +1,47 @@
  1 +class InitAnalyticsPlugin < ActiveRecord::Migration
  2 +
  3 + def up
  4 + create_table :analytics_plugin_visits do |t|
  5 + t.integer :profile_id
  6 + end
  7 +
  8 + create_table :analytics_plugin_page_views do |t|
  9 + t.string :type
  10 + t.integer :visit_id
  11 + t.integer :track_id
  12 + t.integer :referer_page_view_id
  13 + t.string :request_id
  14 +
  15 + t.integer :user_id
  16 + t.integer :session_id
  17 + t.integer :profile_id
  18 +
  19 + t.text :url
  20 + t.text :referer_url
  21 +
  22 + t.text :user_agent
  23 + t.string :remote_ip
  24 +
  25 + t.datetime :request_started_at
  26 + t.datetime :request_finished_at
  27 + t.datetime :page_loaded_at
  28 + t.integer :time_on_page, default: 0
  29 +
  30 + t.text :data, default: {}.to_yaml
  31 + end
  32 + add_index :analytics_plugin_page_views, :request_id
  33 + add_index :analytics_plugin_page_views, :referer_page_view_id
  34 +
  35 + add_index :analytics_plugin_page_views, :user_id
  36 + add_index :analytics_plugin_page_views, :session_id
  37 + add_index :analytics_plugin_page_views, :profile_id
  38 + add_index :analytics_plugin_page_views, :url
  39 + add_index :analytics_plugin_page_views, [:user_id, :session_id, :profile_id, :url], name: :analytics_plugin_referer_find
  40 + end
  41 +
  42 + def down
  43 + drop_table :analytics_plugin_visits
  44 + drop_table :analytics_plugin_page_views
  45 + end
  46 +
  47 +end
... ...
plugins/analytics/lib/analytics_plugin.rb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +module AnalyticsPlugin
  2 +
  3 + TimeOnPageUpdateInterval = 2.minutes * 1000
  4 +
  5 + extend Noosfero::Plugin::ParentMethods
  6 +
  7 + def self.plugin_name
  8 + I18n.t'analytics_plugin.lib.plugin.name'
  9 + end
  10 +
  11 + def self.plugin_description
  12 + I18n.t'analytics_plugin.lib.plugin.description'
  13 + end
  14 +
  15 +end
... ...
plugins/analytics/lib/analytics_plugin/base.rb 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +
  2 +class AnalyticsPlugin::Base < Noosfero::Plugin
  3 +
  4 + def body_ending
  5 + return unless profile and profile.analytics_enabled?
  6 + lambda do
  7 + render 'analytics_plugin/body_ending'
  8 + end
  9 + end
  10 +
  11 + def js_files
  12 + ['analytics'].map{ |j| "javascripts/#{j}" }
  13 + end
  14 +
  15 + def application_controller_filters
  16 + [{
  17 + type: 'around_filter', options: {}, block: -> &block do
  18 + request_started_at = Time.now
  19 + block.call
  20 + request_finished_at = Time.now
  21 +
  22 + return if @analytics_skip_page_view
  23 + return unless profile and profile.analytics_enabled?
  24 +
  25 + Noosfero::Scheduler::Defer.later 'analytics: register page view' do
  26 + page_view = profile.page_views.build request: request, profile_id: profile,
  27 + request_started_at: request_started_at, request_finished_at: request_finished_at
  28 +
  29 + unless profile.analytics_anonymous?
  30 + # FIXME: use session.id in Rails 4
  31 + session_id = Marshal.load(Base64.decode64 request['_session_id'])['session_id'] rescue nil
  32 + #session_id = request.session_options[:id]
  33 + page_view.user = user
  34 + page_view.session_id = session_id
  35 + end
  36 +
  37 + page_view.save!
  38 + end
  39 + end,
  40 + }]
  41 + end
  42 +
  43 +end
... ...
plugins/analytics/lib/ext/profile.rb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +require_dependency 'profile'
  2 +require_dependency 'community'
  3 +
  4 +([Profile] + Profile.descendants).each do |subclass|
  5 +subclass.class_eval do
  6 +
  7 + has_many :visits, foreign_key: :profile_id, class_name: 'AnalyticsPlugin::Visit'
  8 + has_many :page_views, foreign_key: :profile_id, class_name: 'AnalyticsPlugin::PageView'
  9 +
  10 +end
  11 +end
  12 +
  13 +class Profile
  14 +
  15 + def analytics_settings attrs = {}
  16 + @analytics_settings ||= Noosfero::Plugin::Settings.new self, AnalyticsPlugin, attrs
  17 + attrs.each{ |a, v| @analytics_settings.send "#{a}=", v }
  18 + @analytics_settings
  19 + end
  20 + alias_method :analytics_settings=, :analytics_settings
  21 +
  22 + def analytics_enabled?
  23 + self.analytics_settings.enabled
  24 + end
  25 +
  26 + def analytics_anonymous?
  27 + self.analytics_settings.anonymous
  28 + end
  29 +
  30 +end
... ...
plugins/analytics/locales/en.yml 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +
  2 +en: &en
  3 + analytics_plugin:
  4 + lib:
  5 + plugin:
  6 + name: 'Access tracking'
  7 + description: 'Register the access of selected profiles'
  8 +
  9 +en-US:
  10 + <<: *en
  11 +
... ...
plugins/analytics/locales/pt.yml 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +
  2 +pt: &pt
  3 + analytics_plugin:
  4 + lib:
  5 + plugin:
  6 + name: 'Rastreio de accesso'
  7 + description: 'Registra o acesso de perfis selecionados'
  8 +
  9 +pt-BR:
  10 + <<: *pt
... ...
plugins/analytics/models/analytics_plugin/page_view.rb 0 → 100644
... ... @@ -0,0 +1,67 @@
  1 +class AnalyticsPlugin::PageView < ActiveRecord::Base
  2 +
  3 + serialize :data
  4 +
  5 + attr_accessible *self.column_names
  6 + attr_accessible :user, :profile
  7 +
  8 + attr_accessor :request
  9 + attr_accessible :request
  10 +
  11 + acts_as_having_settings field: :options
  12 +
  13 + belongs_to :visit, class_name: 'AnalyticsPlugin::Visit'
  14 + belongs_to :referer_page_view, class_name: 'AnalyticsPlugin::PageView'
  15 +
  16 + belongs_to :user, class_name: 'Person'
  17 + belongs_to :session, primary_key: :session_id, foreign_key: :session_id, class_name: 'Session'
  18 + belongs_to :profile
  19 +
  20 + validates_presence_of :visit
  21 + validates_presence_of :request, on: :create
  22 + validates_presence_of :url
  23 +
  24 + before_validation :extract_request_data, on: :create
  25 + before_validation :fill_referer_page_view, on: :create
  26 + before_validation :fill_visit, on: :create
  27 +
  28 + def request_duration
  29 + self.request_finished_at - self.request_started_at
  30 + end
  31 +
  32 + def page_load!
  33 + self.page_loaded_at = Time.now
  34 + self.update_column :page_loaded_at, self.page_loaded_at
  35 + end
  36 +
  37 + def increase_time_on_page!
  38 + now = Time.now
  39 + initial_time = self.page_loaded_at || self.request_finished_at
  40 + return unless now > initial_time
  41 +
  42 + self.time_on_page = now - initial_time
  43 + self.update_column :time_on_page, self.time_on_page
  44 + end
  45 +
  46 + protected
  47 +
  48 + def extract_request_data
  49 + self.url = self.request.url.sub /\/+$/, ''
  50 + self.referer_url = self.request.referer
  51 + self.user_agent = self.request.headers['User-Agent']
  52 + self.request_id = self.request.env['action_dispatch.request_id']
  53 + self.remote_ip = self.request.remote_ip
  54 + end
  55 +
  56 + def fill_referer_page_view
  57 + self.referer_page_view = AnalyticsPlugin::PageView.order('request_started_at DESC').
  58 + where(url: self.referer_url, session_id: self.session_id, user_id: self.user_id, profile_id: self.profile_id).first if self.referer_url.present?
  59 + end
  60 +
  61 + def fill_visit
  62 + self.visit = self.referer_page_view.visit if self.referer_page_view
  63 + self.visit ||= AnalyticsPlugin::Visit.new profile: profile
  64 + end
  65 +
  66 +end
  67 +
... ...
plugins/analytics/models/analytics_plugin/visit.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class AnalyticsPlugin::Visit < ActiveRecord::Base
  2 +
  3 + attr_accessible *self.column_names
  4 + attr_accessible :profile
  5 +
  6 + default_scope -> { includes :page_views }
  7 +
  8 + belongs_to :profile
  9 + has_many :page_views, class_name: 'AnalyticsPlugin::PageView', dependent: :destroy
  10 +
  11 +end
... ...
plugins/analytics/po/pt/analytics.po 0 → 100644
... ... @@ -0,0 +1,29 @@
  1 +# translation of analytic.po to portuguese
  2 +# Krishnamurti Lelis Lima Vieira Nunes <krishna@colivre.coop.br>, 2007.
  3 +# noosfero - Brazilian Portuguese translation
  4 +# Copyright (C) 2007,
  5 +# Forum Brasileiro de Economia Solidaria <http://www.fbes.org.br/>
  6 +# Copyright (C) 2007,
  7 +# Ynternet.org Foundation <http://www.ynternet.org/>
  8 +# This file is distributed under the same license as noosfero itself.
  9 +# Joenio Costa <joenio@colivre.coop.br>, 2008.
  10 +#
  11 +#
  12 +msgid ""
  13 +msgstr ""
  14 +"Project-Id-Version: 1.0-690-gcb6e853\n"
  15 +"POT-Creation-Date: 2015-03-05 12:10-0300\n"
  16 +"PO-Revision-Date: 2015-07-21 09:23-0300\n"
  17 +"Last-Translator: Michal Čihař <michal@cihar.com>\n"
  18 +"Language-Team: Portuguese <https://hosted.weblate.org/projects/noosfero"
  19 +"/plugin-solr/pt/>\n"
  20 +"Language: pt\n"
  21 +"MIME-Version: 1.0\n"
  22 +"Content-Type: text/plain; charset=UTF-8\n"
  23 +"Content-Transfer-Encoding: 8bit\n"
  24 +"Plural-Forms: nplurals=2; plural=n != 1;\n"
  25 +"X-Generator: Weblate 2.3-dev\n"
  26 +
  27 +msgid "Select the set of communities and users to track"
  28 +msgstr "Seleciona o conjunto de comunidades e usuários para rastrear"
  29 +
... ...
plugins/analytics/public/javascripts/analytics.js 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +analytics = {
  2 + requestId: '',
  3 +
  4 + timeOnPage: {
  5 + updateInterval: 0,
  6 + baseUrl: '',
  7 +
  8 + report: function() {
  9 + $.ajax(analytics.timeOnPage.baseUrl+'/report', {
  10 + type: 'POST', data: {id: analytics.requestId},
  11 + success: function(data) {
  12 +
  13 + analytics.timeOnPage.poll()
  14 + },
  15 + })
  16 + },
  17 +
  18 + poll: function() {
  19 + if (analytics.timeOnPage.updateInterval)
  20 + setTimeout(analytics.timeOnPage.report, analytics.timeOnPage.updateInterval)
  21 + },
  22 + },
  23 +
  24 + init: function() {
  25 + analytics.timeOnPage.poll()
  26 + },
  27 +
  28 + pageLoad: function() {
  29 + $.ajax(analytics.timeOnPage.baseUrl+'/page_load', {
  30 + type: 'POST', data: {id: analytics.requestId},
  31 + success: function(data) {
  32 + },
  33 + });
  34 + }
  35 +
  36 +};
  37 +
  38 +$(document).ready(analytics.pageLoad)
  39 +
... ...
plugins/analytics/test/functional/content_viewer_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,48 @@
  1 +require 'test_helper'
  2 +require 'content_viewer_controller'
  3 +
  4 +class ContentViewerControllerTest < ActionController::TestCase
  5 +
  6 + def setup
  7 + @controller = ContentViewerController.new
  8 + @request = ActionController::TestRequest.new
  9 + @response = ActionController::TestResponse.new
  10 +
  11 + @environment = Environment.default
  12 + @environment.enabled_plugins += ['AnalyticsPlugin']
  13 + @environment.save!
  14 +
  15 + @user = create_user('testinguser').person
  16 + login_as @user.identifier
  17 +
  18 + @community = build Community, identifier: 'testcomm', name: 'test'
  19 + @community.analytics_settings.enabled = true
  20 + @community.analytics_settings.anonymous = false
  21 + @community.save!
  22 + @community.add_member @user
  23 + end
  24 +
  25 + should 'register page view correctly' do
  26 + @request.env['HTTP_REFERER'] = 'http://google.com'
  27 + first_url = 'http://test.host'
  28 + get :view_page, profile: @community.identifier, page: []
  29 + assert_equal 1, @community.page_views.count
  30 + assert_equal 1, @community.visits.count
  31 +
  32 + first_page_view = @community.page_views.order(:id).first
  33 + assert_equal @request.referer, first_page_view.referer_url
  34 +
  35 + @request.env['HTTP_REFERER'] = first_url
  36 + get :view_page, profile: @community.identifier, page: @community.articles.last.path.split('/')
  37 + assert_equal 2, @community.page_views.count
  38 + assert_equal 1, @community.visits.count
  39 +
  40 + second_page_view = @community.page_views.order(:id).last
  41 + assert_equal first_page_view, second_page_view.referer_page_view
  42 +
  43 + assert_equal @user, second_page_view.user
  44 +
  45 + assert second_page_view.request_duration > 0 and second_page_view.request_duration < 1
  46 + end
  47 +
  48 +end
... ...
plugins/analytics/views/analytics_plugin/_body_ending.html.slim 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +javascript:
  2 + analytics.timeOnPage.baseUrl = #{url_for(controller: 'analytics_plugin/time_on_page').to_json}
  3 + analytics.timeOnPage.updateInterval = #{AnalyticsPlugin::TimeOnPageUpdateInterval.to_json}
  4 + analytics.requestId = #{request.env['action_dispatch.request_id'].to_json}
  5 + analytics.init()
  6 +
... ...
plugins/comment_classification/po/fr/comment_classification.po
... ... @@ -7,8 +7,8 @@ msgstr &quot;&quot;
7 7 "Project-Id-Version: 1.1-166-gaf47713\n"
8 8 "Report-Msgid-Bugs-To: \n"
9 9 "POT-Creation-Date: 2015-06-01 17:26-0300\n"
10   -"PO-Revision-Date: 2015-07-03 00:34+0200\n"
11   -"Last-Translator: Christophe DANIEL <papaeng@gmail.com>\n"
  10 +"PO-Revision-Date: 2015-03-07 12:26+0200\n"
  11 +"Last-Translator: Tuux <tuxa@galaxie.eu.org>\n"
12 12 "Language-Team: French <https://hosted.weblate.org/projects/noosfero/plugin-"
13 13 "comment-classification/fr/>\n"
14 14 "Language: fr\n"
... ... @@ -16,7 +16,7 @@ msgstr &quot;&quot;
16 16 "Content-Type: text/plain; charset=UTF-8\n"
17 17 "Content-Transfer-Encoding: 8bit\n"
18 18 "Plural-Forms: nplurals=2; plural=n > 1;\n"
19   -"X-Generator: Weblate 2.4-dev\n"
  19 +"X-Generator: Weblate 2.3-dev\n"
20 20  
21 21 #: plugins/comment_classification/lib/comment_classification_plugin.rb:11
22 22 msgid "A plugin that allow classification of comments."
... ... @@ -80,7 +80,7 @@ msgstr &quot;Statuts&quot;
80 80  
81 81 #: plugins/comment_classification/views/comment_classification_plugin_myprofile/_status_form.html.erb:7
82 82 msgid "Reason:"
83   -msgstr "Raison :"
  83 +msgstr "Raison:"
84 84  
85 85 #: plugins/comment_classification/views/comment_classification_plugin_myprofile/add_status.html.erb:1
86 86 msgid "Status for comment"
... ... @@ -121,7 +121,7 @@ msgstr &quot;Couleur&quot;
121 121  
122 122 #: plugins/comment_classification/views/comment_classification_plugin_labels/_form.html.erb:8
123 123 msgid "Enable this label?"
124   -msgstr "Activer ce label ?"
  124 +msgstr "Activer ce label?"
125 125  
126 126 #: plugins/comment_classification/views/comment_classification_plugin_labels/edit.html.erb:1
127 127 msgid "Editing label %s"
... ... @@ -149,7 +149,7 @@ msgstr &quot;Label&quot;
149 149 #: plugins/comment_classification/views/comment_classification_plugin_labels/index.html.erb:11
150 150 #: plugins/comment_classification/views/comment_classification_plugin_status/index.html.erb:10
151 151 msgid "Enabled"
152   -msgstr "Activé(e)"
  152 +msgstr "Activé"
153 153  
154 154 #: plugins/comment_classification/views/comment_classification_plugin_labels/index.html.erb:12
155 155 #: plugins/comment_classification/views/comment_classification_plugin_status/index.html.erb:12
... ... @@ -162,7 +162,7 @@ msgstr &quot;Êtes-vous sûr(e) que vous voulez retirer ce label ?&quot;
162 162  
163 163 #: plugins/comment_classification/views/comment_classification_plugin_status/_form.html.erb:7
164 164 msgid "Enable this status?"
165   -msgstr "Activer ce statut ?"
  165 +msgstr "Activer ce statut?"
166 166  
167 167 #: plugins/comment_classification/views/comment_classification_plugin_status/edit.html.erb:1
168 168 msgid "Editing status %s"
... ... @@ -177,12 +177,13 @@ msgid &quot;(no status registered yet)&quot;
177 177 msgstr "(il n'y a pas de statut enregistré pour le moment)"
178 178  
179 179 #: plugins/comment_classification/views/comment_classification_plugin_status/index.html.erb:11
  180 +#, fuzzy
180 181 msgid "Reason enabled?"
181   -msgstr "Raison activée ?"
  182 +msgstr "%s n'était pas activé(e)"
182 183  
183 184 #: plugins/comment_classification/views/comment_classification_plugin_status/index.html.erb:21
184 185 msgid "Are you sure you want to remove this status?"
185   -msgstr "Êtes-vous sûr(e) de vouloir supprimer ce statut ?"
  186 +msgstr "Êtes-vous sûr(e) de vouloir supprimer ce statut?"
186 187  
187 188 #: plugins/comment_classification/views/comment/comments_labels_select.html.erb:3
188 189 msgid "[Select ...]"
... ...
plugins/community_hub/Gemfile
  1 +source 'https://rubygems.org'
1 2 gem 'twitter', '~> 5.8.0'
2 3 gem 'proxifier', '~> 1.0.3'
... ...
plugins/community_track/lib/community_track_plugin/step.rb
... ... @@ -76,20 +76,20 @@ class CommunityTrackPlugin::Step &lt; Folder
76 76 end
77 77  
78 78 def active?
79   - (start_date..end_date).include?(Date.today)
  79 + (start_date.to_date..end_date.to_date).include?(Date.today)
80 80 end
81 81  
82 82 def finished?
83   - Date.today > end_date
  83 + Date.today > end_date.to_date
84 84 end
85 85  
86 86 def waiting?
87   - Date.today < start_date
  87 + Date.today < start_date.to_date
88 88 end
89 89  
90 90 def schedule_activation
91 91 return if !changes['start_date'] && !changes['end_date']
92   - if Date.today <= end_date || accept_comments
  92 + if Date.today <= end_date.to_date || accept_comments
93 93 schedule_date = !accept_comments ? start_date : end_date + 1.day
94 94 CommunityTrackPlugin::ActivationJob.find(id).destroy_all
95 95 Delayed::Job.enqueue(CommunityTrackPlugin::ActivationJob.new(self.id), :run_at => schedule_date)
... ...
plugins/custom_forms/po/fr/custom_forms.po
... ... @@ -7,7 +7,7 @@ msgstr &quot;&quot;
7 7 "Project-Id-Version: 1.1-166-gaf47713\n"
8 8 "Report-Msgid-Bugs-To: \n"
9 9 "POT-Creation-Date: 2015-06-01 17:26-0300\n"
10   -"PO-Revision-Date: 2015-07-03 00:36+0200\n"
  10 +"PO-Revision-Date: 2015-06-29 14:17+0200\n"
11 11 "Last-Translator: Christophe DANIEL <papaeng@gmail.com>\n"
12 12 "Language-Team: French <https://hosted.weblate.org/projects/noosfero/plugin-"
13 13 "custom-forms/fr/>\n"
... ... @@ -20,12 +20,13 @@ msgstr &quot;&quot;
20 20  
21 21 #: plugins/custom_forms/lib/custom_forms_plugin/form.rb:67
22 22 msgid "Invalid string format of access."
23   -msgstr "Format de chaîne d'accès non valide."
  23 +msgstr ""
24 24  
25 25 #: plugins/custom_forms/lib/custom_forms_plugin/form.rb:71
26 26 #: plugins/custom_forms/lib/custom_forms_plugin/form.rb:76
  27 +#, fuzzy
27 28 msgid "There is no profile with the provided id."
28   -msgstr "Il n'y a pas le profil avec l'ID fourni."
  29 +msgstr "Il y a un problème avec les lignes suivantes : "
29 30  
30 31 #: plugins/custom_forms/lib/custom_forms_plugin/form.rb:81
31 32 msgid "Invalid type format of access."
... ...
plugins/display_content/po/fr/display_content.po
... ... @@ -7,8 +7,8 @@ msgstr &quot;&quot;
7 7 "Project-Id-Version: 1.1-166-gaf47713\n"
8 8 "Report-Msgid-Bugs-To: \n"
9 9 "POT-Creation-Date: 2015-06-01 17:26-0300\n"
10   -"PO-Revision-Date: 2015-07-03 00:31+0200\n"
11   -"Last-Translator: Christophe DANIEL <papaeng@gmail.com>\n"
  10 +"PO-Revision-Date: 2015-03-07 02:11+0200\n"
  11 +"Last-Translator: Tuux <tuxa@galaxie.eu.org>\n"
12 12 "Language-Team: French <https://hosted.weblate.org/projects/noosfero/plugin-"
13 13 "display-content/fr/>\n"
14 14 "Language: fr\n"
... ... @@ -16,14 +16,14 @@ msgstr &quot;&quot;
16 16 "Content-Type: text/plain; charset=UTF-8\n"
17 17 "Content-Transfer-Encoding: 8bit\n"
18 18 "Plural-Forms: nplurals=2; plural=n > 1;\n"
19   -"X-Generator: Weblate 2.4-dev\n"
  19 +"X-Generator: Weblate 2.3-dev\n"
20 20  
21 21 #: plugins/display_content/lib/display_content_plugin.rb:10
22 22 msgid ""
23 23 "A plugin that adds a block where you could choose any of your content and "
24 24 "display it."
25 25 msgstr ""
26   -"Un plugin qui permet d'ajouter une zone ou vous pourrez y afficher le "
  26 +"Un greffon qui permet d'ajouter une zone ou vous pourrez y afficher le "
27 27 "contenue de votre choix."
28 28  
29 29 #: plugins/display_content/lib/display_content_block.rb:34
... ... @@ -48,33 +48,33 @@ msgstr &quot;Résumé&quot;
48 48  
49 49 #: plugins/display_content/lib/display_content_block.rb:151
50 50 msgid "Read more"
51   -msgstr "Lire plus"
  51 +msgstr "En lire plus"
52 52  
53 53 #: plugins/display_content/lib/display_content_block.rb:194
54 54 msgid "%{month}/%{day}/%{year}"
55   -msgstr "%{day}/%{month}/%{year}"
  55 +msgstr "%{mois}/%{jour}/%{année}"
56 56  
57 57 #: plugins/display_content/lib/display_content_block.rb:194
58 58 msgid "%{month}/%{day}"
59   -msgstr "%{day}/%{month}"
  59 +msgstr "%{mois}/%{jour}"
60 60  
61 61 #: plugins/display_content/lib/display_content_block.rb:197
62 62 msgid "%{month_name} %{day}, %{year}"
63   -msgstr "%{day} %{month_name} %{year}"
  63 +msgstr "%{nom_du_mois} %{jour}, %{année}"
64 64  
65 65 #: plugins/display_content/lib/display_content_block.rb:197
66 66 msgid "%{month_name} %{day}"
67   -msgstr "%{day} %{month_name}"
  67 +msgstr "%{nom_du_mois} %{jour}"
68 68  
69 69 #: plugins/display_content/views/box_organizer/_display_content_block.html.erb:5
70 70 msgid "Choose which attributes should be displayed and drag to reorder them:"
71 71 msgstr ""
72 72 "Choisissez quels attribues doivent êtres afficher et glisser/déposer pour "
73   -"les réorganiser :"
  73 +"les réorganiser:"
74 74  
75 75 #: plugins/display_content/views/box_organizer/_display_content_block.html.erb:21
76 76 msgid "Choose which content should be displayed:"
77   -msgstr "Choisissez le contenu à afficher :"
  77 +msgstr "Choisissez le contenu à afficher:"
78 78  
79 79 #: plugins/display_content/views/box_organizer/_display_content_block.html.erb:23
80 80 msgid "Choose directly"
... ... @@ -86,7 +86,7 @@ msgstr &quot;Choisir par type de contenu&quot;
86 86  
87 87 #: plugins/display_content/views/box_organizer/_display_content_block.html.erb:28
88 88 msgid "Order by:"
89   -msgstr "Trier par :"
  89 +msgstr "Trier par:"
90 90  
91 91 #: plugins/display_content/views/box_organizer/_display_content_block.html.erb:29
92 92 msgid "Most recent"
... ... @@ -98,7 +98,7 @@ msgstr &quot;Le plus ancien&quot;
98 98  
99 99 #: plugins/display_content/views/box_organizer/_choose_by_content_type.html.erb:1
100 100 msgid "Display content types:"
101   -msgstr "Afficher le type de contenu :"
  101 +msgstr "Afficher le types de contenue:"
102 102  
103 103 #: plugins/display_content/views/box_organizer/_choose_by_content_type.html.erb:7
104 104 msgid "more"
... ... @@ -106,8 +106,9 @@ msgstr &quot;plus&quot;
106 106  
107 107 #: plugins/display_content/views/box_organizer/_choose_directly.html.erb:5
108 108 msgid "Dinamically load children of selected folders"
109   -msgstr "charger dynamiquement les sous répertoires des répertoires sélectionnés"
  109 +msgstr ""
  110 +"Dynamiquement charger les sous répertoires des répertoires sélectionnés"
110 111  
111 112 #: plugins/display_content/views/box_organizer/_choose_directly.html.erb:9
112 113 msgid "Limit:"
113   -msgstr "Limite :"
  114 +msgstr "Limite:"
... ...
plugins/gamification
1   -Subproject commit 1f2171520eb4719fefc5a32f26ef2a002cd61f0d
  1 +Subproject commit 7a9aa2a643ccbbe6d4ee2f085a5a15540a6b349d
... ...
plugins/gravatar-provider 0 → 160000
... ... @@ -0,0 +1 @@
  1 +Subproject commit 7d1cef867e1325f623638366faeb5387a9261b0a
... ...
plugins/juventude 0 → 160000
... ... @@ -0,0 +1 @@
  1 +Subproject commit 8fb44a478abb5ccd345b8ffaf1f37817d273ad30
... ...
plugins/oauth_client/Gemfile
  1 +source 'https://rubygems.org'
1 2 gem 'omniauth', '~> 1.2.2'
2 3 gem 'omniauth-facebook', '~> 2.0.0'
3 4 gem "omniauth-google-oauth2", '~> 0.2.6'
... ...
plugins/oauth_client/controllers/public/oauth_client_plugin_public_controller.rb
... ... @@ -22,7 +22,7 @@ class OauthClientPluginPublicController &lt; PublicController
22 22 if session.delete(:oauth_client_popup) || params[:oauth_client_popup]
23 23 current_user.private_token_expired? if current_user.present?
24 24 private_token = current_user.present? ? current_user.private_token : ''
25   - render 'oauth_client_plugin_public/finish', :locals => {:private_token => private_token, :user => params[:user]}, :layout => false
  25 + render 'oauth_client_plugin_public/finish', :locals => {:private_token => private_token}, :layout => false
26 26 else
27 27 redirect_to :controller => :home
28 28 end
... ... @@ -42,22 +42,47 @@ class OauthClientPluginPublicController &lt; PublicController
42 42 else
43 43 session[:notice] = _("Can't login with #{provider.name}")
44 44 end
45   - session[:oauth_client_popup] = true if request.env["omniauth.params"]['oauth_client_popup']
46   - session[:return_to] = url_for(:controller => :oauth_client_plugin_public, :action => :finish)
  45 + session[:oauth_client_popup] = true if request.env.fetch("omniauth.params", {})['oauth_client_popup']
  46 + session[:return_to] = url_for(
  47 + :controller => :oauth_client_plugin_public,
  48 + :action => :finish,
  49 + :user => {
  50 + :login => current_user.login,
  51 + :person => {:identifier => current_user.person.identifier, :name => current_user.person.name}
  52 + } ,
  53 + :profile_data => {:name => current_user.person.name},
  54 + :oauth_client_popup => session[:oauth_client_popup]
  55 + )
47 56  
48 57 redirect_to :controller => :account, :action => :login
49 58 end
50 59  
51 60 def signup(auth)
52 61 login = auth.info.email.split('@').first
  62 +
  63 + # reading provider from session and writing to cache to read when
  64 + # api calls register to confirm signup
  65 + auth_cach_hash = auth.to_hash
  66 + auth_cach_hash[:provider_id] = session[:provider_id]
  67 + signup_token = OauthClientPlugin::SignupDataStore.store_oauth_data(auth.info.email, auth_cach_hash)
  68 +
53 69 session[:oauth_data] = auth
54   - session[:oauth_client_popup] = true if request.env["omniauth.params"]['oauth_client_popup']
  70 + session[:oauth_client_popup] = true if request.env.fetch("omniauth.params", {})['oauth_client_popup']
55 71 session[:return_to] = url_for(:controller => :oauth_client_plugin_public, :action => :finish)
56 72 name = auth.info.name
57 73 name ||= auth.extra && auth.extra.raw_info ? auth.extra.raw_info.name : ''
58 74  
59 75 if session[:oauth_client_popup]
60   - redirect_to :controller => :oauth_client_plugin_public, :action => :finish, :user => {:login => login, :email => auth.info.email, :oauth_providers => [session[:provider_id]]}, :profile_data => {:name => name}, :oauth_client_popup => session[:oauth_client_popup]
  76 + redirect_to :controller => :oauth_client_plugin_public,
  77 + :action => :finish,
  78 + :user => {
  79 + :signup_token => signup_token,
  80 + :login => login,
  81 + :email => auth.info.email,
  82 + :oauth_providers => [session[:provider_id]]
  83 + },
  84 + :profile_data => {:name => name},
  85 + :oauth_client_popup => session[:oauth_client_popup]
61 86 else
62 87 redirect_to :controller => :account, :action => :signup, :user => {:login => login, :email => auth.info.email}, :profile_data => {:name => name}
63 88 end
... ...
plugins/oauth_client/db/migrate/20150714200000_add_oauth_auth_fields_to_user_provider.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +class AddOauthAuthFieldsToUserProvider < ActiveRecord::Migration
  2 +
  3 + def self.up
  4 + change_table :oauth_client_plugin_user_providers do |t|
  5 + t.text :oauth_data
  6 + end
  7 + end
  8 +
  9 + def self.down
  10 + remove_column :oauth_client_plugin_user_providers, :oauth_data
  11 + end
  12 +end
... ...
plugins/oauth_client/lib/ext/environment.rb
... ... @@ -4,4 +4,10 @@ class Environment
4 4  
5 5 has_many :oauth_providers, :class_name => 'OauthClientPlugin::Provider'
6 6  
  7 + def signup_person_fields_with_oauth
  8 + signup_person_fields_without_oauth + [:oauth_signup_token]
  9 + end
  10 +
  11 + alias_method_chain :signup_person_fields, :oauth
  12 +
7 13 end
... ...
plugins/oauth_client/lib/ext/user.rb
... ... @@ -6,24 +6,54 @@ class User
6 6 has_many :oauth_providers, :through => :oauth_user_providers, :source => :provider
7 7  
8 8 def password_required_with_oauth?
  9 + # user creation through api does not set oauth_providers
  10 + check_providers
9 11 password_required_without_oauth? && oauth_providers.empty?
10 12 end
11 13  
  14 + def oauth_data
  15 + @oauth_data
  16 + end
  17 +
  18 + def oauth_signup_token= value
  19 + @oauth_signup_token = value
  20 + end
  21 +
  22 + def oauth_signup_token
  23 + @oauth_signup_token
  24 + end
  25 +
12 26 alias_method_chain :password_required?, :oauth
13 27  
14 28 after_create :activate_oauth_user
15 29  
  30 + # user creation through api does not set oauth_providers
  31 + # so it is being shared through a distributed cache
  32 + def check_providers
  33 + if oauth_providers.empty? && oauth_signup_token.present?
  34 + #check if is oauth user, reading oauth_data recorded at cache store
  35 + @oauth_data = OauthClientPlugin::SignupDataStore.get_oauth_data(self.email, self.oauth_signup_token)
  36 + if @oauth_data
  37 + provider_id = @oauth_data.delete(:provider_id)
  38 + self.oauth_providers = [OauthClientPlugin::Provider.find(provider_id)]
  39 + end
  40 + end
  41 + end
  42 +
16 43 def activate_oauth_user
17   - unless oauth_providers.empty?
18   - activate
19   - oauth_providers.each do |provider|
20   - OauthClientPlugin::UserProvider.create!(:user => self, :provider => provider, :enabled => true)
  44 + self.oauth_providers.each do |provider|
  45 + OauthClientPlugin::UserProvider.create! do |user_provider|
  46 + user_provider.user = self
  47 + user_provider.provider = provider
  48 + user_provider.enabled = true
  49 + user_provider.oauth_data = oauth_data
21 50 end
22 51 end
  52 + activate unless oauth_providers.empty?
23 53 end
24 54  
25 55 def make_activation_code_with_oauth
26   - oauth_providers.blank? ? make_activation_code_without_oauth : nil
  56 + self.oauth_providers.blank? ? make_activation_code_without_oauth : nil
27 57 end
28 58  
29 59 alias_method_chain :make_activation_code, :oauth
... ...
plugins/oauth_client/lib/oauth_client_plugin.rb
... ... @@ -84,8 +84,11 @@ class OauthClientPlugin &lt; Noosfero::Plugin
84 84  
85 85 if auth.present? && params[:user].present?
86 86 params[:user][:oauth_providers] = [OauthClientPlugin::Provider.find(session[:provider_id])]
  87 +
87 88 if request.post? && auth.info.email != params[:user][:email]
88   - raise "Wrong email for oauth signup"
  89 + unless params[:user][:email].blank?
  90 + raise "Wrong email for oauth signup. EMAIL: #{params[:user][:email]}"
  91 + end
89 92 end
90 93 end
91 94 }
... ...
plugins/oauth_client/lib/oauth_client_plugin/signup_data_store.rb 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +# A Distributed Cache Store is needed
  2 +# to save oauth autenthication to be
  3 +# used on OAUTH flow using the Noosfero REST API.
  4 +# Because of the nature session less of api implementation
  5 +# When using more than one server is strongly recomended
  6 +# provide your Rails application with a distributed Cache Store,
  7 +# otherwise you will have to rely on client/server affinify provided by
  8 +# network infrastructure
  9 +class OauthClientPlugin::SignupDataStore
  10 +
  11 + def self.key_name_for email, signup_token
  12 + "#{email}_#{signup_token}"
  13 + end
  14 +
  15 + def self.get_oauth_data email, signup_token
  16 + key_name = key_name_for(email, signup_token)
  17 + puts "OAUTH_KEY_NAME :::: #{key_name}"
  18 + oauth_data = Rails.cache.fetch(key_name)
  19 + Rails.cache.delete(key_name)
  20 + oauth_data
  21 + end
  22 +
  23 + def self.store_oauth_data email, auth_obj
  24 + signup_token = SecureRandom.hex
  25 + Rails.cache.write(key_name_for(email, signup_token), auth_obj, :expires_in => 300)
  26 + signup_token
  27 + end
  28 +
  29 + def self.delete_cache_for email
  30 + Rails.cache.delete(cache_name_for(email))
  31 + end
  32 +
  33 +
  34 +end
... ...
plugins/oauth_client/lib/oauth_client_plugin/user_provider.rb
... ... @@ -7,4 +7,5 @@ class OauthClientPlugin::UserProvider &lt; Noosfero::Plugin::ActiveRecord
7 7  
8 8 attr_accessible :user, :provider, :enabled
9 9  
  10 + acts_as_having_settings :field => :oauth_data
10 11 end
... ...