Commit 2cdcd813d955c0e5f5d50d0e5acd907a3b087979
Exists in
theme-brasil-digital-from-staging
and in
9 other branches
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
.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 | ... | ... |
... | ... | @@ -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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 < 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 < 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 < 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 < 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 < 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 | ... | ... |
... | ... | @@ -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
app/models/environment.rb
... | ... | @@ -21,6 +21,7 @@ class Environment < 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 < 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 < 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 'builder' |
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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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
app/models/tiny_mce_article.rb
app/models/user.rb
... | ... | @@ -102,18 +102,20 @@ class User < 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 < 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
... | ... | @@ -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 %> | ... | ... |
... | ... | @@ -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/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
... | ... | @@ -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 | + — | |
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
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 | - — | |
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
... | ... | @@ -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
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 < 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
lib/noosfero/api/helpers.rb
... | ... | @@ -160,8 +160,27 @@ require 'grape' |
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 'grape' |
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 | ... | ... |
... | ... | @@ -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/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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | + | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | + | ... | ... |
... | ... | @@ -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 "" |
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 "" |
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 "Statuts" |
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 "Couleur" |
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 "Label" |
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 "Êtes-vous sûr(e) que vous voulez retirer ce label ?" |
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 "(no status registered yet)" |
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
plugins/community_track/lib/community_track_plugin/step.rb
... | ... | @@ -76,20 +76,20 @@ class CommunityTrackPlugin::Step < 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 "" |
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 "" |
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 "" |
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 "" |
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 "Résumé" |
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 "Choisir par type de contenu" |
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 "Le plus ancien" |
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 "plus" |
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
... | ... | @@ -0,0 +1 @@ |
1 | +Subproject commit 7d1cef867e1325f623638366faeb5387a9261b0a | ... | ... |
... | ... | @@ -0,0 +1 @@ |
1 | +Subproject commit 8fb44a478abb5ccd345b8ffaf1f37817d273ad30 | ... | ... |
plugins/oauth_client/Gemfile
plugins/oauth_client/controllers/public/oauth_client_plugin_public_controller.rb
... | ... | @@ -22,7 +22,7 @@ class OauthClientPluginPublicController < 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 < 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 < 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