Commit 45b484729bb6aa46e4902e0deadb2df9b480eaa1

Authored by Rodrigo Souto
2 parents 70890398 c90b8e63

Merge commit 'refs/merge-requests/248' of git://gitorious.org/noosfero/noosfero …

…into merge-requests/248

Conflicts:
	app/models/person.rb
	app/views/catalog/index.rhtml
	public/stylesheets/application.css
	test/functional/catalog_controller_test.rb
Showing 223 changed files with 74020 additions and 62087 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 223 files displayed.

app/controllers/admin/admin_panel_controller.rb
... ... @@ -8,6 +8,9 @@ class AdminPanelController < AdminController
8 8  
9 9 def site_info
10 10 if request.post?
  11 + if params[:environment][:languages]
  12 + params[:environment][:languages] = params[:environment][:languages].map {|lang, value| lang if value=='true'}.compact
  13 + end
11 14 if @environment.update_attributes(params[:environment])
12 15 session[:notice] = _('Environment settings updated')
13 16 redirect_to :action => 'index'
... ...
app/controllers/application_controller.rb
... ... @@ -42,9 +42,9 @@ class ApplicationController < ActionController::Base
42 42  
43 43 before_filter :set_locale
44 44 def set_locale
45   - FastGettext.available_locales = Noosfero.available_locales
46   - FastGettext.default_locale = Noosfero.default_locale
47   - FastGettext.locale = (params[:lang] || session[:lang] || Noosfero.default_locale || request.env['HTTP_ACCEPT_LANGUAGE'] || 'en')
  45 + FastGettext.available_locales = environment.available_locales
  46 + FastGettext.default_locale = environment.default_locale
  47 + FastGettext.locale = (params[:lang] || session[:lang] || environment.default_locale || request.env['HTTP_ACCEPT_LANGUAGE'] || 'en')
48 48 I18n.locale = FastGettext.locale
49 49 if params[:lang]
50 50 session[:lang] = params[:lang]
... ...
app/controllers/box_organizer_controller.rb
... ... @@ -68,7 +68,8 @@ class BoxOrganizerController < ApplicationController
68 68 raise ArgumentError.new("Type %s is not allowed. Go away." % type)
69 69 end
70 70 else
71   - @block_types = available_blocks
  71 + @center_block_types = Box.acceptable_center_blocks & available_blocks
  72 + @side_block_types = Box.acceptable_side_blocks & available_blocks
72 73 @boxes = boxes_holder.boxes
73 74 render :action => 'add_block', :layout => false
74 75 end
... ...
app/controllers/my_profile/cms_controller.rb
... ... @@ -116,11 +116,11 @@ class CmsController < MyProfileController
116 116 @parent_id = parent.id
117 117 end
118 118  
119   - translations if @article.translatable?
120   -
121 119 @article.profile = profile
122 120 @article.last_changed_by = user
123 121  
  122 + translations if @article.translatable?
  123 +
124 124 continue = params[:continue]
125 125 if request.post?
126 126 if @article.save
... ... @@ -149,7 +149,6 @@ class CmsController < MyProfileController
149 149 @uploaded_files = []
150 150 @article = @parent = check_parent(params[:parent_id])
151 151 @target = @parent ? ('/%s/%s' % [profile.identifier, @parent.full_name]) : '/%s' % profile.identifier
152   - @folders = Folder.find(:all, :conditions => { :profile_id => profile })
153 152 if @article
154 153 record_coming
155 154 end
... ... @@ -345,7 +344,7 @@ class CmsController < MyProfileController
345 344 end
346 345  
347 346 def translations
348   - @locales = Noosfero.locales.invert.reject { |name, lang| !@article.possible_translations.include?(lang) }
  347 + @locales = environment.locales.invert.reject { |name, lang| !@article.possible_translations.include?(lang) }
349 348 @selected_locale = @article.language || FastGettext.locale
350 349 end
351 350  
... ...
app/controllers/my_profile/profile_design_controller.rb
... ... @@ -5,7 +5,7 @@ class ProfileDesignController < BoxOrganizerController
5 5 protect 'edit_profile_design', :profile
6 6  
7 7 def available_blocks
8   - blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock ]
  8 + blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock, HighlightsBlock ]
9 9  
10 10 # blocks exclusive for organizations
11 11 if profile.has_members?
... ...
app/controllers/my_profile/profile_editor_controller.rb
... ... @@ -14,6 +14,7 @@ class ProfileEditorController < MyProfileController
14 14 @profile_data = profile
15 15 @possible_domains = profile.possible_domains
16 16 if request.post?
  17 + params[:profile_data][:fields_privacy] ||= {} if profile.person? && params[:profile_data].is_a?(Hash)
17 18 begin
18 19 Profile.transaction do
19 20 Image.transaction do
... ...
app/controllers/my_profile/profile_members_controller.rb
... ... @@ -156,18 +156,4 @@ class ProfileMembersController < MyProfileController
156 156 end
157 157 end
158 158  
159   - def send_mail
160   - @mailing = profile.mailings.build(params[:mailing])
161   - if request.post?
162   - @mailing.locale = locale
163   - @mailing.person = user
164   - if @mailing.save
165   - session[:notice] = _('The e-mails are being sent')
166   - redirect_to :action => 'index'
167   - else
168   - session[:notice] = _('Could not create the e-mail')
169   - end
170   - end
171   - end
172   -
173 159 end
... ...
app/controllers/public/account_controller.rb
... ... @@ -25,11 +25,13 @@ class AccountController < ApplicationController
25 25  
26 26 # action to perform login to the application
27 27 def login
28   - @user = User.new
29   - @person = @user.build_person
30 28 store_location(request.referer) unless session[:return_to]
31 29 return unless request.post?
32   - self.current_user = User.authenticate(params[:user][:login], params[:user][:password], environment) if params[:user]
  30 +
  31 + self.current_user = plugins_alternative_authentication
  32 +
  33 + self.current_user ||= User.authenticate(params[:user][:login], params[:user][:password], environment) if params[:user]
  34 +
33 35 if logged_in?
34 36 if params[:remember_me] == "1"
35 37 self.current_user.remember_me
... ... @@ -41,7 +43,6 @@ class AccountController < ApplicationController
41 43 end
42 44 else
43 45 session[:notice] = _('Incorrect username or password') if redirect?
44   - redirect_to :back if redirect?
45 46 end
46 47 end
47 48  
... ... @@ -56,6 +57,11 @@ class AccountController < ApplicationController
56 57  
57 58 # action to register an user to the application
58 59 def signup
  60 + if @plugins.dispatch(:allow_user_registration).include?(false)
  61 + redirect_back_or_default(:controller => 'home')
  62 + session[:notice] = _("This environment doesn't allow user registration.")
  63 + end
  64 +
59 65 @invitation_code = params[:invitation_code]
60 66 begin
61 67 if params[:user]
... ... @@ -125,6 +131,10 @@ class AccountController < ApplicationController
125 131 #
126 132 # Posts back.
127 133 def forgot_password
  134 + if @plugins.dispatch(:allow_password_recovery).include?(false)
  135 + redirect_back_or_default(:controller => 'home')
  136 + session[:notice] = _("This environment doesn't allow password recovery.")
  137 + end
128 138 @change_password = ChangePassword.new(params[:change_password])
129 139  
130 140 if request.post?
... ... @@ -303,10 +313,27 @@ class AccountController < ApplicationController
303 313 end
304 314  
305 315 def go_to_initial_page
306   - if environment == current_user.environment
307   - redirect_back_or_default(user.admin_url)
  316 + if environment.enabled?('allow_change_of_redirection_after_login')
  317 + case user.preferred_login_redirection
  318 + when 'keep_on_same_page'
  319 + redirect_back_or_default(user.admin_url)
  320 + when 'site_homepage'
  321 + redirect_to :controller => :home
  322 + when 'user_profile_page'
  323 + redirect_to user.public_profile_url
  324 + when 'user_homepage'
  325 + redirect_to user.url
  326 + when 'user_control_panel'
  327 + redirect_to user.admin_url
  328 + else
  329 + redirect_back_or_default(user.admin_url)
  330 + end
308 331 else
309   - redirect_back_or_default(:controller => 'home')
  332 + if environment == current_user.environment
  333 + redirect_back_or_default(user.admin_url)
  334 + else
  335 + redirect_back_or_default(:controller => 'home')
  336 + end
310 337 end
311 338 end
312 339  
... ... @@ -316,4 +343,13 @@ class AccountController < ApplicationController
316 343 end
317 344 end
318 345  
  346 + def plugins_alternative_authentication
  347 + user = nil
  348 + @plugins.each do |plugin|
  349 + user = plugin.alternative_authentication
  350 + break unless user.nil?
  351 + end
  352 + user
  353 + end
  354 +
319 355 end
... ...
app/controllers/public/not_found_controller.rb
... ... @@ -2,4 +2,8 @@ class NotFoundController < ApplicationController
2 2 def index
3 3 render_not_found
4 4 end
  5 +
  6 + def nothing
  7 + render :nothing => true, :status => 404
  8 + end
5 9 end
... ...
app/controllers/public/profile_controller.rb
... ... @@ -2,11 +2,13 @@ class ProfileController < PublicController
2 2  
3 3 needs_profile
4 4 before_filter :check_access_to_profile, :except => [:join, :join_not_logged, :index, :add]
5   - before_filter :store_location, :only => [:join, :join_not_logged, :report_abuse]
6   - before_filter :login_required, :only => [:add, :join, :join_not_logged, :leave, :unblock, :leave_scrap, :remove_scrap, :remove_activity, :view_more_activities, :view_more_network_activities, :report_abuse, :register_report, :leave_comment_on_activity]
  5 + before_filter :store_location, :only => [:join, :join_not_logged, :report_abuse, :send_mail]
  6 + before_filter :login_required, :only => [:add, :join, :join_not_logged, :leave, :unblock, :leave_scrap, :remove_scrap, :remove_activity, :view_more_activities, :view_more_network_activities, :report_abuse, :register_report, :leave_comment_on_activity, :send_mail]
7 7  
8 8 helper TagsHelper
9 9  
  10 + protect 'send_mail_to_members', :profile, :only => [:send_mail]
  11 +
10 12 def index
11 13 @network_activities = !@profile.is_a?(Person) ? @profile.tracked_notifications.visible.paginate(:per_page => 15, :page => params[:page]) : []
12 14 if logged_in? && current_person.follows?(@profile)
... ... @@ -49,36 +51,36 @@ class ProfileController < PublicController
49 51  
50 52 def communities
51 53 if is_cache_expired?(profile.communities_cache_key(params))
52   - @communities = profile.communities.paginate(:per_page => per_page, :page => params[:npage])
  54 + @communities = profile.communities.includes(relations_to_include).paginate(:per_page => per_page, :page => params[:npage])
53 55 end
54 56 end
55 57  
56 58 def enterprises
57   - @enterprises = profile.enterprises
  59 + @enterprises = profile.enterprises.includes(relations_to_include)
58 60 end
59 61  
60 62 def friends
61 63 if is_cache_expired?(profile.friends_cache_key(params))
62   - @friends = profile.friends.paginate(:per_page => per_page, :page => params[:npage])
  64 + @friends = profile.friends.includes(relations_to_include).paginate(:per_page => per_page, :page => params[:npage])
63 65 end
64 66 end
65 67  
66 68 def members
67 69 if is_cache_expired?(profile.members_cache_key(params))
68   - @members = profile.members.paginate(:per_page => members_per_page, :page => params[:npage])
  70 + @members = profile.members.includes(relations_to_include).paginate(:per_page => members_per_page, :page => params[:npage])
69 71 end
70 72 end
71 73  
72 74 def fans
73   - @fans = profile.fans
  75 + @fans = profile.fans.includes(relations_to_include)
74 76 end
75 77  
76 78 def favorite_enterprises
77   - @favorite_enterprises = profile.favorite_enterprises
  79 + @favorite_enterprises = profile.favorite_enterprises.includes(relations_to_include)
78 80 end
79 81  
80 82 def sitemap
81   - @articles = profile.top_level_articles
  83 + @articles = profile.top_level_articles.includes([:profile, :parent])
82 84 end
83 85  
84 86 def join
... ... @@ -327,6 +329,20 @@ class ProfileController < PublicController
327 329 end
328 330 end
329 331  
  332 + def send_mail
  333 + @mailing = profile.mailings.build(params[:mailing])
  334 + if request.post?
  335 + @mailing.locale = locale
  336 + @mailing.person = user
  337 + if @mailing.save
  338 + session[:notice] = _('The e-mails are being sent')
  339 + redirect_to_previous_location
  340 + else
  341 + session[:notice] = _('Could not create the e-mail')
  342 + end
  343 + end
  344 + end
  345 +
330 346 protected
331 347  
332 348 def check_access_to_profile
... ... @@ -378,4 +394,8 @@ class ProfileController < PublicController
378 394 @can_edit_profile ||= user && user.has_permission?('edit_profile', profile)
379 395 end
380 396 helper_method :can_edit_profile
  397 +
  398 + def relations_to_include
  399 + [:image, :domains, :preferred_domain, :environment]
  400 + end
381 401 end
... ...
app/controllers/public/search_controller.rb
... ... @@ -46,7 +46,7 @@ class SearchController < PublicController
46 46 if !@empty_query
47 47 full_text_search ['public:true']
48 48 else
49   - @results[@asset] = @environment.people.visible.send(@filter).paginate(paginate_options)
  49 + @results[@asset] = visible_profiles(Person).send(@filter).paginate(paginate_options)
50 50 end
51 51 end
52 52  
... ... @@ -76,7 +76,7 @@ class SearchController < PublicController
76 76 full_text_search ['public:true']
77 77 else
78 78 @filter_title = _('Enterprises from network')
79   - @results[@asset] = @environment.enterprises.visible.paginate(paginate_options)
  79 + @results[@asset] = visible_profiles(Enterprise, [{:products => :product_category}]).paginate(paginate_options)
80 80 end
81 81 end
82 82  
... ... @@ -84,7 +84,7 @@ class SearchController < PublicController
84 84 if !@empty_query
85 85 full_text_search ['public:true']
86 86 else
87   - @results[@asset] = @environment.communities.visible.send(@filter).paginate(paginate_options)
  87 + @results[@asset] = visible_profiles(Community).send(@filter).paginate(paginate_options)
88 88 end
89 89 end
90 90  
... ... @@ -310,4 +310,12 @@ class SearchController < PublicController
310 310 @all_facets = ret[:all_facets]
311 311 end
312 312  
  313 + private
  314 +
  315 + def visible_profiles(klass, *extra_relations)
  316 + relations = [:image, :domains, :environment, :preferred_domain]
  317 + relations += extra_relations
  318 + @environment.send(klass.name.underscore.pluralize).visible.includes(relations)
  319 + end
  320 +
313 321 end
... ...
app/helpers/application_helper.rb
... ... @@ -265,9 +265,9 @@ module ApplicationHelper
265 265  
266 266 VIEW_EXTENSIONS = %w[.rhtml .html.erb]
267 267  
268   - def partial_for_class_in_view_path(klass, view_path)
  268 + def partial_for_class_in_view_path(klass, view_path, suffix = nil)
269 269 return nil if klass.nil?
270   - name = klass.name.underscore
  270 + name = [klass.name.underscore, suffix].compact.map(&:to_s).join('_')
271 271  
272 272 search_name = String.new(name)
273 273 if search_name.include?("/")
... ... @@ -282,31 +282,20 @@ module ApplicationHelper
282 282 return name if File.exists?(File.join(path))
283 283 end
284 284  
285   - partial_for_class_in_view_path(klass.superclass, view_path)
  285 + partial_for_class_in_view_path(klass.superclass, view_path, suffix)
286 286 end
287 287  
288   - def partial_for_class(klass)
  288 + def partial_for_class(klass, suffix=nil)
289 289 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil?
290 290 name = klass.name.underscore
291 291 @controller.view_paths.each do |view_path|
292   - partial = partial_for_class_in_view_path(klass, view_path)
  292 + partial = partial_for_class_in_view_path(klass, view_path, suffix)
293 293 return partial if partial
294 294 end
295 295  
296 296 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?'
297 297 end
298 298  
299   - def partial_for_task_class(klass, action)
300   - raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil?
301   -
302   - name = "#{klass.name.underscore}_#{action.to_s}"
303   - VIEW_EXTENSIONS.each do |ext|
304   - return name if File.exists?(File.join(RAILS_ROOT, 'app', 'views', params[:controller], '_'+name+ext))
305   - end
306   -
307   - partial_for_task_class(klass.superclass, action)
308   - end
309   -
310 299 def view_for_profile_actions(klass)
311 300 raise ArgumentError, 'No profile actions view for this class.' if klass.nil?
312 301  
... ... @@ -664,19 +653,6 @@ module ApplicationHelper
664 653 content_tag('div', result)
665 654 end
666 655  
667   - def select_folder(label, object, method, collection, html_options = {}, js_options = {})
668   - root = profile ? profile.identifier : _("root")
669   - labelled_form_field(label, select(object, method,
670   - collection.map {|f| [ root + '/' + f.full_name, f.id ]},
671   - {:include_blank => root}, html_options.merge(js_options)))
672   - end
673   -
674   - def select_profile_folder(label, object, method, profile, html_options = {}, js_options = {})
675   - labelled_form_field(label, select(object, method,
676   - profile.folders.map {|f| [ profile.identifier + '/' + f.full_name, f.id ]},
677   - {:include_blank => profile.identifier}, html_options.merge(js_options)))
678   - end
679   -
680 656 def theme_option(opt = nil)
681 657 conf = RAILS_ROOT.to_s() +
682 658 '/public' + theme_path +
... ... @@ -877,7 +853,7 @@ module ApplicationHelper
877 853 end
878 854 else
879 855 if profile.active_fields.include?(name)
880   - result = field_html
  856 + result = content_tag('div', field_html + profile_field_privacy_selector(profile, name), :class => 'field-with-privacy-selector')
881 857 end
882 858 end
883 859  
... ... @@ -892,6 +868,11 @@ module ApplicationHelper
892 868 result
893 869 end
894 870  
  871 + def profile_field_privacy_selector(profile, name)
  872 + return '' unless profile.public?
  873 + content_tag('div', labelled_check_box(_('Public'), 'profile_data[fields_privacy]['+name+']', 'public', profile.public_fields.include?(name)), :class => 'field-privacy-selector')
  874 + end
  875 +
895 876 def template_stylesheet_path
896 877 if profile.nil?
897 878 "/designs/templates/#{environment.layout_template}/stylesheets/style.css"
... ...
app/helpers/article_helper.rb
... ... @@ -2,14 +2,19 @@ module ArticleHelper
2 2  
3 3 def custom_options_for_article(article)
4 4 @article = article
  5 + content_tag('h4', _('Visibility')) +
  6 + content_tag('div',
  7 + content_tag('div',
  8 + radio_button(:article, :published, true) +
  9 + content_tag('label', _('Public (visible to other people)'), :for => 'article_published_true')
  10 + ) +
  11 + content_tag('div',
  12 + radio_button(:article, :published, false) +
  13 + content_tag('label', _('Private'), :for => 'article_published_false')
  14 + )
  15 + ) +
5 16 content_tag('h4', _('Options')) +
6 17 content_tag('div',
7   - content_tag(
8   - 'div',
9   - check_box(:article, :published) +
10   - content_tag('label', _('This article must be published (visible to other people)'), :for => 'article_published')
11   - ) +
12   -
13 18 (article.profile.has_members? ?
14 19 content_tag(
15 20 'div',
... ...
app/helpers/boxes_helper.rb
... ... @@ -66,7 +66,7 @@ module BoxesHelper
66 66  
67 67 def display_box_content(box, main_content)
68 68 context = { :article => @page, :request_path => request.path, :locale => locale }
69   - box_decorator.select_blocks(box.blocks, context).map { |item| display_block(item, main_content) }.join("\n") + box_decorator.block_target(box)
  69 + box_decorator.select_blocks(box.blocks.includes(:box), context).map { |item| display_block(item, main_content) }.join("\n") + box_decorator.block_target(box)
70 70 end
71 71  
72 72 def select_blocks(arr, context)
... ... @@ -162,9 +162,6 @@ module BoxesHelper
162 162 #
163 163 # +box+ is always needed
164 164 def block_target(box, block = nil)
165   - # FIXME hardcoded
166   - return '' if box.position == 1
167   -
168 165 id =
169 166 if block.nil?
170 167 "end-of-box-#{box.id}"
... ... @@ -172,14 +169,11 @@ module BoxesHelper
172 169 "before-block-#{block.id}"
173 170 end
174 171  
175   - content_tag('div', ' ', :id => id, :class => 'block-target' ) + drop_receiving_element(id, :url => { :action => 'move_block', :target => id }, :accept => 'block', :hoverclass => 'block-target-hover')
  172 + content_tag('div', ' ', :id => id, :class => 'block-target' ) + drop_receiving_element(id, :url => { :action => 'move_block', :target => id }, :accept => box.acceptable_blocks, :hoverclass => 'block-target-hover')
176 173 end
177 174  
178 175 # makes the given block draggable so it can be moved away.
179 176 def block_handle(block)
180   - # FIXME hardcoded
181   - return '' if block.box.position == 1
182   -
183 177 draggable_element("block-#{block.id}", :revert => true)
184 178 end
185 179  
... ... @@ -211,7 +205,7 @@ module BoxesHelper
211 205 end
212 206  
213 207 if block.editable?
214   - buttons << lightbox_icon_button(:edit, _('Edit'), { :action => 'edit', :id => block.id })
  208 + buttons << colorbox_icon_button(:edit, _('Edit'), { :action => 'edit', :id => block.id })
215 209 end
216 210  
217 211 if !block.main?
... ...
app/helpers/colorbox_helper.rb
... ... @@ -8,6 +8,10 @@ module ColorboxHelper
8 8 button(type, label, url, colorbox_options(options))
9 9 end
10 10  
  11 + def colorbox_icon_button(type, label, url, options = {})
  12 + icon_button(type, label, url, colorbox_options(options))
  13 + end
  14 +
11 15 # options must be an HTML options hash as passed to link_to etc.
12 16 #
13 17 # returns a new hash with colorbox class added. Keeps existing classes.
... ...
app/helpers/content_viewer_helper.rb
... ... @@ -42,7 +42,7 @@ module ContentViewerHelper
42 42 def article_translations(article)
43 43 unless article.native_translation.translations.empty?
44 44 links = (article.native_translation.translations + [article.native_translation]).map do |translation|
45   - { Noosfero.locales[translation.language] => { :href => url_for(translation.url) } }
  45 + { article.environment.locales[translation.language] => { :href => url_for(translation.url) } }
46 46 end
47 47 content_tag(:div, link_to(_('Translations'), '#',
48 48 :onclick => "toggleSubmenu(this, '#{_('Translations')}', #{links.to_json}); return false",
... ...
app/helpers/display_helper.rb
... ... @@ -8,6 +8,14 @@ module DisplayHelper
8 8 opts
9 9 end
10 10  
  11 + def themed_path(file)
  12 + if File.exists?(File.join(Rails.root, 'public', theme_path, file))
  13 + File.join(theme_path, file)
  14 + else
  15 + file
  16 + end
  17 + end
  18 +
11 19 def image_link_to_product(product, opts={})
12 20 return _('No product') unless product
13 21 target = product_path(product)
... ...
app/helpers/folder_helper.rb
... ... @@ -58,18 +58,18 @@ module FolderHelper
58 58  
59 59 def custom_options_for_article(article)
60 60 @article = article
61   - content_tag('h4', _('Options')) +
  61 + content_tag('h4', _('Visibility')) +
  62 + content_tag('div',
  63 + content_tag('div',
  64 + radio_button(:article, :published, true) +
  65 + content_tag('label', _('Public (visible to other people)'), :for => 'article_published_true')
  66 + ) +
  67 + content_tag('div',
  68 + radio_button(:article, :published, false) +
  69 + content_tag('label', _('Private'), :for => 'article_published_false')
  70 + )
  71 + ) +
62 72 content_tag('div',
63   - content_tag(
64   - 'div',
65   - check_box(:article, :published) +
66   - content_tag('label', _('This article must be published (visible to other people)'), :for => 'article_published')
67   - ) + (article.can_display_hits? ?
68   - content_tag(
69   - 'div',
70   - check_box(:article, :display_hits) +
71   - content_tag('label', _('I want this article to display the number of hits it received'), :for => 'article_display_hits')
72   - ) : '') +
73 73 hidden_field_tag('article[accept_comments]', 0)
74 74 )
75 75 end
... ...
app/helpers/forms_helper.rb
... ... @@ -123,6 +123,170 @@ module FormsHelper
123 123 options_for_select.join("\n")
124 124 end
125 125  
  126 + def balanced_table(items, per_row=3)
  127 + items = items.map {|item| content_tag('td', item, :style => 'border: none; background: transparent;')}
  128 + rows = []
  129 + row = []
  130 + counter = 0
  131 + items.each do |item|
  132 + counter += 1
  133 + row << item
  134 + if counter % per_row == 0
  135 + rows << content_tag('tr', row.join("\n"))
  136 + counter = 0
  137 + row = []
  138 + end
  139 + end
  140 + rows << content_tag('tr', row.join("\n"))
  141 +
  142 + content_tag('table',rows.join("\n"))
  143 + end
  144 +
  145 + def date_field(name, value, format = '%Y-%m-%d', datepicker_options = {}, html_options = {})
  146 + datepicker_options[:disabled] ||= false
  147 + datepicker_options[:alt_field] ||= ''
  148 + datepicker_options[:alt_format] ||= ''
  149 + datepicker_options[:append_text] ||= ''
  150 + datepicker_options[:auto_size] ||= false
  151 + datepicker_options[:button_image] ||= ''
  152 + datepicker_options[:button_image_only] ||= false
  153 + datepicker_options[:button_text] ||= '...'
  154 + datepicker_options[:calculate_week] ||= 'jQuery.datepicker.iso8601Week'
  155 + datepicker_options[:change_month] ||= false
  156 + datepicker_options[:change_year] ||= false
  157 + datepicker_options[:close_text] ||= _('Done')
  158 + datepicker_options[:constrain_input] ||= true
  159 + datepicker_options[:current_text] ||= _('Today')
  160 + datepicker_options[:date_format] ||= 'mm/dd/yy'
  161 + datepicker_options[:day_names] ||= [_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday')]
  162 + datepicker_options[:day_names_min] ||= [_('Su'), _('Mo'), _('Tu'), _('We'), _('Th'), _('Fr'), _('Sa')]
  163 + datepicker_options[:day_names_short] ||= [_('Sun'), _('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'), _('Sat')]
  164 + datepicker_options[:default_date] ||= nil
  165 + datepicker_options[:duration] ||= 'normal'
  166 + datepicker_options[:first_day] ||= 0
  167 + datepicker_options[:goto_current] ||= false
  168 + datepicker_options[:hide_if_no_prev_next] ||= false
  169 + datepicker_options[:is_rtl] ||= false
  170 + datepicker_options[:max_date] ||= nil
  171 + datepicker_options[:min_date] ||= nil
  172 + datepicker_options[:month_names] ||= [_('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December')]
  173 + datepicker_options[:month_names_short] ||= [_('Jan'), _('Feb'), _('Mar'), _('Apr'), _('May'), _('Jun'), _('Jul'), _('Aug'), _('Sep'), _('Oct'), _('Nov'), _('Dec')]
  174 + datepicker_options[:navigation_as_date_format] ||= false
  175 + datepicker_options[:next_text] ||= _('Next')
  176 + datepicker_options[:number_of_months] ||= 1
  177 + datepicker_options[:prev_text] ||= _('Prev')
  178 + datepicker_options[:select_other_months] ||= false
  179 + datepicker_options[:short_year_cutoff] ||= '+10'
  180 + datepicker_options[:show_button_panel] ||= false
  181 + datepicker_options[:show_current_at_pos] ||= 0
  182 + datepicker_options[:show_month_after_year] ||= false
  183 + datepicker_options[:show_on] ||= 'focus'
  184 + datepicker_options[:show_options] ||= {}
  185 + datepicker_options[:show_other_months] ||= false
  186 + datepicker_options[:show_week] ||= false
  187 + datepicker_options[:step_months] ||= 1
  188 + datepicker_options[:week_header] ||= _('Wk')
  189 + datepicker_options[:year_range] ||= 'c-10:c+10'
  190 + datepicker_options[:year_suffix] ||= ''
  191 +
  192 + element_id = html_options[:id] || 'datepicker-date'
  193 + value = value.strftime(format) if value.present?
  194 + method = datepicker_options[:time] ? 'datetimepicker' : 'datepicker'
  195 + result = text_field_tag(name, value, html_options)
  196 + result +=
  197 + "
  198 + <script type='text/javascript'>
  199 + jQuery('##{element_id}').#{method}({
  200 + disabled: #{datepicker_options[:disabled].to_json},
  201 + altField: #{datepicker_options[:alt_field].to_json},
  202 + altFormat: #{datepicker_options[:alt_format].to_json},
  203 + appendText: #{datepicker_options[:append_text].to_json},
  204 + autoSize: #{datepicker_options[:auto_size].to_json},
  205 + buttonImage: #{datepicker_options[:button_image].to_json},
  206 + buttonImageOnly: #{datepicker_options[:button_image_only].to_json},
  207 + buttonText: #{datepicker_options[:button_text].to_json},
  208 + calculateWeek: #{datepicker_options[:calculate_week].to_json},
  209 + changeMonth: #{datepicker_options[:change_month].to_json},
  210 + changeYear: #{datepicker_options[:change_year].to_json},
  211 + closeText: #{datepicker_options[:close_text].to_json},
  212 + constrainInput: #{datepicker_options[:constrain_input].to_json},
  213 + currentText: #{datepicker_options[:current_text].to_json},
  214 + dateFormat: #{datepicker_options[:date_format].to_json},
  215 + dayNames: #{datepicker_options[:day_names].to_json},
  216 + dayNamesMin: #{datepicker_options[:day_names_min].to_json},
  217 + dayNamesShort: #{datepicker_options[:day_names_short].to_json},
  218 + defaultDate: #{datepicker_options[:default_date].to_json},
  219 + duration: #{datepicker_options[:duration].to_json},
  220 + firstDay: #{datepicker_options[:first_day].to_json},
  221 + gotoCurrent: #{datepicker_options[:goto_current].to_json},
  222 + hideIfNoPrevNext: #{datepicker_options[:hide_if_no_prev_next].to_json},
  223 + isRTL: #{datepicker_options[:is_rtl].to_json},
  224 + maxDate: #{datepicker_options[:max_date].to_json},
  225 + minDate: #{datepicker_options[:min_date].to_json},
  226 + monthNames: #{datepicker_options[:month_names].to_json},
  227 + monthNamesShort: #{datepicker_options[:month_names_short].to_json},
  228 + navigationAsDateFormat: #{datepicker_options[:navigation_as_date_format].to_json},
  229 + nextText: #{datepicker_options[:next_text].to_json},
  230 + numberOfMonths: #{datepicker_options[:number_of_months].to_json},
  231 + prevText: #{datepicker_options[:prev_text].to_json},
  232 + selectOtherMonths: #{datepicker_options[:select_other_months].to_json},
  233 + shortYearCutoff: #{datepicker_options[:short_year_cutoff].to_json},
  234 + showButtonPanel: #{datepicker_options[:show_button_panel].to_json},
  235 + showCurrentAtPos: #{datepicker_options[:show_current_at_pos].to_json},
  236 + showMonthAfterYear: #{datepicker_options[:show_month_after_year].to_json},
  237 + showOn: #{datepicker_options[:show_on].to_json},
  238 + showOptions: #{datepicker_options[:show_options].to_json},
  239 + showOtherMonths: #{datepicker_options[:show_other_months].to_json},
  240 + showWeek: #{datepicker_options[:show_week].to_json},
  241 + stepMonths: #{datepicker_options[:step_months].to_json},
  242 + weekHeader: #{datepicker_options[:week_header].to_json},
  243 + yearRange: #{datepicker_options[:year_range].to_json},
  244 + yearSuffix: #{datepicker_options[:year_suffix].to_json}
  245 + })
  246 + </script>
  247 + "
  248 + result
  249 + end
  250 +
  251 + def date_range_field(from_name, to_name, from_value, to_value, format = '%Y-%m-%d', datepicker_options = {}, html_options = {})
  252 + from_id = html_options[:from_id] || 'datepicker-from-date'
  253 + to_id = html_options[:to_id] || 'datepicker-to-date'
  254 + return _('From') +' '+ date_field(from_name, from_value, format, datepicker_options, html_options.merge({:id => from_id})) +
  255 + ' ' + _('until') +' '+ date_field(to_name, to_value, format, datepicker_options, html_options.merge({:id => to_id}))
  256 + end
  257 +
  258 + def select_folder(label_text, field_id, collection, default_value=nil, html_options = {}, js_options = {})
  259 + root = profile ? profile.identifier : _("root")
  260 + labelled_form_field(
  261 + label_text,
  262 + select_tag(
  263 + field_id,
  264 + options_for_select(
  265 + [[root, '']] +
  266 + collection.collect {|f| [ root + '/' + f.full_name, f.id ] },
  267 + default_value
  268 + ),
  269 + html_options.merge(js_options)
  270 + )
  271 + )
  272 + end
  273 +
  274 + def select_profile_folder(label_text, field_id, profile, default_value='', html_options = {}, js_options = {})
  275 + result = labelled_form_field(
  276 + label_text,
  277 + select_tag(
  278 + field_id,
  279 + options_for_select(
  280 + [[profile.identifier, '']] +
  281 + profile.folders.collect {|f| [ profile.identifier + '/' + f.full_name, f.id ] },
  282 + default_value
  283 + ),
  284 + html_options.merge(js_options)
  285 + )
  286 + )
  287 + return result
  288 + end
  289 +
126 290 protected
127 291 def self.next_id_number
128 292 if defined? @@id_num
... ...
app/helpers/language_helper.rb
... ... @@ -13,18 +13,19 @@ module LanguageHelper
13 13  
14 14 alias :calendar_date_select_language :tinymce_language
15 15  
16   - def language_chooser(options = {})
  16 + def language_chooser(environment, options = {})
  17 + return if environment.locales.size < 2
17 18 current = language
18 19 separator = options[:separator] || ' &mdash; '
19 20  
20 21 if options[:element] == 'dropdown'
21 22 select_tag('lang',
22   - options_for_select(Noosfero.locales.map{|code,name| [name, code]}, current),
  23 + options_for_select(environment.locales.map{|code,name| [name, code]}, current),
23 24 :onchange => "document.location.href= #{url_for(params.merge(:lang => 'LANGUAGE')).inspect}.replace(/LANGUAGE/, this.value) ;",
24 25 :help => _('The language you choose here is the language used for options, buttons, etc. It does not affect the language of the content created by other users.')
25 26 )
26 27 else
27   - languages = Noosfero.locales.map do |code,name|
  28 + languages = environment.locales.map do |code,name|
28 29 if code == current
29 30 content_tag('strong', name)
30 31 else
... ...
app/helpers/profile_editor_helper.rb
... ... @@ -145,4 +145,12 @@ module ProfileEditorHelper
145 145 link_to title, url, :class => 'control-panel-%s' % icon
146 146 end
147 147  
  148 + def unchangeable_privacy_field(profile)
  149 + if profile.public?
  150 + labelled_check_box(_('Public'), '', '', true, :disabled => true, :title => _('This field must be public'), :class => 'disabled')
  151 + else
  152 + ''
  153 + end
  154 + end
  155 +
148 156 end
... ...
app/helpers/profile_helper.rb
1 1 module ProfileHelper
2 2  
3 3 def display_field(title, profile, field, force = false)
4   - if !force && !profile.active_fields.include?(field.to_s)
  4 + if (!force && !profile.active_fields.include?(field.to_s)) ||
  5 + (profile.active_fields.include?(field.to_s) && !profile.public_fields.include?(field.to_s) && (!user || (user != profile && !user.is_a_friend?(profile))))
5 6 return ''
6 7 end
7 8 value = profile.send(field)
... ... @@ -15,4 +16,26 @@ module ProfileHelper
15 16 end
16 17 end
17 18  
  19 + def display_contact(profile)
  20 + address = display_field(_('Address:'), profile, :address)
  21 + zip = display_field(_('ZIP code:'), profile, :zip_code)
  22 + phone = display_field(_('Contact phone:'), profile, :contact_phone)
  23 + email = display_field(_('e-Mail:'), profile, :email) { |email| link_to_email(email) }
  24 + if address.blank? && zip.blank? && phone.blank? && email.blank?
  25 + ''
  26 + else
  27 + content_tag('tr', content_tag('th', _('Contact'), { :colspan => 2 })) + address + zip + phone + email
  28 + end
  29 + end
  30 +
  31 + def display_work_info(profile)
  32 + organization = display_field(_('Organization:'), profile, :organization)
  33 + organization_site = display_field(_('Organization website:'), profile, :organization_website) { |url| link_to(url, url) }
  34 + if organization.blank? && organization_site.blank?
  35 + ''
  36 + else
  37 + content_tag('tr', content_tag('th', _('Work'), { :colspan => 2 })) + organization + organization_site
  38 + end
  39 + end
  40 +
18 41 end
... ...
app/models/article.rb
... ... @@ -79,6 +79,25 @@ class Article &lt; ActiveRecord::Base
79 79 validate :native_translation_must_have_language
80 80 validate :translation_must_have_language
81 81  
  82 + validate :no_self_reference
  83 + validate :no_cyclical_reference, :if => 'parent_id.present?'
  84 +
  85 + def no_self_reference
  86 + errors.add(:parent_id, _('self-reference is not allowed.')) if id && parent_id == id
  87 + end
  88 +
  89 + def no_cyclical_reference
  90 + current_parent = Article.find(parent_id)
  91 + while current_parent
  92 + if current_parent == self
  93 + errors.add(:parent_id, _('cyclical reference is not allowed.'))
  94 + break
  95 + end
  96 + current_parent = current_parent.parent
  97 + end
  98 + end
  99 +
  100 +
82 101 def is_trackable?
83 102 self.published? && self.notifiable? && self.advertise? && self.profile.public_profile
84 103 end
... ... @@ -154,6 +173,12 @@ class Article &lt; ActiveRecord::Base
154 173 article.advertise = true
155 174 end
156 175  
  176 + before_save do |article|
  177 + article.parent = article.parent_id ? Article.find(article.parent_id) : nil
  178 + parent_path = article.parent ? article.parent.path : nil
  179 + article.path = [parent_path, article.slug].compact.join('/')
  180 + end
  181 +
157 182 # retrieves all articles belonging to the given +profile+ that are not
158 183 # sub-articles of any other article.
159 184 named_scope :top_level_for, lambda { |profile|
... ... @@ -179,37 +204,23 @@ class Article &lt; ActiveRecord::Base
179 204 end
180 205  
181 206 named_scope :more_popular, :order => 'hits DESC'
182   -
183   - # retrieves the latest +limit+ articles, sorted from the most recent to the
184   - # oldest.
185   - #
186   - # Only includes articles where advertise == true
187   - def self.recent(limit = nil, extra_conditions = {})
188   - # FIXME this method is a horrible hack
189   - options = { :page => 1, :per_page => limit,
190   - :conditions => [
191   - "advertise = ? AND
192   - published = ? AND
193   - profiles.visible = ? AND
194   - profiles.public_profile = ? AND
195   - ((articles.type != ? and articles.type != ? and articles.type != ?) OR articles.type is NULL)", true, true, true, true, 'UploadedFile', 'RssFeed', 'Blog'
196   - ],
197   - :include => 'profile',
198   - :order => 'articles.published_at desc, articles.id desc'
199   - }
200   - if ( scoped_methods && scoped_methods.last &&
201   - scoped_methods.last[:find] &&
202   - scoped_methods.last[:find][:joins] &&
203   - scoped_methods.last[:find][:joins].index('profiles') )
204   - options.delete(:include)
205   - end
206   - if extra_conditions == {}
207   - self.paginate(options)
208   - else
209   - with_scope :find => {:conditions => extra_conditions} do
210   - self.paginate(options)
211   - end
  207 + named_scope :relevant_as_recent, :conditions => ["(articles.type != 'UploadedFile' and articles.type != 'RssFeed' and articles.type != 'Blog') OR articles.type is NULL"]
  208 +
  209 + def self.recent(limit = nil, extra_conditions = {}, pagination = true)
  210 + result = scoped({:conditions => extra_conditions}).
  211 + public.
  212 + relevant_as_recent.
  213 + limit(limit).
  214 + order(['articles.published_at desc', 'articles.id desc'])
  215 +
  216 + if !( scoped_methods && scoped_methods.last &&
  217 + scoped_methods.last[:find] &&
  218 + scoped_methods.last[:find][:joins] &&
  219 + scoped_methods.last[:find][:joins].index('profiles') )
  220 + result = result.includes(:profile)
212 221 end
  222 +
  223 + pagination ? result.paginate({:page => 1, :per_page => limit}) : result
213 224 end
214 225  
215 226 # produces the HTML code that is to be displayed as this article's contents.
... ... @@ -325,14 +336,14 @@ class Article &lt; ActiveRecord::Base
325 336 end
326 337  
327 338 def possible_translations
328   - possibilities = Noosfero.locales.keys - self.native_translation.translations(:select => :language).map(&:language) - [self.native_translation.language]
  339 + possibilities = environment.locales.keys - self.native_translation.translations(:select => :language).map(&:language) - [self.native_translation.language]
329 340 possibilities << self.language unless self.language_changed?
330 341 possibilities
331 342 end
332 343  
333 344 def known_language
334 345 unless self.language.blank?
335   - errors.add(:language, N_('Language not supported by Noosfero')) unless Noosfero.locales.key?(self.language)
  346 + errors.add(:language, N_('Language not supported by the environment.')) unless environment.locales.key?(self.language)
336 347 end
337 348 end
338 349  
... ... @@ -665,7 +676,7 @@ class Article &lt; ActiveRecord::Base
665 676 self.categories.collect(&:name)
666 677 end
667 678  
668   - delegate :region, :region_id, :environment, :environment_id, :to => :profile
  679 + delegate :region, :region_id, :environment, :environment_id, :to => :profile, :allow_nil => true
669 680 def name_sortable # give a different name for solr
670 681 name
671 682 end
... ...
app/models/block.rb
... ... @@ -7,6 +7,8 @@ class Block &lt; ActiveRecord::Base
7 7 # Block-specific stuff
8 8 include BlockHelper
9 9  
  10 + delegate :environment, :to => :box, :allow_nil => true
  11 +
10 12 acts_as_list :scope => :box
11 13 belongs_to :box
12 14  
... ...
app/models/box.rb
... ... @@ -2,4 +2,80 @@ class Box &lt; ActiveRecord::Base
2 2 belongs_to :owner, :polymorphic => true
3 3 acts_as_list :scope => 'owner_id = #{owner_id} and owner_type = \'#{owner_type}\''
4 4 has_many :blocks, :dependent => :destroy, :order => 'position'
  5 +
  6 + def environment
  7 + owner ? (owner.kind_of?(Environment) ? owner : owner.environment) : nil
  8 + end
  9 +
  10 + def acceptable_blocks
  11 + to_css_class_name central? ? Box.acceptable_center_blocks : Box.acceptable_side_blocks
  12 + end
  13 +
  14 + def central?
  15 + position == 1
  16 + end
  17 +
  18 + def self.acceptable_center_blocks
  19 + [ ArticleBlock,
  20 + BlogArchivesBlock,
  21 + CategoriesBlock,
  22 + CommunitiesBlock,
  23 + EnterprisesBlock,
  24 + EnvironmentStatisticsBlock,
  25 + FansBlock,
  26 + FavoriteEnterprisesBlock,
  27 + FeedReaderBlock,
  28 + FriendsBlock,
  29 + HighlightsBlock,
  30 + LinkListBlock,
  31 + LoginBlock,
  32 + MainBlock,
  33 + MembersBlock,
  34 + MyNetworkBlock,
  35 + PeopleBlock,
  36 + ProfileImageBlock,
  37 + RawHTMLBlock,
  38 + RecentDocumentsBlock,
  39 + SellersSearchBlock,
  40 + TagsBlock ]
  41 + end
  42 +
  43 + def self.acceptable_side_blocks
  44 + [ ArticleBlock,
  45 + BlogArchivesBlock,
  46 + CategoriesBlock,
  47 + CommunitiesBlock,
  48 + DisabledEnterpriseMessageBlock,
  49 + EnterprisesBlock,
  50 + EnvironmentStatisticsBlock,
  51 + FansBlock,
  52 + FavoriteEnterprisesBlock,
  53 + FeaturedProductsBlock,
  54 + FeedReaderBlock,
  55 + FriendsBlock,
  56 + HighlightsBlock,
  57 + LinkListBlock,
  58 + LocationBlock,
  59 + LoginBlock,
  60 + MembersBlock,
  61 + MyNetworkBlock,
  62 + PeopleBlock,
  63 + ProductsBlock,
  64 + ProfileImageBlock,
  65 + ProfileInfoBlock,
  66 + ProfileSearchBlock,
  67 + RawHTMLBlock,
  68 + RecentDocumentsBlock,
  69 + SellersSearchBlock,
  70 + SlideshowBlock,
  71 + TagsBlock
  72 + ]
  73 + end
  74 +
  75 + private
  76 +
  77 + def to_css_class_name(blocks)
  78 + blocks.map{ |block| block.to_s.underscore.tr('_', '-') }
  79 + end
  80 +
5 81 end
... ...
app/models/change_password.rb
... ... @@ -7,7 +7,7 @@ class ChangePassword &lt; Task
7 7 when :login:
8 8 _('Username')
9 9 when :email
10   - _('e-Mail')
  10 + _('e-mail')
11 11 when :password
12 12 _('Password')
13 13 when :password_confirmation
... ... @@ -20,9 +20,7 @@ class ChangePassword &lt; Task
20 20 ###################################################
21 21 # validations for creating a ChangePassword task
22 22  
23   - validates_presence_of :login, :email, :environment_id, :on => :create
24   -
25   - validates_presence_of :requestor_id
  23 + validates_presence_of :login, :email, :environment_id, :on => :create, :message => _('must be filled in')
26 24  
27 25 validates_format_of :email, :on => :create, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda { |obj| !obj.email.blank? })
28 26  
... ... @@ -30,10 +28,10 @@ class ChangePassword &lt; Task
30 28 unless data.login.blank? || data.email.blank?
31 29 user = User.find_by_login_and_environment_id(data.login, data.environment_id)
32 30 if user.nil?
33   - data.errors.add(:login, _('%{fn} is not a valid username.').fix_i18n)
  31 + data.errors.add(:login, _('is invalid or user does not exists.'))
34 32 else
35 33 if user.email != data.email
36   - data.errors.add(:email)
  34 + data.errors.add(:email, _('does not match the username you filled in'))
37 35 end
38 36 end
39 37 end
... ...
app/models/enterprise.rb
... ... @@ -17,7 +17,7 @@ class Enterprise &lt; Organization
17 17 after_save_reindex [:products], :with => :delayed_job
18 18 extra_data_for_index :product_categories
19 19 def product_categories
20   - products.map{|p| p.category_full_name}.compact
  20 + products.includes(:product_category).map{|p| p.category_full_name}.compact
21 21 end
22 22  
23 23 N_('Organization website'); N_('Historic and current context'); N_('Activities short description'); N_('City'); N_('State'); N_('Country'); N_('ZIP code')
... ...
app/models/environment.rb
... ... @@ -123,10 +123,23 @@ class Environment &lt; ActiveRecord::Base
123 123 'xmpp_chat' => _('XMPP/Jabber based chat'),
124 124 'show_zoom_button_on_article_images' => _('Show a zoom link on all article images'),
125 125 'captcha_for_logged_users' => _('Ask captcha when a logged user comments too'),
126   - 'skip_new_user_email_confirmation' => _('Skip e-mail confirmation for new users')
  126 + 'skip_new_user_email_confirmation' => _('Skip e-mail confirmation for new users'),
  127 + 'send_welcome_email_to_new_users' => _('Send welcome e-mail to new users'),
  128 + 'allow_change_of_redirection_after_login' => _('Allow users to set the page to redirect after login')
127 129 }
128 130 end
129 131  
  132 + def self.login_redirection_options
  133 + {
  134 + 'keep_on_same_page' => _('Stays on the same page the user was before login.'),
  135 + 'site_homepage' => _('Redirects the user to the environment homepage.'),
  136 + 'user_profile_page' => _('Redirects the user to his profile page.'),
  137 + 'user_homepage' => _('Redirects the user to his homepage.'),
  138 + 'user_control_panel' => _('Redirects the user to his control panel.')
  139 + }
  140 + end
  141 + validates_inclusion_of :redirection_after_login, :in => Environment.login_redirection_options.keys, :allow_nil => true
  142 +
130 143 # #################################################
131 144 # Relationships and applied behaviour
132 145 # #################################################
... ... @@ -530,6 +543,31 @@ class Environment &lt; ActiveRecord::Base
530 543 signup_fields
531 544 end
532 545  
  546 + serialize :signup_welcome_text, Hash
  547 + def signup_welcome_text
  548 + self[:signup_welcome_text] ||= {}
  549 + end
  550 +
  551 + def signup_welcome_text_subject
  552 + self.signup_welcome_text[:subject]
  553 + end
  554 +
  555 + def signup_welcome_text_subject=(subject)
  556 + self.signup_welcome_text[:subject] = subject
  557 + end
  558 +
  559 + def signup_welcome_text_body
  560 + self.signup_welcome_text[:body]
  561 + end
  562 +
  563 + def signup_welcome_text_body=(body)
  564 + self.signup_welcome_text[:body] = body
  565 + end
  566 +
  567 + def has_signup_welcome_text?
  568 + signup_welcome_text && !signup_welcome_text_body.blank?
  569 + end
  570 +
533 571 # #################################################
534 572 # Validations
535 573 # #################################################
... ... @@ -591,8 +629,8 @@ class Environment &lt; ActiveRecord::Base
591 629 end
592 630  
593 631 has_many :articles, :through => :profiles
594   - def recent_documents(limit = 10)
595   - self.articles.recent(limit)
  632 + def recent_documents(limit = 10, options = {}, pagination = true)
  633 + self.articles.recent(limit, options, pagination)
596 634 end
597 635  
598 636 has_many :events, :through => :profiles, :source => :articles, :class_name => 'Event'
... ... @@ -766,4 +804,53 @@ class Environment &lt; ActiveRecord::Base
766 804 def image_galleries
767 805 portal_community ? portal_community.image_galleries : []
768 806 end
  807 +
  808 + serialize :languages
  809 +
  810 + before_validation do |environment|
  811 + environment.default_language = nil if environment.default_language.blank?
  812 + end
  813 +
  814 + validate :default_language_available
  815 + validate :languages_available
  816 +
  817 + def locales
  818 + if languages.present?
  819 + languages.inject({}) {|r, l| r.merge({l => Noosfero.locales[l]})}
  820 + else
  821 + Noosfero.locales
  822 + end
  823 + end
  824 +
  825 + def default_locale
  826 + default_language || Noosfero.default_locale
  827 + end
  828 +
  829 + def available_locales
  830 + locales_list = locales.keys
  831 + # move English to the beginning
  832 + if locales_list.include?('en')
  833 + locales_list = ['en'] + (locales_list - ['en']).sort
  834 + end
  835 + locales_list
  836 + end
  837 +
  838 + private
  839 +
  840 + def default_language_available
  841 + if default_language.present? && !available_locales.include?(default_language)
  842 + errors.add(:default_language, _('is not available.'))
  843 + end
  844 + end
  845 +
  846 + def languages_available
  847 + if languages.present?
  848 + languages.each do |language|
  849 + if !Noosfero.available_locales.include?(language)
  850 + errors.add(:languages, _('have unsupported languages.'))
  851 + break
  852 + end
  853 + end
  854 + end
  855 + end
769 856 end
... ...
app/models/external_feed.rb
... ... @@ -19,9 +19,15 @@ class ExternalFeed &lt; ActiveRecord::Base
19 19 article.valid?
20 20 end
21 21  
  22 + def address=(new_address)
  23 + self.fetched_at = nil unless address == new_address
  24 + super(new_address)
  25 + end
  26 +
22 27 def clear
23 28 # do nothing
24 29 end
  30 +
25 31 def finish_fetch
26 32 if self.only_once && self.update_errors.zero?
27 33 self.enabled = false
... ...
app/models/person.rb
... ... @@ -71,10 +71,7 @@ class Person &lt; Profile
71 71 Friendship.find(:all, :conditions => { :friend_id => person.id}).each { |friendship| friendship.destroy }
72 72 end
73 73  
74   - after_destroy :destroy_user
75   - def destroy_user
76   - self.user.destroy if self.user
77   - end
  74 + belongs_to :user, :dependent => :delete
78 75  
79 76 def can_control_scrap?(scrap)
80 77 begin
... ... @@ -183,7 +180,8 @@ class Person &lt; Profile
183 180 include MaybeAddHttp
184 181  
185 182 def active_fields
186   - environment ? environment.active_person_fields : []
  183 + fields = environment ? environment.active_person_fields : []
  184 + fields << 'email'
187 185 end
188 186  
189 187 def required_fields
... ... @@ -253,7 +251,7 @@ class Person &lt; Profile
253 251  
254 252 def is_admin?(environment = nil)
255 253 environment ||= self.environment
256   - role_assignments.select { |ra| ra.resource == environment }.map{|ra|ra.role.permissions}.any? do |ps|
  254 + role_assignments.includes([:role, :resource]).select { |ra| ra.resource == environment }.map{|ra|ra.role.permissions}.any? do |ps|
257 255 ps.any? do |p|
258 256 ActiveRecord::Base::PERMISSIONS['Environment'].keys.include?(p)
259 257 end
... ... @@ -461,6 +459,10 @@ class Person &lt; Profile
461 459 Scrap.find_by_sql("SELECT id, updated_at, '#{Scrap.to_s}' AS klass FROM #{Scrap.table_name} WHERE scraps.receiver_id = #{self.id} AND scraps.scrap_id IS NULL UNION SELECT id, updated_at, '#{ActionTracker::Record.to_s}' AS klass FROM #{ActionTracker::Record.table_name} WHERE action_tracker.user_id = #{self.id} and action_tracker.verb != 'leave_scrap_to_self' and action_tracker.verb != 'add_member_in_community' ORDER BY updated_at DESC")
462 460 end
463 461  
  462 + def public_fields
  463 + self.fields_privacy.nil? ? self.active_fields : self.fields_privacy.reject{ |k, v| v != 'public' }.keys.map(&:to_s)
  464 + end
  465 +
464 466 protected
465 467  
466 468 def followed_by?(profile)
... ...
app/models/profile.rb
... ... @@ -57,6 +57,7 @@ class Profile &lt; ActiveRecord::Base
57 57 'view_private_content' => N_('View private content'),
58 58 'publish_content' => N_('Publish content'),
59 59 'invite_members' => N_('Invite members'),
  60 + 'send_mail_to_members' => N_('Send e-Mail to members'),
60 61 }
61 62  
62 63 acts_as_accessible
... ... @@ -147,6 +148,7 @@ class Profile &lt; ActiveRecord::Base
147 148 settings_items :redirect_l10n, :type => :boolean, :default => false
148 149 settings_items :public_content, :type => :boolean, :default => true
149 150 settings_items :description
  151 + settings_items :fields_privacy, :type => :hash, :default => {}
150 152  
151 153 validates_length_of :description, :maximum => 550, :allow_nil => true
152 154  
... ... @@ -402,8 +404,8 @@ class Profile &lt; ActiveRecord::Base
402 404 #
403 405 # +limit+ is the maximum number of documents to be returned. It defaults to
404 406 # 10.
405   - def recent_documents(limit = 10, options = {})
406   - self.articles.recent(limit, options)
  407 + def recent_documents(limit = 10, options = {}, pagination = true)
  408 + self.articles.recent(limit, options, pagination)
407 409 end
408 410  
409 411 def last_articles(limit = 10, options = {})
... ... @@ -879,6 +881,15 @@ private :generate_url, :url_options
879 881 []
880 882 end
881 883  
  884 + # field => privacy (e.g.: "address" => "public")
  885 + def fields_privacy
  886 + self.data[:fields_privacy]
  887 + end
  888 +
  889 + def public_fields
  890 + self.active_fields
  891 + end
  892 +
882 893 private
883 894 def self.f_categories_label_proc(environment)
884 895 ids = environment.top_level_category_as_facet_ids
... ... @@ -975,4 +986,8 @@ private :generate_url, :url_options
975 986 end
976 987 end
977 988  
  989 + validates_inclusion_of :redirection_after_login, :in => Environment.login_redirection_options.keys, :allow_nil => true
  990 + def preferred_login_redirection
  991 + redirection_after_login.blank? ? environment.redirection_after_login : redirection_after_login
  992 + end
978 993 end
... ...
app/models/profile_list_block.rb
... ... @@ -14,12 +14,13 @@ class ProfileListBlock &lt; Block
14 14  
15 15 def profile_list
16 16 result = nil
  17 + visible_profiles = profiles.visible.includes([:image,:domains,:preferred_domain,:environment])
17 18 if !prioritize_profiles_with_image
18   - result = profiles.visible.all(:limit => limit, :order => 'updated_at DESC').sort_by{ rand }
19   - elsif profiles.visible.with_image.count >= limit
20   - result = profiles.visible.with_image.all(:limit => limit * 5, :order => 'updated_at DESC').sort_by{ rand }
  19 + result = visible_profiles.all(:limit => limit, :order => 'updated_at DESC').sort_by{ rand }
  20 + elsif visible_profiles.with_image.count >= limit
  21 + result = visible_profiles.with_image.all(:limit => limit * 5, :order => 'updated_at DESC').sort_by{ rand }
21 22 else
22   - result = profiles.visible.with_image.sort_by{ rand } + profiles.visible.without_image.all(:limit => limit * 5, :order => 'updated_at DESC').sort_by{ rand }
  23 + result = visible_profiles.with_image.sort_by{ rand } + visible_profiles.without_image.all(:limit => limit * 5, :order => 'updated_at DESC').sort_by{ rand }
23 24 end
24 25 result.slice(0..limit-1)
25 26 end
... ...
app/models/recent_documents_block.rb
... ... @@ -16,11 +16,9 @@ class RecentDocumentsBlock &lt; Block
16 16  
17 17 include ActionController::UrlWriter
18 18 def content(args={})
19   - docs = self.limit.nil? ? owner.recent_documents : owner.recent_documents(self.limit)
20   -
  19 + docs = self.limit.nil? ? owner.recent_documents(nil, {}, false) : owner.recent_documents(self.limit, {}, false)
21 20 block_title(title) +
22 21 content_tag('ul', docs.map {|item| content_tag('li', link_to(h(item.title), item.url))}.join("\n"))
23   -
24 22 end
25 23  
26 24 def footer
... ...
app/models/task.rb
... ... @@ -31,7 +31,7 @@ class Task &lt; ActiveRecord::Base
31 31 end
32 32 end
33 33  
34   - belongs_to :requestor, :class_name => 'Person', :foreign_key => :requestor_id
  34 + belongs_to :requestor, :class_name => 'Profile', :foreign_key => :requestor_id
35 35 belongs_to :target, :foreign_key => :target_id, :polymorphic => true
36 36  
37 37 validates_uniqueness_of :code, :on => :create
... ...
app/models/user.rb
... ... @@ -30,7 +30,7 @@ class User &lt; ActiveRecord::Base
30 30  
31 31 after_create do |user|
32 32 user.person ||= Person.new
33   - user.person.attributes = user.person_data.merge(:identifier => user.login, :user_id => user.id, :environment_id => user.environment_id)
  33 + user.person.attributes = user.person_data.merge(:identifier => user.login, :user => user, :environment_id => user.environment_id)
34 34 user.person.name ||= user.login
35 35 user.person.visible = false unless user.activated?
36 36 user.person.save!
... ... @@ -73,6 +73,18 @@ class User &lt; ActiveRecord::Base
73 73 :environment => user.environment.name,
74 74 :url => user.environment.top_url
75 75 end
  76 +
  77 + def signup_welcome_email(user)
  78 + email_body = user.environment.signup_welcome_text_body.gsub('{user_name}', user.name)
  79 + email_subject = user.environment.signup_welcome_text_subject
  80 +
  81 + content_type 'text/html'
  82 + recipients user.email
  83 +
  84 + from "#{user.environment.name} <#{user.environment.contact_email}>"
  85 + subject email_subject.blank? ? _("Welcome to environment %s") % [user.environment.name] : email_subject
  86 + body email_body
  87 + end
76 88 end
77 89  
78 90 def signup!
... ... @@ -88,13 +100,13 @@ class User &lt; ActiveRecord::Base
88 100 attr_protected :activated_at
89 101  
90 102 # Virtual attribute for the unencrypted password
91   - attr_accessor :password
  103 + attr_accessor :password, :name
92 104  
93 105 validates_presence_of :login, :email
94 106 validates_format_of :login, :with => Profile::IDENTIFIER_FORMAT, :if => (lambda {|user| !user.login.blank?})
95 107 validates_presence_of :password, :if => :password_required?
96   - validates_presence_of :password_confirmation, :if => :password_required?, :if => (lambda {|user| !user.password.blank?})
97   - validates_length_of :password, :within => 4..40, :if => :password_required?, :if => (lambda {|user| !user.password.blank?})
  108 + validates_presence_of :password_confirmation, :if => :password_required?
  109 + validates_length_of :password, :within => 4..40, :if => :password_required?
98 110 validates_confirmation_of :password, :if => :password_required?
99 111 validates_length_of :login, :within => 2..40, :if => (lambda {|user| !user.login.blank?})
100 112 validates_length_of :email, :within => 3..100, :if => (lambda {|user| !user.email.blank?})
... ... @@ -117,7 +129,17 @@ class User &lt; ActiveRecord::Base
117 129 self.activated_at = Time.now.utc
118 130 self.activation_code = nil
119 131 self.person.visible = true
120   - self.person.save! && self.save!
  132 + begin
  133 + self.person.save! && self.save!
  134 + rescue Exception => exception
  135 + logger.error(exception.to_s)
  136 + false
  137 + else
  138 + if environment.enabled?('send_welcome_email_to_new_users') && environment.has_signup_welcome_text?
  139 + User::Mailer.delay.deliver_signup_welcome_email(self)
  140 + end
  141 + true
  142 + end
121 143 end
122 144  
123 145 def activated?
... ... @@ -228,7 +250,12 @@ class User &lt; ActiveRecord::Base
228 250 end
229 251  
230 252 def name
231   - person ? person.name : login
  253 + name = (self[:name] || login)
  254 + person.nil? ? name : (person.name || name)
  255 + end
  256 +
  257 + def name= name
  258 + self[:name] = name
232 259 end
233 260  
234 261 def enable_email!
... ... @@ -274,6 +301,11 @@ class User &lt; ActiveRecord::Base
274 301 15 # in minutes
275 302 end
276 303  
  304 +
  305 + def not_require_password!
  306 + @is_password_required = false
  307 + end
  308 +
277 309 protected
278 310 # before filter
279 311 def encrypt_password
... ... @@ -282,9 +314,13 @@ class User &lt; ActiveRecord::Base
282 314 self.password_type ||= User.system_encryption_method.to_s
283 315 self.crypted_password = encrypt(password)
284 316 end
285   -
  317 +
286 318 def password_required?
287   - crypted_password.blank? || !password.blank?
  319 + (crypted_password.blank? || !password.blank?) && is_password_required?
  320 + end
  321 +
  322 + def is_password_required?
  323 + @is_password_required.nil? ? true : @is_password_required
288 324 end
289 325  
290 326 def make_activation_code
... ...
app/sweepers/article_sweeper.rb
... ... @@ -10,17 +10,24 @@ class ArticleSweeper &lt; ActiveRecord::Observer
10 10 expire_caches(article)
11 11 end
12 12  
  13 + def before_update(article)
  14 + if article.parent_id_change
  15 + Article.find(article.parent_id_was).touch if article.parent_id_was
  16 + end
  17 + end
  18 +
13 19 protected
14 20  
15 21 def expire_caches(article)
16   - article.hierarchy.each { |a| a.touch if a != article }
  22 + return if !article.environment
  23 + article.hierarchy(true).each { |a| a.touch if a != article }
17 24 blocks = article.profile.blocks
18 25 blocks += article.profile.environment.blocks if article.profile.environment
19 26 blocks = blocks.select{|b|[RecentDocumentsBlock, BlogArchivesBlock].any?{|c| b.kind_of?(c)}}
20 27 BlockSweeper.expire_blocks(blocks)
21 28 env = article.profile.environment
22 29 if env && (env.portal_community == article.profile)
23   - Noosfero.locales.keys.each do |locale|
  30 + article.environment.locales.keys.each do |locale|
24 31 expire_fragment(env.portal_news_cache_key(locale))
25 32 end
26 33 end
... ...
app/sweepers/block_sweeper.rb
... ... @@ -7,10 +7,11 @@ class BlockSweeper &lt; ActiveRecord::Observer
7 7  
8 8 # Expire block's all languages cache
9 9 def expire_block(block)
  10 + return if !block.environment
10 11 regex = '-[a-z]*$'
11 12 clean_ck = block.cache_key.gsub(/#{regex}/,'')
12 13  
13   - Noosfero.locales.keys.each do |locale|
  14 + block.environment.locales.keys.each do |locale|
14 15 expire_timeout_fragment("#{clean_ck}-#{locale}")
15 16 end
16 17 end
... ...
app/views/account/forgot_password.rhtml
1 1 <h1><%= _('Forgot your password?') %></h1>
2 2  
3   -<%= error_messages_for :change_password %>
  3 +<%= error_messages_for :change_password, :header_message => _('Instructions to password recovery could not be sent'), :message => nil %>
4 4  
5 5 <% labelled_form_for :change_password, @change_password, :url => { :action => 'forgot_password' } do |f| %>
6 6  
... ...
app/views/account/login.rhtml
... ... @@ -13,6 +13,8 @@
13 13  
14 14 <%= f.password_field :password %>
15 15  
  16 + <%= @plugins.dispatch(:login_extra_contents).collect { |content| instance_eval(&content) }.join("") %>
  17 +
16 18 <% button_bar do %>
17 19 <%= submit_button( 'login', _('Log in') )%>
18 20 <% if is_thickbox %>
... ... @@ -23,8 +25,13 @@
23 25 <% end %>
24 26  
25 27 <% button_bar do %>
26   - <%= button :add, _("New user"), :controller => 'account', :action => 'signup' %>
27   - <%= button :help, _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %>
  28 + <% unless @plugins.dispatch(:allow_user_registration).include?(false) %>
  29 + <%= button :add, _("New user"), :controller => 'account', :action => 'signup' %>
  30 + <% end %>
  31 +
  32 + <% unless @plugins.dispatch(:allow_password_recovery).include?(false) %>
  33 + <%= button :help, _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %>
  34 + <% end %>
28 35 <% end %>
29 36  
30 37 </div><!-- end class="login-box" -->
... ...
app/views/account/login_block.rhtml
... ... @@ -9,25 +9,30 @@
9 9 @user ||= User.new
10 10 %>
11 11  
12   - <% labelled_form_for :user, @user,
13   - :url => login_url do |f| %>
  12 + <% labelled_form_for :user, @user, :url => login_url do |f| %>
14 13  
15   - <%= f.text_field :login, :onchange => 'this.value = convToValidLogin( this.value )' %>
  14 + <%= f.text_field :login, :onchange => 'this.value = convToValidLogin( this.value )' %>
16 15  
17   - <%= f.password_field :password %>
  16 + <%= f.password_field :password %>
  17 +
  18 + <%= @plugins.dispatch(:login_extra_contents).collect { |content| instance_eval(&content) }.join("") %>
18 19  
19 20 <% button_bar do %>
20 21 <%= submit_button( 'login', _('Log in') )%>
21   - <%= link_to content_tag( 'span', _('New user') ),
22   - { :controller => 'account', :action => 'signup' },
23   - :class => 'button with-text icon-add' %>
  22 + <% unless @plugins.dispatch(:allow_user_registration).include?(false) %>
  23 + <%= link_to content_tag( 'span', _('New user') ),
  24 + { :controller => 'account', :action => 'signup' },
  25 + :class => 'button with-text icon-add' %>
  26 + <% end %>
24 27 <% end %>
25 28  
26 29 <% end %>
27 30  
28   - <p class="forgot-passwd">
29   - <%= link_to _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %>
30   - </p>
  31 + <% unless @plugins.dispatch(:allow_password_recovery).include?(false) %>
  32 + <p class="forgot-passwd">
  33 + <%= link_to _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %>
  34 + </p>
  35 + <% end %>
31 36  
32 37 </div>
33 38  
... ...
app/views/admin_panel/_signup_welcome_text.rhtml 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<div class='description'>
  2 + <%= _('This text will be sent to new users if the feature "Send welcome e-mail to new users" is enabled on environment.') %><br/><br/>
  3 + <%= _('Including %s on body, it will be replaced by the real name of the e-mail recipient.') % content_tag('code', '{user_name}') %>
  4 +</div>
  5 +
  6 +<%= labelled_form_field(_('Subject'), text_field(:environment, :signup_welcome_text_subject, :style => 'width:100%')) %>
  7 +<%= labelled_form_field(_('Body'), text_area(:environment, :signup_welcome_text_body, :cols => 40, :style => 'width: 100%', :class => 'mceEditor')) %>
... ...
app/views/admin_panel/_site_info.rhtml
1 1 <%= required labelled_form_field(_('Site name'), text_field(:environment, :name)) %>
  2 +<%= labelled_form_field(_('Contact email'), text_field(:environment, :contact_email)) %>
  3 +<% themes_options = Theme.system_themes.map {|theme| [theme.name, theme.id] }.sort %>
  4 +<%= labelled_form_field(_('Theme'), select(:environment, :theme, options_for_select(themes_options, environment.theme))) %>
2 5 <%= required f.text_field(:reports_lower_bound, :size => 3) %>
  6 +<%= labelled_form_field(_('Default language'), select(:environment, :default_language, environment.locales.invert, { :selected => environment.default_locale, :include_blank => true })) %>
  7 +<%= label_tag :languages, _('Available languages') %>
  8 +<br />
  9 +
  10 +<%
  11 + fields = Noosfero.locales.map do |value, name|
  12 + labelled_check_box(name, "environment[languages][#{value}]", true, environment.available_locales.include?(value))
  13 + end
  14 +%>
  15 +<%= balanced_table(fields)%>
  16 +
  17 +<br />
3 18 <%= labelled_form_field _('Homepage content'), text_area(:environment, :description, :cols => 40, :style => 'width: 90%', :class => 'mceEditor') %>
... ...
app/views/admin_panel/site_info.rhtml
... ... @@ -10,6 +10,8 @@
10 10 :content => (render :partial => 'site_info', :locals => {:f => f})} %>
11 11 <% tabs << {:title => _('Terms of use'), :id => 'terms-of-use',
12 12 :content => (render :partial => 'terms_of_use', :locals => {:f => f})} %>
  13 + <% tabs << {:title => _('Signup welcome text'), :id => 'signup-welcome-text',
  14 + :content => (render :partial => 'signup_welcome_text', :locals => {:f => f})} %>
13 15 <%= render_tabs(tabs) %>
14 16 <% button_bar do %>
15 17 <%= submit_button(:save, _('Save'), :cancel => {:action => 'index'}) %>
... ...
app/views/blocks/profile_info_actions/community.rhtml
... ... @@ -32,7 +32,7 @@
32 32 { :profile => profile.identifier,
33 33 :controller => 'contact',
34 34 :action => 'new' },
35   - :class => 'button with-text icon-menu-mail' %>
  35 + {:class => 'button with-text icon-menu-mail', :title => _('Send an e-mail to the administrators')} %>
36 36 </li>
37 37 <% end %>
38 38  
... ...
app/views/box_organizer/_block_types.rhtml 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +<% block_types.in_groups_of(2) do |block1, block2| %>
  2 + <div style='float: left; width: 48%; padding-top: 2px;'>
  3 + <%= labelled_radio_button(block1.description, :type, block1.name) %>
  4 + </div>
  5 + <% if block2 %>
  6 + <div style='float: left; width: 48%; padding-top: 2px;'>
  7 + <%= labelled_radio_button(block2.description, :type, block2.name) %>
  8 + </div>
  9 + <% end %>
  10 +<% end %>
... ...
app/views/box_organizer/_highlights_block.rhtml
1 1 <strong><%= _('Highlights') %></strong>
2   -<div id='edit-highlights-block'>
  2 +<div id='edit-highlights-block' style='width:450px'>
3 3 <table id='highlights' class='noborder'>
4 4 <tr><th><%= _('Image') %></th><th><%= _('Address') %></th><th><%= _('Position') %></th><th><%= _('Title') %></th></tr>
5 5 <% for image in @block.images do %>
... ...
app/views/box_organizer/_link_list_block.rhtml
1 1 <strong><%= _('Links') %></strong>
2   -<div id='edit-link-list-block'>
  2 +<div id='edit-link-list-block' style='width:450px'>
3 3 <table id='links' class='noborder'>
4 4 <tr><th><%= _('Icon') %></th><th><%= _('Name') %></th><th><%= _('Address') %></th></tr>
5 5 <% for link in @block.links do %>
... ...
app/views/box_organizer/_raw_html_block.rhtml
1   -<%= labelled_form_field(_('HTML code'), text_area(:block, :html, :style => 'width: 100%', :rows => 15)) %>
  1 +<%= labelled_form_field(_('HTML code'), text_area(:block, :html, :style => 'width: 90%', :rows => 15)) %>
... ...
app/views/box_organizer/add_block.rhtml
1   -<% form_tag do %>
2   -
3   - <p><%= _('In what area do you want to put your new block?') %></p>
4   -
5   - <%# FIXME hardcoded stuff %>
6   - <%= select_tag('box_id', options_for_select(@boxes.select { |item| item.position != 1 }.map {|item| [ _("Area %d") % item.position, item.id]})) %>
7   -
8   - <p><%= _('Select the type of block you want to add to your page.') %></p>
9   -
10   - <% @block_types.in_groups_of(2) do |block1, block2| %>
11   - <div style='float: left; width: 48%; padding-top: 2px;'>
12   - <%= radio_button_tag('type', block1.name) %>
13   - <%= label_tag "type_#{block1.name.downcase}", block1.description %>
  1 +<div style='height:350px'>
  2 + <% form_tag do %>
  3 +
  4 + <p><%= _('In what area do you want to put your new block?') %></p>
  5 +
  6 + <% @boxes.each do |box| %>
  7 + <%= labelled_radio_button(_("Area %d") % box.position, :box_id, box.id, box.central?, { :class => 'box-position', 'data-position' => box.position }) %>
  8 + <% end %>
  9 +
  10 + <script type="text/javascript">
  11 + (function ($) {
  12 + $(document).ready(function () {
  13 + $(".box-position").live('change', function () {
  14 + if ($(this).attr('data-position') == '1') {
  15 + $('#center-block-types').show();
  16 + $('#side-block-types').hide();
  17 + } else {
  18 + $('#center-block-types').hide();
  19 + $('#side-block-types').show();
  20 + };
  21 + });
  22 + })})(jQuery);
  23 + </script>
  24 +
  25 + <p><%= _('Select the type of block you want to add to your page.') %></p>
  26 +
  27 + <div id='center-block-types'>
  28 + <%= render :partial => 'block_types', :locals => { :block_types => @center_block_types } %>
14 29 </div>
15   - <% if block2 %>
16   - <div style='float: left; width: 48%; padding-top: 2px;'>
17   - <%= radio_button_tag('type', block2.name) %>
18   - <%= label_tag "type_#{block2.name.downcase}", block2.description %>
19   - </div>
  30 +
  31 + <div id='side-block-types' style='display:none'>
  32 + <%= render :partial => 'block_types', :locals => { :block_types => @side_block_types } %>
  33 + </div>
  34 +
  35 + <br style='clear: both'/>
  36 +
  37 + <% button_bar do %>
  38 + <%= submit_button(:add, _("Add")) %>
  39 + <%= colorbox_close_button(_('Close')) %>
20 40 <% end %>
21   - <% end %>
22   - <br style='clear: both'/>
23   -
24   - <% button_bar do %>
25   - <%= submit_button(:add, _("Add")) %>
26   - <%= lightbox_close_button(_('Close')) %>
27   - <% end %>
28 41  
29   -<% end %>
  42 + <% end %>
  43 +</div>
... ...
app/views/box_organizer/edit.rhtml
1   -<h2><%= _('Editing block') %></h2>
  1 +<div style='width: 500px;'>
  2 + <h2><%= _('Editing block') %></h2>
2 3  
3   -<% form_tag(:action => 'save', :id => @block.id) do %>
  4 + <% form_tag(:action => 'save', :id => @block.id) do %>
4 5  
5   - <%= labelled_form_field(_('Custom title for this block: '), text_field(:block, :title, :maxlength => 20)) %>
  6 + <%= labelled_form_field(_('Custom title for this block: '), text_field(:block, :title, :maxlength => 20)) %>
6 7  
7   - <%= render :partial => partial_for_class(@block.class) %>
  8 + <%= render :partial => partial_for_class(@block.class) %>
8 9  
9   - <%= labelled_form_field _('Display this block:'), '' %>
10   - <div style='margin-left: 10px'>
11   - <%= radio_button(:block, :display, 'always') %>
12   - <%= label_tag('block_display_always', _('In all pages')) %>
13   - <br/>
14   - <%= radio_button(:block, :display, 'home_page_only') %>
15   - <%= label_tag('block_display_home_page_only', _('Only in the homepage')) %>
16   - <br/>
17   - <%= radio_button(:block, :display, 'except_home_page') %>
18   - <%= label_tag('block_display_except_home_page', _('In all pages, except in the homepage')) %>
19   - <br/>
20   - <%= radio_button(:block, :display, 'never') %>
21   - <%= label_tag('block_display_never', _("Don't display")) %>
22   - </div>
  10 + <%= labelled_form_field _('Display this block:'), '' %>
  11 + <div style='margin-left: 10px'>
  12 + <%= radio_button(:block, :display, 'always') %>
  13 + <%= label_tag('block_display_always', _('In all pages')) %>
  14 + <br/>
  15 + <%= radio_button(:block, :display, 'home_page_only') %>
  16 + <%= label_tag('block_display_home_page_only', _('Only in the homepage')) %>
  17 + <br/>
  18 + <%= radio_button(:block, :display, 'except_home_page') %>
  19 + <%= label_tag('block_display_except_home_page', _('In all pages, except in the homepage')) %>
  20 + <br/>
  21 + <%= radio_button(:block, :display, 'never') %>
  22 + <%= label_tag('block_display_never', _("Don't display")) %>
  23 + </div>
23 24  
24   - <%= labelled_form_field(_('Show for:'), select(:block, :language, [ [ _('all languages'), 'all']] + Noosfero.locales.map {|key, value| [value, key]} )) %>
  25 + <%= labelled_form_field(_('Show for:'), select(:block, :language, [ [ _('all languages'), 'all']] + environment.locales.map {|key, value| [value, key]} )) %>
25 26  
26   - <% button_bar do %>
27   - <%= submit_button(:save, _('Save')) %>
28   - <%= lightbox_close_button(_('Cancel')) %>
29   - <% end %>
  27 + <% button_bar do %>
  28 + <%= submit_button(:save, _('Save')) %>
  29 + <%= colorbox_close_button(_('Cancel')) %>
  30 + <% end %>
30 31  
31   -<% end %>
  32 + <% end %>
  33 +</div>
... ...
app/views/box_organizer/index.rhtml
1 1 <h1><%= _('Editing sideboxes')%></h1>
2 2  
3 3 <% button_bar do %>
4   - <%= lightbox_button('add', _('Add a block'), { :action => 'add_block' }) %>
  4 + <%= colorbox_button('add', _('Add a block'), { :action => 'add_block' }) %>
5 5 <%= button(:back, _('Back to control panel'), :controller => (profile.nil? ? 'admin_panel': 'profile_editor')) %>
6 6 <% end %>
... ...
app/views/catalog/index.rhtml
... ... @@ -30,6 +30,9 @@
30 30 <li class="product <%= status.join(' ') %>">
31 31 <ul>
32 32 <li class="product-image-link">
  33 + <% if product.highlighted? %>
  34 + <%= link_to image_tag(themed_path('/images/star.png'), :class => 'star', :alt => _('Highlighted product')), product_path(product) %>
  35 + <% end %>
33 36 <% if product.image %>
34 37 <div class="zoomable-image">
35 38 <%= link_to_product product, :class => 'product-big', :style => "background-image: url(#{product.default_image(:big)})" %>
... ...
app/views/cms/_blog.rhtml
... ... @@ -62,7 +62,7 @@
62 62  
63 63 <% f.fields_for 'feed', @article.feed do |feed| %>
64 64 <%= labelled_form_field(_('Limit of posts in RSS Feed'), feed.select(:limit, [5, 10, 20, 50])) %>
65   - <%= labelled_form_field(_('Include in RSS Feed only posts from language:'), feed.select(:language, [[_('All'), nil ]] + Noosfero.locales.map { |k,v| [v, k]})) %>
  65 + <%= labelled_form_field(_('Include in RSS Feed only posts from language:'), feed.select(:language, [[_('All'), nil ]] + environment.locales.map { |k,v| [v, k]})) %>
66 66 <% end %>
67 67  
68 68 <% f.fields_for 'external_feed_builder', @article.external_feed do |efeed| %>
... ...
app/views/cms/_general_fields.html.erb
  1 +<%= select_profile_folder(_('Parent folder:'), 'article[parent_id]', profile, @article.parent_id) %>
1 2 <%= labelled_form_field(_('License'), select(:article, :license_id, options_for_select_with_title([[_('None'), nil]] + profile.environment.licenses.map {|license| [license.name, license.id]}, @article.license ? @article.license.id : nil))) %>
... ...
app/views/cms/_rss_feed.rhtml
... ... @@ -6,7 +6,7 @@
6 6  
7 7 <%= required labelled_form_field(_('Limit of articles'), text_field(:article, :limit)) %>
8 8  
9   -<%= labelled_form_field(_('Include in RSS Feed only posts from language:'), f.select(:language, [[_('All'), nil ]] + Noosfero.locales.map { |k,v| [v, k]})) %>
  9 +<%= labelled_form_field(_('Include in RSS Feed only posts from language:'), f.select(:language, [[_('All'), nil ]] + environment.locales.map { |k,v| [v, k]})) %>
10 10  
11 11 <%= labelled_form_field(_('Use as item description:'), select(:article, :feed_item_description, [ [ _('Article abstract'), 'abstract'], [ _('Article body'), 'body']])) %>
12 12  
... ...
app/views/cms/_text_editor_sidebar.rhtml
... ... @@ -9,8 +9,7 @@
9 9 <div id='media-upload-form'>
10 10 <% form_tag({ :action => 'media_upload' }, :multipart => true) do %>
11 11 <div class='formfield'>
12   - <%# TODO duplicated from partial upload_file_form %>
13   - <%= labelled_form_field(_('Choose folder to upload files:'), select_tag('parent_id', options_for_select([[profile.identifier, '']] + profile.folders.collect {|f| [ profile.identifier + '/' + f.full_name, f.id ] }))) %>
  12 + <%= select_profile_folder(_('Choose folder to upload files:'), :parent_id, profile) %>
14 13 </div>
15 14 <p><%= file_field_tag('file1') %></p>
16 15 <p><%= file_field_tag('file2') %></p>
... ...
app/views/cms/_upload_file_form.rhtml
1 1 <% if @parent %>
2 2 <%= hidden_field_tag('parent_id', @parent.id) %>
3 3 <% else %>
4   - <%= labelled_form_field(_('Choose folder to upload files:'), select_tag('parent_id', options_for_select([[profile.identifier, '']] + @folders.collect {|f| [ profile.identifier + '/' + f.full_name, f.id ] }))) %>
  4 + <%= select_profile_folder(_('Choose folder to upload files:'), :parent_id, profile) %>
5 5 <% end %>
6 6  
7 7 <div id='uploaded_files'>
... ...
app/views/cms/_uploaded_file.rhtml
1 1 <%= labelled_form_field(_('Title'), text_field(:article, :title, :maxlength => 60)) %>
  2 +
  3 +<%= render :partial => 'general_fields' %>
  4 +
2 5 <%= labelled_form_field(_('Description'), text_area(:article, :abstract, :rows => 3, :cols => 64)) %>
3 6 <% if @article.image? %>
4 7 <%= f.text_field(:external_link, :size => 64) %>
... ...
app/views/contact/new.rhtml
1   -<h1><%= _('Send an e-mail to %s') % profile.name %></h1>
  1 +<% if profile.person? %>
  2 + <h1><%= _('Send an e-mail to %s') % profile.name %></h1>
  3 +<% else %>
  4 + <h1><%= _('Send an e-mail to administrators') %></h1>
2 5  
3   -<%= error_messages_for 'contact' %>
  6 + <div class='tooltip'><%= _("The e-mail will be sent to the administrators of '%s'") % profile.name %></div>
  7 +<% end %>
4 8  
  9 +<%= error_messages_for 'contact' %>
5 10  
6 11 <% labelled_form_for :contact, @contact do |f| %>
7 12 <%= hidden_field_tag(:confirm, 'false') %>
... ...
app/views/features/index.rhtml
... ... @@ -26,17 +26,12 @@ Check all the features you want to enable for your environment, uncheck all the
26 26  
27 27 <h2><%= _('Configure features') %></h2>
28 28  
29   -<table>
30   - <tr>
31   - <th><%= _('Option') %></th>
32   - <th><%= _('Choice') %></th>
33   - </tr>
34   - <tr>
35   - <td><%= _('Organization Approval Method') %></td>
36   - <td><%= select_organization_approval_method('environment', 'organization_approval_method') %></td>
37   - </tr>
38   -</table>
39   -
  29 +<h3><%= _('Page to redirect after login') %></h3>
  30 + <%= select 'environment', 'redirection_after_login', Environment.login_redirection_options.map{|key,value|[value,key]} %>
  31 +<hr/>
  32 +<h3><%= _('Organization Approval Method') %></h3>
  33 + <%= select_organization_approval_method('environment', 'organization_approval_method') %>
  34 +<hr/>
40 35  
41 36 <div>
42 37 <% button_bar do %>
... ...
app/views/layouts/_javascript.rhtml
... ... @@ -2,7 +2,8 @@
2 2 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'colorbox',
3 3 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate',
4 4 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput',
5   -'add-and-join', 'report-abuse', 'catalog', 'manage-products', :cache => 'cache-general' %>
  5 +'add-and-join', 'report-abuse', 'catalog', 'manage-products',
  6 +'jquery-ui-timepicker-addon', :cache => 'cache-general' %>
6 7  
7 8 <% language = FastGettext.locale %>
8 9 <% %w{messages methods}.each do |type| %>
... ...
app/views/layouts/application-ng.rhtml
... ... @@ -56,10 +56,18 @@
56 56 <%= usermenu_logged_in %>
57 57 </span>
58 58 <span class='not-logged-in' style='display: none'>
59   - <%= _("<span class='login'>%s</span> <span class='or'>or</span> <span class='signup'>%s</span>") % [thickbox_inline_popup_link('<i class="icon-menu-login"></i><strong>' + _('Login') + '</strong>', login_url, 'inlineLoginBox', :id => 'link_login'), link_to('<strong>' + _('Sign up') + '</strong>', :controller => 'account', :action => 'signup') ] %>
  59 +
  60 + <%= _("<span class='login'>%s</span>") % thickbox_inline_popup_link('<i class="icon-menu-login"></i><strong>' + _('Login') + '</strong>', login_url, 'inlineLoginBox', :id => 'link_login') %>
  61 + <%= @plugins.dispatch(:alternative_authentication_link).collect { |content| instance_eval(&content) }.join("") %>
  62 +
60 63 <div id='inlineLoginBox' style='display: none;'>
61 64 <%= render :file => 'account/login', :locals => { :is_thickbox => true } %>
62 65 </div>
  66 +
  67 + <% unless @plugins.dispatch(:allow_user_registration).include?(false) %>
  68 + <%= _("<span class='or'>or</span> <span class='signup'>%s</span>") % link_to('<strong>' + _('Sign up') + '</strong>', :controller => 'account', :action => 'signup')%>
  69 + <% end %>
  70 +
63 71 </span>
64 72 <form action="/search" class="search_form" method="get" class="clean">
65 73 <input name="query" size="15" title="<%=_('Search...')%>" onfocus="this.form.className='focused';" onblur="this.form.className=''" />
... ...
app/views/layouts/application.rhtml
... ... @@ -80,7 +80,7 @@
80 80 </div><!-- id='navigation_bar' -->
81 81  
82 82 <div id="language-selector">
83   - <%= language_chooser(:element => 'dropdown') %>
  83 + <%= language_chooser(environment, :element => 'dropdown') %>
84 84 </div>
85 85  
86 86 <div id="user_box">
... ...
app/views/profile/_common.rhtml
1   -
2   -<script type="text/javascript">
3   - jQuery( function() {
4   - var parent_selector = '.profile-wall-description, .profile-activity-description, .profile-network-description';
5   - var child_selector = '.icon-delete, .icon-reply';
6   - jQuery(parent_selector).live('mouseover', function () { jQuery(this).find(child_selector).css('visibility', 'visible'); });
7   - jQuery(parent_selector).live('mouseout', function () { jQuery(this).find(child_selector).css('visibility', 'hidden'); });
8   - });
9   -</script>
10   -
11 1 <% unless @action %>
12 2 <% cache_timeout(profile.cache_key + '-profile-general-info', 4.hours) do %>
13 3 <tr>
... ...
app/views/profile/_person_profile.rhtml
... ... @@ -13,31 +13,16 @@
13 13 <td><%= show_date(profile.created_at) %></td>
14 14 </tr>
15 15  
16   - <% if profile == user || profile.friends.include?(user) %>
17   - <tr>
18   - <th colspan='2'><%= _('Contact')%></th>
19   - </tr>
20   - <%= display_field(_('Address:'), profile, :address) %>
21   - <%= display_field(_('ZIP code:'), profile, :zip_code) %>
22   - <%= display_field(_('Contact phone:'), profile, :contact_phone) %>
23   - <%= display_field(_('e-Mail:'), profile, :email, true) { |email| link_to_email(email) } %>
24   - <% end %>
  16 + <%= display_contact profile %>
25 17  
26 18 <% cache_timeout(profile.relationships_cache_key, 4.hours) do %>
27   - <% if !(profile.organization.blank? && profile.organization_website.blank?) && (profile.active_fields.include?('organization') || profile.active_fields.include?('organization_website')) %>
28   - <tr>
29   - <th colspan='2'><%= _('Work')%></th>
30   - </tr>
31   - <% end %>
32   - <%= display_field(_('Organization:'), profile, :organization) %>
33   - <%= display_field(_('Organization website:'), profile, :organization_website) { |url| link_to(url, url) }%>
34   -
  19 + <%= display_work_info profile %>
35 20  
36 21 <% if !environment.enabled?('disable_asset_enterprises') && !profile.enterprises.empty? %>
37 22 <tr>
38 23 <th colspan='2'><%= __('Enterprises') %></th>
39 24 </tr>
40   - <% profile.enterprises.each do |item| %>
  25 + <% profile.enterprises.includes(:environment,:domains, :preferred_domain).each do |item| %>
41 26 <tr>
42 27 <td></td>
43 28 <td><%= button 'menu-enterprise', item.name, item.url %></td>
... ... @@ -59,6 +44,6 @@
59 44  
60 45 <%= render :partial => 'common' %>
61 46  
62   - </table>
63   -<% end %>
  47 + <% end %>
  48 +</table>
64 49  
... ...
app/views/profile/members.rhtml
... ... @@ -16,8 +16,13 @@
16 16  
17 17 <% button_bar do %>
18 18 <%= button :back, _('Go back'), { :controller => 'profile' } %>
19   - <% if profile.community? and user and user.has_permission?(:invite_members, profile) %>
20   - <%= button :search, _('Invite your friends to join %s') % profile.name, :controller => 'invite', :action => 'select_address_book' %>
  19 + <% if profile.community? and user %>
  20 + <% if user.has_permission?(:invite_members, profile) %>
  21 + <%= button :search, _('Invite your friends to join %s') % profile.name, :controller => 'invite', :action => 'select_address_book' %>
  22 + <% end %>
  23 + <% if user.has_permission?(:send_mail_to_members, profile) %>
  24 + <%= button :send, _('Send e-mail to members'), :controller => 'profile', :action => 'send_mail' %>
  25 + <% end %>
21 26 <% end %>
22 27 <% end %>
23 28  
... ...
app/views/profile/send_mail.rhtml 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +<h1><%= h profile.short_name(50) %></h1>
  2 +
  3 +<h2><%= _('Send e-mail to members') %></h2>
  4 +
  5 +<%= error_messages_for :mailing %>
  6 +
  7 +<%= render :file => 'shared/tiny_mce' %>
  8 +
  9 +<% form_for :mailing, :url => {:action => 'send_mail'}, :html => {:id => 'mailing-form'} do |f| %>
  10 + <%= labelled_form_field(_('Subject:'), f.text_field(:subject)) %>
  11 + <%= labelled_form_field(_('Body:'), f.text_area(:body, :class => 'mceEditor')) %>
  12 + <%= submit_button(:send, _('Send')) %>
  13 + <%= button :cancel, _('Cancel e-mail'), :back %>
  14 +<% end %>
... ...
app/views/profile_editor/_person.rhtml
... ... @@ -2,9 +2,19 @@
2 2  
3 3 <%= required_fields_message %>
4 4  
5   - <%= required f.text_field(:name) %>
  5 + <div class="field-with-privacy-selector">
  6 + <%= required f.text_field(:name) %>
  7 + <div class="field-privacy-selector">
  8 + <%= unchangeable_privacy_field @profile %>
  9 + </div>
  10 + </div>
6 11  
7   - <%= required f.text_field(:email) %>
  12 + <div class="field-with-privacy-selector">
  13 + <%= required f.text_field(:email) %>
  14 + <div class="field-privacy-selector">
  15 + <%= profile_field_privacy_selector @profile, 'email' %>
  16 + </div>
  17 + </div>
8 18  
9 19 <%= @plugins.dispatch(:profile_info_extra_contents).collect { |content| instance_eval(&content) }.join("") %>
10 20  
... ...
app/views/profile_editor/_person_form.rhtml
... ... @@ -61,11 +61,3 @@
61 61 <%= optional_field(@person, 'professional_activity', f.text_field(:professional_activity, :rel => _('Professional activity'))) %>
62 62 <%= optional_field(@person, 'organization', f.text_field(:organization, :rel => _('Organization'))) %>
63 63 <%= optional_field(@person, 'organization_website', f.text_field(:organization_website, :rel => _('Organization website'))) %>
64   -
65   -<% optional_field(@person, 'image') do %>
66   - <div id="profile_choose_picture">
67   - <% f.fields_for :image_builder, @person.image do |i| %>
68   - <%= file_field_or_thumbnail(_('Image:'), @person.image, i) %><span class="person_image_maxsize"><%= _("Max size: %s (.jpg, .gif, .png)")% Image.max_size.to_humanreadable %></span>
69   - <% end %>
70   - </div>
71   -<% end %>
... ...
app/views/profile_editor/edit.rhtml
... ... @@ -12,73 +12,37 @@
12 12  
13 13 <%= render :partial => partial_for_class(@profile.class), :locals => { :f => f } %>
14 14  
15   - <% unless @profile.person? && @environment.active_person_fields.include?('image') %>
16   - <div id="profile_change_picture">
  15 + <div id="profile_change_picture_title">
17 16 <h2><%= _('Change picture') %></h2>
  17 + <span><%= unchangeable_privacy_field @profile %></span>
  18 + </div>
  19 + <div id="profile_change_picture">
18 20 <% f.fields_for :image_builder, @profile.image do |i| %>
19 21 <%= file_field_or_thumbnail(_('Image:'), @profile.image, i) %><%= _("Max size: %s (.jpg, .gif, .png)")% Image.max_size.to_humanreadable %>
20 22 <% end %>
21 23 </div>
22   - <% end %>
23 24  
24 25 <h2><%= _('Privacy options') %></h2>
25 26  
26 27 <% if profile.person? %>
27   - <table>
28   - <tr>
29   - <th style='text-align: right;'>
30   - <%= _('This profile is:') %>
31   - </th>
32   - <th>
33   - <%= radio_button 'profile_data', 'public_profile', 'true' %>
34   - <label for="profile_data_public_profile_true"><u><%= _('Public') %></u></label>
35   - </th>
36   - <th style='padding: 2px 10px 2px 2px;'>
37   - <%= radio_button 'profile_data', 'public_profile', 'false' %>
38   - <label for="profile_data_public_profile_false"><u><%= _('Private') %></u></label>
39   - </th>
40   - </tr>
41   - <tr>
42   - <td> <%= _('Activate Intranet access (restricted area only for me)') %> </td><td><%= _('Yes') %></td><td><%= _('Yes') %></td>
43   - </tr>
44   - <tr>
45   - <td> <%= _('Include my contact in directory of people') %> </td><td><%= _('Yes') %></td><td><%= _('Yes') %></td>
46   - </tr>
47   - <tr>
48   - <td> <%= _('Show my contents to all internet users') %> </td><td><%= _('Yes') %></td><td><%= _('No') %></td>
49   - </tr>
50   - <tr>
51   - <td> <%= _('Show my contents to my friends (person)') %> </td><td><%= _('Yes') %></td><td><%= _('Yes') %></td>
52   - </tr>
53   - </table>
  28 + <div>
  29 + <%= labelled_radio_button _('Public &mdash; show my contents to all internet users'), 'profile_data[public_profile]', true, @profile.public_profile? %>
  30 + </div>
  31 + <div>
  32 + <%= labelled_radio_button _('Private &mdash; show my contents only to friends'), 'profile_data[public_profile]', false, !@profile.public_profile? %>
  33 + </div>
54 34 <% else %>
55   - <table>
56   - <tr>
57   - <th style='text-align: right;'>
58   - <%= _('This profile is:') %>
59   - </th>
60   - <th>
61   - <%= radio_button 'profile_data', 'public_profile', 'true' %>
62   - <label for="profile_data_public_profile_true"><u><%= _('Public') %></u></label>
63   - </th>
64   - <th style='padding: 2px 10px 2px 2px;'>
65   - <%= radio_button 'profile_data', 'public_profile', 'false' %>
66   - <label for="profile_data_public_profile_false"><u><%= _('Private') %></u></label>
67   - </th>
68   - </tr>
69   - <tr>
70   - <td> <%= _('Activate Intranet access (restricted area only for members)') %> </td><td><%= _('Yes') %></td><td><%= _('Yes') %></td>
71   - </tr>
72   - <tr>
73   - <td> <%= _('Include this group directory of groups') %> </td><td><%= _('Yes') %></td><td><%= _('Yes') %></td>
74   - </tr>
75   - <tr>
76   - <td> <%= _('Show content of this group to all internet users') %> </td><td><%= _('Yes') %></td><td><%= _('No') %></td>
77   - </tr>
78   - <tr>
79   - <td> <%= _('Show content of this group to members') %> </td><td><%= _('Yes') %></td><td><%= _('Yes') %></td>
80   - </tr>
81   - </table>
  35 + <div>
  36 + <%= labelled_radio_button _('Public &mdash; show content of this group to all internet users'), 'profile_data[public_profile]', true, @profile.public_profile? %>
  37 + </div>
  38 + <div>
  39 + <%= labelled_radio_button _('Private &mdash; show content of this group only to members'), 'profile_data[public_profile]', false, !@profile.public_profile? %>
  40 + </div>
  41 + <% end %>
  42 +
  43 + <% if environment.enabled?('allow_change_of_redirection_after_login') %>
  44 + <h2><%= _('Page to redirect after login') %></h2>
  45 + <%= select 'profile_data', 'redirection_after_login', Environment.login_redirection_options.map{|key,value|[value,key]}, { :selected => @profile.preferred_login_redirection} %>
82 46 <% end %>
83 47  
84 48 <h2><%= _('Translations') %></h2>
... ...
app/views/profile_members/_index_buttons.rhtml
... ... @@ -4,7 +4,9 @@
4 4 <% if profile.community? and user.has_permission?(:invite_members, profile) %>
5 5 <%= button :search, _('Invite your friends to join %s') % profile.short_name, :controller => 'invite', :action => 'select_address_book' %>
6 6 <% end %>
7   - <%= button :send, _('Send e-mail to members'), :action => 'send_mail' %>
  7 + <% if profile.community? and user.has_permission?(:send_mail_to_members, profile) %>
  8 + <%= button :send, _('Send e-mail to members'), :controller => 'profile', :action => 'send_mail' %>
  9 + <% end %>
8 10 <% @plugins.dispatch(:manage_members_extra_buttons).each do |plugin_button| %>
9 11 <%= button plugin_button[:icon], plugin_button[:title], plugin_button[:url] %>
10 12 <% end %>
... ...
app/views/profile_members/send_mail.rhtml
... ... @@ -1,14 +0,0 @@
1   -<h1><%= h profile.short_name(50) %></h1>
2   -
3   -<h2><%= _('Send e-mail to members') %></h2>
4   -
5   -<%= error_messages_for :mailing %>
6   -
7   -<%= render :file => 'shared/tiny_mce' %>
8   -
9   -<% form_for :mailing, :url => {:action => 'send_mail'}, :html => {:id => 'mailing-form'} do |f| %>
10   - <%= labelled_form_field(_('Subject:'), f.text_field(:subject)) %>
11   - <%= labelled_form_field(_('Body:'), f.text_area(:body, :class => 'mceEditor')) %>
12   - <%= submit_button(:send, _('Send')) %>
13   - <%= button :cancel, _('Cancel e-mail'), :action => 'index' %>
14   -<% end %>
app/views/search/_product.rhtml
1 1 <% extra_content = @plugins.dispatch(:asset_product_extras, product, product.enterprise).collect { |content| instance_eval(&content) } %>
2 2 <% extra_properties = @plugins.dispatch(:asset_product_properties, product)%>
3 3  
4   -<li class="search-product-item">
  4 +<li class="search-product-item <%= 'highlighted' if product.highlighted? %>">
5 5  
6 6 <div class="search-product-item-first-column">
7 7 <%= render :partial => 'search/image', :object => product %>
... ...
app/views/tasks/_approve_article_accept_details.rhtml
1 1 <%= render :file => 'shared/tiny_mce' %>
2 2  
3 3 <%= labelled_form_field(_('Name for publishing'), f.text_field(:name)) %>
4   -<%= select_profile_folder(_('Select the folder where the article must be published'), "tasks[#{task.id}][task]", 'article_parent_id', task.target) %>
  4 +<%= select_profile_folder(_('Select the folder where the article must be published'), "tasks[#{task.id}][task][article_parent_id]", task.target) %>
5 5 <%= labelled_form_field(_('Highlight this article'), f.check_box(:highlighted)) %>
6 6  
7 7 <% tiny = task.article && task.article.tiny_mce? ? {:tiny_mce => true} : {} %>
... ...
app/views/tasks/_suggest_article_accept_details.rhtml
... ... @@ -6,7 +6,7 @@
6 6 <%= labelled_form_field(_('Source'), f.text_field(:source_name)) %>
7 7 <%= labelled_form_field(_("Source URL"), f.text_field(:source)) %>
8 8  
9   -<%= select_profile_folder(_('Select the folder where the article must be published'), "tasks[#{task.id}][task]", 'article_parent_id', task.target) %>
  9 +<%= select_profile_folder(_('Select the folder where the article must be published'), "tasks[#{task.id}][task][article_parent_id]", task.target) %>
10 10 <%= labelled_form_field(_('Highlight this article'), f.check_box(:highlighted)) %>
11 11  
12 12 <%= render :partial => 'shared/lead_and_body', :locals => {:tiny_mce => true, :f => f, :abstract_method => 'article_abstract', :body_method => 'article_body', :lead_id => task.id} %>
... ...
app/views/tasks/_task.rhtml
... ... @@ -50,13 +50,13 @@
50 50 <% fields_for "tasks[#{task.id}][task]", task do |f| %>
51 51 <% if task.accept_details %>
52 52 <div id="on-accept-information-<%=task.id%>" style="display: none">
53   - <%= render :partial => partial_for_task_class(task.class, :accept_details), :locals => {:task => task, :f => f} %>
  53 + <%= render :partial => partial_for_class(task.class, :accept_details), :locals => {:task => task, :f => f} %>
54 54 </div>
55 55 <% end %>
56 56  
57 57 <% if task.reject_details %>
58 58 <div id="on-reject-information-<%=task.id%>" style="display: none">
59   - <%= render :partial => partial_for_task_class(task.class, :reject_details), :locals => {:task => task, :f => f} %>
  59 + <%= render :partial => partial_for_class(task.class, :reject_details), :locals => {:task => task, :f => f} %>
60 60 </div>
61 61 <% end %>
62 62 <% end %>
... ...
app/views/user/mailer/signup_welcome_email.rhtml 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +<!DOCTYPE html>
  2 +<html>
  3 + <head>
  4 + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  5 + </head>
  6 + <body>
  7 + <p><%= word_wrap @body %></p>
  8 + </body>
  9 +</html>
... ...
config/routes.rb
... ... @@ -19,16 +19,17 @@ ActionController::Routing::Routes.draw do |map|
19 19  
20 20 # -- just remember to delete public/index.html.
21 21 # You can have the root of your site routed by hooking up ''
  22 + map.root :controller => "home", :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } }
22 23 map.connect '', :controller => "home", :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } }
23 24 map.home 'site/:action', :controller => 'home'
24 25  
25   - map.connect 'images/*stuff', :controller => 'not_found', :action => 'index'
26   - map.connect 'stylesheets/*stuff', :controller => 'not_found', :action => 'index'
27   - map.connect 'designs/*stuff', :controller => 'not_found', :action => 'index'
28   - map.connect 'articles/*stuff', :controller => 'not_found', :action => 'index'
29   - map.connect 'javascripts/*stuff', :controller => 'not_found', :action => 'index'
30   - map.connect 'thumbnails/*stuff', :controller => 'not_found', :action => 'index'
31   - map.connect 'user_themes/*stuff', :controller => 'not_found', :action => 'index'
  26 + map.connect 'images/*stuff', :controller => 'not_found', :action => 'nothing'
  27 + map.connect 'stylesheets/*stuff', :controller => 'not_found', :action => 'nothing'
  28 + map.connect 'designs/*stuff', :controller => 'not_found', :action => 'nothing'
  29 + map.connect 'articles/*stuff', :controller => 'not_found', :action => 'nothing'
  30 + map.connect 'javascripts/*stuff', :controller => 'not_found', :action => 'nothing'
  31 + map.connect 'thumbnails/*stuff', :controller => 'not_found', :action => 'nothing'
  32 + map.connect 'user_themes/*stuff', :controller => 'not_found', :action => 'nothing'
32 33  
33 34 # online documentation
34 35 map.doc 'doc', :controller => 'doc', :action => 'index'
... ...
db/migrate/20120823215007_add_languages_and_default_language_to_environment.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class AddLanguagesAndDefaultLanguageToEnvironment < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :environments, :languages, :string
  4 + add_column :environments, :default_language, :string
  5 + end
  6 +
  7 + def self.down
  8 + remove_column :environments, :languages
  9 + remove_column :environments, :default_language
  10 + end
  11 +end
... ...
db/migrate/20120824165019_add_permission_for_send_mail_to_members_to_admin_and_moderator_roles.rb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +class AddPermissionForSendMailToMembersToAdminAndModeratorRoles < ActiveRecord::Migration
  2 + def self.up
  3 + Environment.all.map(&:id).each do |id|
  4 + role = Profile::Roles.admin(id)
  5 + role.permissions += ['send_mail_to_members']
  6 + role.save!
  7 + role = Profile::Roles.moderator(id)
  8 + role.permissions += ['send_mail_to_members']
  9 + role.save!
  10 + end
  11 + end
  12 +
  13 + def self.down
  14 + Environment.all.map(&:id).each do |id|
  15 + role = Profile::Roles.admin(id)
  16 + role.permissions -= ['send_mail_to_members']
  17 + role.save!
  18 + role = Profile::Roles.moderator(id)
  19 + role.permissions -= ['send_mail_to_members']
  20 + role.save!
  21 + end
  22 + end
  23 +end
... ...
db/migrate/20120824183534_add_redirection_after_login_to_environment.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class AddRedirectionAfterLoginToEnvironment < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :environments, :redirection_after_login, :string, :default => 'keep_on_same_page'
  4 + end
  5 +
  6 + def self.down
  7 + remove_column :environments, :redirection_after_login
  8 + end
  9 +end
... ...
db/migrate/20120824184046_add_redirection_after_login_to_profiles.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class AddRedirectionAfterLoginToProfiles < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :profiles, :redirection_after_login, :string
  4 + end
  5 +
  6 + def self.down
  7 + remove_column :profiles, :redirection_after_login
  8 + end
  9 +end
... ...
db/migrate/20121008185303_add_signup_welcome_text.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class AddSignupWelcomeText < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :environments, :signup_welcome_text, :text
  4 + end
  5 +
  6 + def self.down
  7 + remove_column :environments, :signup_welcome_text
  8 + end
  9 +end
... ...
db/schema.rb
... ... @@ -9,7 +9,7 @@
9 9 #
10 10 # It's strongly recommended to check this file into your version control system.
11 11  
12   -ActiveRecord::Schema.define(:version => 20120825185219) do
  12 +ActiveRecord::Schema.define(:version => 20121008185303) do
13 13  
14 14 create_table "abuse_reports", :force => true do |t|
15 15 t.integer "reporter_id"
... ... @@ -261,6 +261,10 @@ ActiveRecord::Schema.define(:version =&gt; 20120825185219) do
261 261 t.datetime "created_at"
262 262 t.datetime "updated_at"
263 263 t.integer "reports_lower_bound", :default => 0, :null => false
  264 + t.string "redirection_after_login", :default => "keep_on_same_page"
  265 + t.text "signup_welcome_text"
  266 + t.string "languages"
  267 + t.string "default_language"
264 268 end
265 269  
266 270 create_table "external_feeds", :force => true do |t|
... ... @@ -438,6 +442,7 @@ ActiveRecord::Schema.define(:version =&gt; 20120825185219) do
438 442 t.string "national_region_code"
439 443 t.boolean "is_template", :default => false
440 444 t.integer "template_id"
  445 + t.string "redirection_after_login"
441 446 end
442 447  
443 448 add_index "profiles", ["environment_id"], :name => "index_profiles_on_environment_id"
... ...
debian/changelog
... ... @@ -4,6 +4,12 @@ noosfero (0.39.0~1) UNRELEASED; urgency=low
4 4  
5 5 -- Antonio Terceiro <terceiro@debian.org> Thu, 30 Aug 2012 14:55:10 -0300
6 6  
  7 +noosfero (0.38.3) unstable; urgency=low
  8 +
  9 + * Bugfixes release
  10 +
  11 + -- Daniela Soares Feitosa <daniela@colivre.coop.br> Wed, 07 Nov 2012 20:25:51 -0200
  12 +
7 13 noosfero (0.38.2) unstable; urgency=low
8 14  
9 15 * Bugfixes release
... ...
features/login.feature
... ... @@ -9,7 +9,8 @@ Feature: login
9 9 | joaosilva | Joao Silva |
10 10  
11 11 Scenario: login from portal homepage
12   - Given I am not logged in
  12 + Given feature "allow_change_of_redirection_after_login" is disabled on environment
  13 + And I am not logged in
13 14 And I go to the homepage
14 15 And I fill in the following:
15 16 | Username | joaosilva |
... ... @@ -19,7 +20,8 @@ Feature: login
19 20 And I should be logged in as "joaosilva"
20 21  
21 22 Scenario: login from some profile page
22   - Given I am not logged in
  23 + Given feature "allow_change_of_redirection_after_login" is disabled on environment
  24 + And I am not logged in
23 25 And the following users
24 26 | login | name |
25 27 | mariasilva | Maria Silva |
... ... @@ -35,7 +37,8 @@ Feature: login
35 37 Then I should be on Maria Silva's homepage
36 38  
37 39 Scenario: view my control panel
38   - Given I am not logged in
  40 + Given feature "allow_change_of_redirection_after_login" is disabled on environment
  41 + And I am not logged in
39 42 And I go to Joao Silva's control panel
40 43 And I should be on login page
41 44 And I fill in the following:
... ... @@ -48,3 +51,146 @@ Feature: login
48 51 Given I am logged in as "joaosilva"
49 52 And I go to login page
50 53 Then I should be on Joao Silva's control panel
  54 +
  55 + Scenario: stay on the same page after login if this is the environment default
  56 + Given feature "allow_change_of_redirection_after_login" is enabled on environment
  57 + And I am not logged in
  58 + And the environment is configured to stay on the same page after login
  59 + And the following users
  60 + | login | name |
  61 + | mariasilva | Maria Silva |
  62 + And the following articles
  63 + | owner | name | homepage |
  64 + | mariasilva | my home page | true |
  65 + And I go to Maria Silva's homepage
  66 + And I follow "Login"
  67 + And I fill in the following:
  68 + | Username | joaosilva |
  69 + | Password | 123456 |
  70 + When I press "Log in"
  71 + Then I should be on Maria Silva's homepage
  72 +
  73 + Scenario: go to site homepage if this is the environment default
  74 + Given feature "allow_change_of_redirection_after_login" is enabled on environment
  75 + And I am not logged in
  76 + And the environment is configured to redirect to site homepage after login
  77 + And I go to Joao Silva's homepage
  78 + And I follow "Login"
  79 + And I fill in the following:
  80 + | Username | joaosilva |
  81 + | Password | 123456 |
  82 + When I press "Log in"
  83 + Then I should be on the homepage
  84 +
  85 + Scenario: go to user profile after login if this is the environment default
  86 + Given feature "allow_change_of_redirection_after_login" is enabled on environment
  87 + And I am not logged in
  88 + And the environment is configured to redirect to user profile page after login
  89 + And I go to the homepage
  90 + And I follow "Login"
  91 + And I fill in the following:
  92 + | Username | joaosilva |
  93 + | Password | 123456 |
  94 + When I press "Log in"
  95 + Then I should be on Joao Silva's profile
  96 +
  97 + Scenario: go to profile homepage after login if this is the environment default
  98 + Given the following articles
  99 + | owner | name | body | homepage |
  100 + | joaosilva | Sample Article | This is an article | true |
  101 + And feature "allow_change_of_redirection_after_login" is enabled on environment
  102 + And I am not logged in
  103 + And the environment is configured to redirect to profile homepage after login
  104 + And I go to the homepage
  105 + And I follow "Login"
  106 + And I fill in the following:
  107 + | Username | joaosilva |
  108 + | Password | 123456 |
  109 + When I press "Log in"
  110 + Then I should be on Joao Silva's homepage
  111 +
  112 + Scenario: go to profile control panel after login if this is the environment default
  113 + Given feature "allow_change_of_redirection_after_login" is enabled on environment
  114 + And I am not logged in
  115 + And the environment is configured to redirect to profile control panel after login
  116 + And I go to the homepage
  117 + And I follow "Login"
  118 + And I fill in the following:
  119 + | Username | joaosilva |
  120 + | Password | 123456 |
  121 + When I press "Log in"
  122 + Then I should be on Joao Silva's control panel
  123 +
  124 + Scenario: stay on the same page after login if this is the profile default
  125 + Given feature "allow_change_of_redirection_after_login" is enabled on environment
  126 + And I am not logged in
  127 + And the environment is configured to redirect to site homepage after login
  128 + And the profile joaosilva is configured to stay on the same page after login
  129 + And the following users
  130 + | login | name |
  131 + | mariasilva | Maria Silva |
  132 + And the following articles
  133 + | owner | name | homepage |
  134 + | mariasilva | my home page | true |
  135 + And I go to Maria Silva's homepage
  136 + And I follow "Login"
  137 + And I fill in the following:
  138 + | Username | joaosilva |
  139 + | Password | 123456 |
  140 + When I press "Log in"
  141 + Then I should be on Maria Silva's homepage
  142 +
  143 + Scenario: go to site homepage if this is the profile default
  144 + Given feature "allow_change_of_redirection_after_login" is enabled on environment
  145 + And I am not logged in
  146 + And the environment is configured to stay on the same page after login
  147 + And the profile joaosilva is configured to redirect to site homepage after login
  148 + And I go to Joao Silva's homepage
  149 + And I follow "Login"
  150 + And I fill in the following:
  151 + | Username | joaosilva |
  152 + | Password | 123456 |
  153 + When I press "Log in"
  154 + Then I should be on the homepage
  155 +
  156 + Scenario: go to user profile after login if this is the profile default
  157 + Given feature "allow_change_of_redirection_after_login" is enabled on environment
  158 + And I am not logged in
  159 + And the environment is configured to stay on the same page after login
  160 + And the profile joaosilva is configured to redirect to user profile page after login
  161 + And I go to the homepage
  162 + And I follow "Login"
  163 + And I fill in the following:
  164 + | Username | joaosilva |
  165 + | Password | 123456 |
  166 + When I press "Log in"
  167 + Then I should be on Joao Silva's profile
  168 +
  169 + Scenario: go to profile homepage after login if this is the profile default
  170 + Given the following articles
  171 + | owner | name | body | homepage |
  172 + | joaosilva | Sample Article | This is an article | true |
  173 + And feature "allow_change_of_redirection_after_login" is enabled on environment
  174 + And I am not logged in
  175 + And the environment is configured to stay on the same page after login
  176 + And the profile joaosilva is configured to redirect to profile homepage after login
  177 + And I go to the homepage
  178 + And I follow "Login"
  179 + And I fill in the following:
  180 + | Username | joaosilva |
  181 + | Password | 123456 |
  182 + When I press "Log in"
  183 + Then I should be on Joao Silva's homepage
  184 +
  185 + Scenario: go to profile control panel after login if this is the profile default
  186 + Given feature "allow_change_of_redirection_after_login" is enabled on environment
  187 + And I am not logged in
  188 + And the environment is configured to stay on the same page after login
  189 + And the profile joaosilva is configured to redirect to profile control panel after login
  190 + And I go to the homepage
  191 + And I follow "Login"
  192 + And I fill in the following:
  193 + | Username | joaosilva |
  194 + | Password | 123456 |
  195 + When I press "Log in"
  196 + Then I should be on Joao Silva's control panel
... ...
features/send_email_to_organization_members.feature
1 1 Feature: send emails to organization members
2   - As a organization administrator
  2 + As a organization administrator or moderator
3 3 I want to send email to all members
4 4  
5 5 Background:
6 6 Given the following users
7 7 | login | name |
8 8 | joaosilva | Joao Silva |
  9 + | jose | Jose Silva |
  10 + | manoel | Manoel Silva |
9 11 And the following communities
10 12 | identifier | name |
11 13 | sample-community | Sample Community |
12 14 And "Joao Silva" is admin of "Sample Community"
  15 + And "Jose Silva" is moderator of "Sample Community"
  16 + And "Manoel Silva" is a member of "Sample Community"
13 17  
14 18 Scenario: Cant access if not logged in
15 19 Given I am not logged in
16   - When I go to /myprofile/sample-community/profile_members/send_mail
  20 + When I go to /profile/sample-community/send_mail
17 21 Then I should be on login page
18 22  
19 23 Scenario: Cant access as normal user
... ... @@ -21,7 +25,7 @@ Feature: send emails to organization members
21 25 | login |
22 26 | josesilva |
23 27 And I am logged in as "josesilva"
24   - When I go to /myprofile/sample-community/profile_members/send_mail
  28 + When I go to /profile/sample-community/send_mail
25 29 Then I should see "Access denied"
26 30  
27 31 Scenario: Send e-mail to members
... ... @@ -39,7 +43,7 @@ Feature: send emails to organization members
39 43 And I follow "Send e-mail to members"
40 44 And I fill in "body" with "We have some news"
41 45 When I press "Send"
42   - Then I should be on /myprofile/sample-community/profile_members/send_mail
  46 + Then I should be on /profile/sample-community/send_mail
43 47  
44 48 Scenario: Not send e-mail to members if body is blank
45 49 Given I am logged in as "joaosilva"
... ... @@ -47,7 +51,7 @@ Feature: send emails to organization members
47 51 And I follow "Send e-mail to members"
48 52 And I fill in "Subject" with "Hello, user!"
49 53 When I press "Send"
50   - Then I should be on /myprofile/sample-community/profile_members/send_mail
  54 + Then I should be on /profile/sample-community/send_mail
51 55  
52 56 Scenario: Cancel creation of mailing
53 57 Given I am logged in as "joaosilva"
... ... @@ -55,3 +59,34 @@ Feature: send emails to organization members
55 59 And I follow "Send e-mail to members"
56 60 When I follow "Cancel e-mail"
57 61 Then I should be on Sample Community's members management
  62 +
  63 + Scenario: Cant access if has no send_mail_to_members permission
  64 + Given I am logged in as "manoel"
  65 + When I go to /profile/sample-community/send_mail
  66 + Then I should see "Access denied"
  67 +
  68 + Scenario: Show button "Send e-Mail to members" of community to an moderator
  69 + Given I am logged in as "jose"
  70 + When I go to Sample Community's members page
  71 + Then I should see "Send e-mail to members" link
  72 +
  73 + Scenario: Not show button "Send e-Mail to members" if user has no right permission
  74 + Given I am logged in as "manoel"
  75 + When I go to Sample Community's members page
  76 + Then I should not see "Send e-mail to members" link
  77 +
  78 + Scenario: Redirect back to profile members page after send mail
  79 + Given I am logged in as "jose"
  80 + When I go to Sample Community's members page
  81 + And I follow "Send e-mail to members"
  82 + And I fill in "Subject" with "Hello, member!"
  83 + And I fill in "body" with "We have some news"
  84 + When I press "Send"
  85 + Then I should be on Sample Community's members page
  86 +
  87 + Scenario: Back to profile members page after cancel creation of mailing
  88 + Given I am logged in as "jose"
  89 + And I go to Sample Community's members page
  90 + And I follow "Send e-mail to members"
  91 + When I follow "Cancel e-mail"
  92 + Then I should be on Sample Community's members page
... ...
features/step_definitions/noosfero_steps.rb
... ... @@ -355,6 +355,12 @@ Given /^&quot;(.+)&quot; is admin of &quot;(.+)&quot;$/ do |person, organization|
355 355 org.add_admin(user)
356 356 end
357 357  
  358 +Given /^"(.+)" is moderator of "(.+)"$/ do |person, organization|
  359 + org = Profile.find_by_name(organization)
  360 + user = Profile.find_by_name(person)
  361 + org.add_moderator(user)
  362 +end
  363 +
358 364 Then /^"(.+)" should be admin of "(.+)"$/ do |person, organization|
359 365 org = Organization.find_by_name(organization)
360 366 user = Person.find_by_name(person)
... ... @@ -706,3 +712,39 @@ When /^I make a AJAX request to (.*)$/ do |page|
706 712 header 'X-Requested-With', 'XMLHttpRequest'
707 713 visit(path_to(page))
708 714 end
  715 +
  716 +Given /^the environment is configured to (.*) after login$/ do |option|
  717 + redirection = case option
  718 + when 'stay on the same page'
  719 + 'keep_on_same_page'
  720 + when 'redirect to site homepage'
  721 + 'site_homepage'
  722 + when 'redirect to user profile page'
  723 + 'user_profile_page'
  724 + when 'redirect to profile homepage'
  725 + 'user_homepage'
  726 + when 'redirect to profile control panel'
  727 + 'user_control_panel'
  728 + end
  729 + environment = Environment.default
  730 + environment.redirection_after_login = redirection
  731 + environment.save
  732 +end
  733 +
  734 +Given /^the profile (.*) is configured to (.*) after login$/ do |profile, option|
  735 + redirection = case option
  736 + when 'stay on the same page'
  737 + 'keep_on_same_page'
  738 + when 'redirect to site homepage'
  739 + 'site_homepage'
  740 + when 'redirect to user profile page'
  741 + 'user_profile_page'
  742 + when 'redirect to profile homepage'
  743 + 'user_homepage'
  744 + when 'redirect to profile control panel'
  745 + 'user_control_panel'
  746 + end
  747 + profile = Profile.find_by_identifier(profile)
  748 + profile.redirection_after_login = redirection
  749 + profile.save
  750 +end
... ...
features/support/paths.rb
... ... @@ -108,6 +108,9 @@ module NavigationHelpers
108 108 when /the user data path/
109 109 '/account/user_data'
110 110  
  111 + when /^(.+)'s members page/
  112 + '/profile/%s/members' % Profile.find_by_name($1).identifier
  113 +
111 114 # Add more mappings here.
112 115 # Here is a more fancy example:
113 116 #
... ...
lib/acts_as_having_boxes.rb
... ... @@ -18,7 +18,7 @@ module ActsAsHavingBoxes
18 18 @blocks = nil
19 19 end
20 20 if @blocks.nil?
21   - @blocks = boxes.inject([]) do |acc,obj|
  21 + @blocks = boxes.includes(:blocks).inject([]) do |acc,obj|
22 22 acc.concat(obj.blocks)
23 23 end
24 24 @blocks.send(:extend, BlockArray)
... ...
lib/noosfero/plugin.rb
... ... @@ -320,6 +320,37 @@ class Noosfero::Plugin
320 320 nil
321 321 end
322 322  
  323 + # -> Add an alternative authentication method.
  324 + # Your plugin have to make the access control and return the logged user.
  325 + # returns = User
  326 + def alternative_authentication
  327 + nil
  328 + end
  329 +
  330 + # -> Adds adicional link to make the user authentication
  331 + # returns = lambda block that creates html code
  332 + def alternative_authentication_link
  333 + nil
  334 + end
  335 +
  336 + # -> Allow or not user registration
  337 + # returns = boolean
  338 + def allow_user_registration
  339 + true
  340 + end
  341 +
  342 + # -> Allow or not password recovery by users
  343 + # returns = boolean
  344 + def allow_password_recovery
  345 + true
  346 + end
  347 +
  348 + # -> Adds fields to the login form
  349 + # returns = lambda block that creates html code
  350 + def login_extra_contents
  351 + nil
  352 + end
  353 +
323 354 def method_missing(method, *args, &block)
324 355 # This is a generic hotspot for all controllers on Noosfero.
325 356 # If any plugin wants to define filters to run on any controller, the name of
... ...
plugins/custom_forms/controllers/custom_forms_plugin_myprofile_controller.rb 0 → 100644
... ... @@ -0,0 +1,147 @@
  1 +class CustomFormsPluginMyprofileController < MyProfileController
  2 +
  3 + protect 'post_content', :profile
  4 + def index
  5 + @forms = CustomFormsPlugin::Form.from(profile)
  6 + end
  7 +
  8 + def create
  9 + @form = CustomFormsPlugin::Form.new(:profile => profile)
  10 + @fields = []
  11 + @empty_field = CustomFormsPlugin::Field.new
  12 + if request.post?
  13 + begin
  14 + @form.update_attributes!(params[:form])
  15 + params[:fields] = format_kind(params[:fields])
  16 + params[:fields] = format_choices(params[:fields])
  17 + params[:fields] = set_form_id(params[:fields], @form.id)
  18 + create_fields(new_fields(params))
  19 + session['notice'] = _('Form created')
  20 + redirect_to :action => 'index'
  21 + rescue Exception => exception
  22 + logger.error(exception.to_s)
  23 + session['notice'] = _('Form could not be created')
  24 + end
  25 + end
  26 + end
  27 +
  28 + def edit
  29 + @form = CustomFormsPlugin::Form.find(params[:id])
  30 + @fields = @form.fields
  31 + @empty_field = CustomFormsPlugin::TextField.new
  32 + if request.post?
  33 + begin
  34 + @form.update_attributes!(params[:form])
  35 + params[:fields] = format_kind(params[:fields])
  36 + params[:fields] = format_choices(params[:fields])
  37 + remove_fields(params, @form)
  38 + create_fields(new_fields(params))
  39 + update_fields(edited_fields(params))
  40 + session['notice'] = _('Form updated')
  41 + redirect_to :action => 'index'
  42 + rescue Exception => exception
  43 + logger.error(exception.to_s)
  44 + session['notice'] = _('Form could not be updated')
  45 + end
  46 + end
  47 + end
  48 +
  49 + def remove
  50 + @form = CustomFormsPlugin::Form.find(params[:id])
  51 + begin
  52 + @form.destroy
  53 + session[:notice] = _('Form removed')
  54 + rescue
  55 + session[:notice] = _('Form could not be removed')
  56 + end
  57 + redirect_to :action => 'index'
  58 + end
  59 +
  60 + def submissions
  61 + @form = CustomFormsPlugin::Form.find(params[:id])
  62 + @submissions = @form.submissions
  63 + end
  64 +
  65 + def show_submission
  66 + @submission = CustomFormsPlugin::Submission.find(params[:id])
  67 + @form = @submission.form
  68 + end
  69 +
  70 + private
  71 +
  72 + def new_fields(params)
  73 + result = params[:fields].map {|id, hash| hash.has_key?(:real_id) ? nil : hash}.compact
  74 + result.delete_if {|field| field[:name].blank?}
  75 + result
  76 + end
  77 +
  78 + def edited_fields(params)
  79 + params[:fields].map {|id, hash| hash.has_key?(:real_id) ? hash : nil}.compact
  80 + end
  81 +
  82 + def create_fields(fields)
  83 + fields.each do |field|
  84 + case field[:type]
  85 + when 'text_field'
  86 + CustomFormsPlugin::TextField.create!(field)
  87 + when 'select_field'
  88 + CustomFormsPlugin::SelectField.create!(field)
  89 + else
  90 + CustomFormsPlugin::Field.create!(field)
  91 + end
  92 + end
  93 + end
  94 +
  95 + def update_fields(fields)
  96 + fields.each do |field_attrs|
  97 + field = CustomFormsPlugin::Field.find(field_attrs.delete(:real_id))
  98 + field.attributes = field_attrs
  99 + field.save! if field.changed?
  100 + end
  101 + end
  102 +
  103 + def format_kind(fields)
  104 + fields.each do |id, field|
  105 + next if field[:kind].blank?
  106 + kind = field.delete(:kind)
  107 + case kind
  108 + when 'radio'
  109 + field[:list] = false
  110 + field[:multiple] = false
  111 + when 'check_box'
  112 + field[:list] = false
  113 + field[:multiple] = true
  114 + when 'select'
  115 + field[:list] = true
  116 + field[:multiple] = false
  117 + when 'multiple_select'
  118 + field[:list] = true
  119 + field[:multiple] = true
  120 + end
  121 + end
  122 + fields
  123 + end
  124 +
  125 + def format_choices(fields)
  126 + fields.each do |id, field|
  127 + next if !field.has_key?(:choices)
  128 + field[:choices] = field[:choices].map {|key, value| value}.inject({}) do |result, choice|
  129 + hash = (choice[:name].blank? || choice[:value].blank?) ? {} : {choice[:name] => choice[:value]}
  130 + result.merge!(hash)
  131 + end
  132 + end
  133 + fields
  134 + end
  135 +
  136 + def remove_fields(params, form)
  137 + present_fields = params[:fields].map{|id, value| value}.collect {|field| field[:real_id]}.compact
  138 + form.fields.each {|field| field.destroy if !present_fields.include?(field.id.to_s) }
  139 + end
  140 +
  141 + def set_form_id(fields, form_id)
  142 + fields.each do |id, field|
  143 + field[:form_id] = form_id
  144 + end
  145 + fields
  146 + end
  147 +end
... ...
plugins/custom_forms/controllers/custom_forms_plugin_profile_controller.rb 0 → 100644
... ... @@ -0,0 +1,45 @@
  1 +class CustomFormsPluginProfileController < ProfileController
  2 +
  3 + before_filter :has_access, :show
  4 +
  5 + def show
  6 + @form = CustomFormsPlugin::Form.find(params[:id])
  7 + if user
  8 + @submission ||= CustomFormsPlugin::Submission.find_by_form_id_and_profile_id(@form.id,user.id)
  9 + @submission ||= CustomFormsPlugin::Submission.new(:form_id => @form.id, :profile_id => user.id)
  10 + else
  11 + @submission ||= CustomFormsPlugin::Submission.new(:form_id => @form.id)
  12 + end
  13 + if request.post?
  14 + begin
  15 + extend(CustomFormsPlugin::Helper)
  16 + answers = build_answers(params[:submission], @form)
  17 + failed_answers = answers.select {|answer| !answer.valid? }
  18 + if failed_answers.empty?
  19 + if !user
  20 + @submission.author_name = params[:author_name]
  21 + @submission.author_email = params[:author_email]
  22 + end
  23 + @submission.save!
  24 + answers.map {|answer| answer.submission = @submission; answer.save!}
  25 + else
  26 + @submission.valid?
  27 + failed_answers.each do |answer|
  28 + @submission.errors.add(answer.field.name.to_sym, answer.errors[answer.field.slug.to_sym])
  29 + end
  30 + end
  31 + session[:notice] = _('Submission saved')
  32 + redirect_to :action => 'show'
  33 + rescue
  34 + session[:notice] = _('Submission could not be saved')
  35 + end
  36 + end
  37 + end
  38 +
  39 + private
  40 +
  41 + def has_access
  42 + form = CustomFormsPlugin::Form.find(params[:id])
  43 + render_access_denied if !form.accessible_to(user)
  44 + end
  45 +end
... ...
plugins/custom_forms/db/migrate/20120727162444_create_custom_forms_plugin_forms.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +class CreateCustomFormsPluginForms < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :custom_forms_plugin_forms do |t|
  4 + t.string :name
  5 + t.string :slug
  6 + t.text :description
  7 + t.references :profile
  8 + t.datetime :begining
  9 + t.datetime :ending
  10 + t.boolean :report_submissions, :default => false
  11 + t.boolean :on_membership, :default => false
  12 + t.string :access
  13 + t.timestamps
  14 + end
  15 + end
  16 +
  17 + def self.down
  18 + drop_table :custom_forms_plugin_forms
  19 + end
  20 +end
... ...
plugins/custom_forms/db/migrate/20120727174506_create_custom_forms_plugin_fields.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +class CreateCustomFormsPluginFields < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :custom_forms_plugin_fields do |t|
  4 + t.string :name
  5 + t.string :slug
  6 + t.string :type
  7 + t.string :default_value
  8 + t.string :choices
  9 + t.float :minimum
  10 + t.float :maximum
  11 + t.references :form
  12 + t.boolean :mandatory, :default => false
  13 + t.boolean :multiple
  14 + t.boolean :list
  15 + end
  16 + end
  17 +
  18 + def self.down
  19 + drop_table :custom_forms_plugin_fields
  20 + end
  21 +end
... ...
plugins/custom_forms/db/migrate/20120727175250_create_custom_forms_plugin_answers.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class CreateCustomFormsPluginAnswers < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :custom_forms_plugin_answers do |t|
  4 + t.text :value
  5 + t.references :field
  6 + t.references :submission
  7 + end
  8 + end
  9 +
  10 + def self.down
  11 + drop_table :custom_forms_plugin_answers
  12 + end
  13 +end
... ...
plugins/custom_forms/db/migrate/20120727180512_create_custom_forms_plugin_submissions.rb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +class CreateCustomFormsPluginSubmissions < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :custom_forms_plugin_submissions do |t|
  4 + t.string :author_name
  5 + t.string :author_email
  6 + t.references :profile
  7 + t.references :form
  8 + t.timestamps
  9 + end
  10 + end
  11 +
  12 + def self.down
  13 + drop_table :custom_forms_plugin_submissions
  14 + end
  15 +end
... ...
plugins/custom_forms/lib/custom_forms_plugin.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +require 'ext/role_assignment_trigger'
  2 +
  3 +class CustomFormsPlugin < Noosfero::Plugin
  4 +
  5 + def self.plugin_name
  6 + "Custom Forms"
  7 + end
  8 +
  9 + def self.plugin_description
  10 + _("Enables the creation of forms.")
  11 + end
  12 +
  13 + def stylesheet?
  14 + true
  15 + end
  16 +
  17 + def control_panel_buttons
  18 + {:title => _('Manage Forms'), :icon => 'custom-forms', :url => {:controller => 'custom_forms_plugin_myprofile'}}
  19 + end
  20 +
  21 +end
... ...
plugins/custom_forms/lib/custom_forms_plugin/answer.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class CustomFormsPlugin::Answer < Noosfero::Plugin::ActiveRecord
  2 + belongs_to :field, :class_name => 'CustomFormsPlugin::Field'
  3 + belongs_to :submission, :class_name => 'CustomFormsPlugin::Submission'
  4 +
  5 + validates_presence_of :field
  6 + validate :value_mandatory, :if => 'field.present?'
  7 +
  8 + def value_mandatory
  9 + if field.mandatory && value.blank?
  10 + errors.add(field.slug.to_sym, _("is mandatory.").fix_i18n)
  11 + end
  12 + end
  13 +end
  14 +
... ...
plugins/custom_forms/lib/custom_forms_plugin/field.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +class CustomFormsPlugin::Field < ActiveRecord::Base
  2 + set_table_name :custom_forms_plugin_fields
  3 +
  4 + validates_presence_of :form, :name
  5 + validates_uniqueness_of :slug, :scope => :form_id
  6 +
  7 + belongs_to :form, :class_name => 'CustomFormsPlugin::Form', :dependent => :destroy
  8 + has_many :answers, :class_name => 'CustomFormsPlugin::Answer'
  9 +
  10 + serialize :choices, Hash
  11 +
  12 + before_validation do |field|
  13 + field.slug = field.name.to_slug if field.name.present?
  14 + end
  15 +end
  16 +
... ...