Commit bd4954d3143c452fee2a074571ffc66cbc2b2eda

Authored by Rodrigo Souto
2 parents 25126f2b 28a75f78

Merge branch 'master' into next-origin

Conflicts:
	app/models/category.rb
	app/models/uploaded_file.rb
	test/functional/memberships_controller_test.rb
	test/functional/profile_controller_test.rb
	vendor/plugins/action_tracker_has_comments/init.rb
Showing 46 changed files with 389 additions and 75 deletions   Show diff stats
AUTHORS
... ... @@ -111,6 +111,7 @@ Diego Martinez <diegoamc90@gmail.com>
111 111 Diego Martinez <diego@diego-K55A.(none)>
112 112 Diego + Renan <renanteruoc@gmail.com>
113 113 Fernanda Lopes <nanda.listas+psl@gmail.com>
  114 +Francisco Marcelo A. Lima Júnior <francisco.lima-junior@serpro.gov.br>
114 115 Francisco Marcelo de Araujo Lima Junior <79350259591@serpro-1457614.(none)>
115 116 Grazieno Pellegrino <grazieno@gmail.com>
116 117 Isaac Canan <isaac@intelletto.com.br>
... ... @@ -189,12 +190,14 @@ Renan Teruo + Diego Araujo &lt;renanteruoc@gmail.com&gt;
189 190 Renan Teruo + Diego Araújo <renanteruoc@gmail.com>
190 191 Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com>
191 192 Renan Teruo + Rafael Manzo <renanteruoc@gmail.com>
  193 +Rodrigo Souto <diguliu@gmail.com>
192 194 Rodrigo Souto <rodrigo@colivre.coop.br>
193 195 Ronny Kursawe <kursawe.ronny@googlemail.com>
194 196 root <root@debian.sdr.serpro>
195 197 Samuel R. C. Vale <srcvale@holoscopio.com>
196 198 Valessio Brito <valessio@gmail.com>
197 199 vfcosta <vfcosta@gmail.com>
  200 +Vinicius Cubas Brand <viniciuscb@gmail.com>
198 201 Visita <visita@debian.(none)>
199 202 Yann Lugrin <yann.lugrin@liquid-concept.ch>
200 203  
... ...
app/controllers/application_controller.rb
... ... @@ -174,8 +174,6 @@ class ApplicationController &lt; ActionController::Base
174 174 end
175 175  
176 176 def find_by_contents(asset, scope, query, paginate_options={:page => 1}, options={})
177   - scope = scope.send(options[:filter]) if options[:filter]
178   -
179 177 @plugins.dispatch_first(:find_by_contents, asset, scope, query, paginate_options, options) ||
180 178 fallback_find_by_contents(asset, scope, query, paginate_options, options)
181 179 end
... ... @@ -183,8 +181,9 @@ class ApplicationController &lt; ActionController::Base
183 181 private
184 182  
185 183 def fallback_find_by_contents(asset, scope, query, paginate_options, options)
186   - return {:results => scope.paginate(paginate_options)} if query.blank?
187   - {:results => scope.like_search(query).paginate(paginate_options)}
  184 + scope = scope.like_search(query) unless query.blank?
  185 + scope = scope.send(options[:filter]) unless options[:filter].blank?
  186 + {:results => scope.paginate(paginate_options)}
188 187 end
189 188  
190 189 end
... ...
app/controllers/public/content_viewer_controller.rb
... ... @@ -55,8 +55,9 @@ class ContentViewerController &lt; ApplicationController
55 55  
56 56 @page = FilePresenter.for @page
57 57  
58   - unless @page.mime_type == 'text/html' || params[:view]
  58 + if @page.download? params[:view]
59 59 headers['Content-Type'] = @page.mime_type
  60 + headers.merge! @page.download_headers
60 61 data = @page.data
61 62  
62 63 # TODO test the condition
... ... @@ -72,7 +73,7 @@ class ContentViewerController &lt; ApplicationController
72 73  
73 74 #FIXME see a better way to do this. It's not need to pass this variable anymore
74 75 @comment = Comment.new
75   -
  76 +
76 77 if @page.has_posts?
77 78 posts = if params[:year] and params[:month]
78 79 filter_date = DateTime.parse("#{params[:year]}-#{params[:month]}-01")
... ...
app/helpers/application_helper.rb
... ... @@ -730,8 +730,15 @@ module ApplicationHelper
730 730 end
731 731  
732 732 def rolename_for(profile, resource)
733   - role = profile.role_assignments.find_by_resource_id(resource.id).role
734   - content_tag('span', role.name, :style => "color: #{role_color(role, resource.environment.id)}")
  733 + roles = profile.role_assignments.
  734 + where(:resource_id => resource.id).
  735 + sort_by{ |role_assignment| role_assignment.role_id }.
  736 + map(&:role)
  737 + names = []
  738 + roles.each do |role|
  739 + names << content_tag('span', role.name, :style => "color: #{role_color(role, resource.environment.id)}")
  740 + end
  741 + names.join(', ')
735 742 end
736 743  
737 744 def role_color(role, env_id)
... ...
app/models/article.rb
... ... @@ -194,7 +194,7 @@ class Article &lt; ActiveRecord::Base
194 194 pending_categorizations.clear
195 195 end
196 196  
197   - acts_as_taggable
  197 + acts_as_taggable
198 198 N_('Tag list')
199 199  
200 200 acts_as_filesystem
... ... @@ -274,7 +274,7 @@ class Article &lt; ActiveRecord::Base
274 274 end
275 275  
276 276 # returns the data of the article. Must be overriden in each subclass to
277   - # provide the correct content for the article.
  277 + # provide the correct content for the article.
278 278 def data
279 279 body
280 280 end
... ... @@ -370,6 +370,16 @@ class Article &lt; ActiveRecord::Base
370 370 false
371 371 end
372 372  
  373 + def download? view = nil
  374 + (self.uploaded_file? and not self.image?) or
  375 + (self.image? and view.blank?) or
  376 + (not self.uploaded_file? and self.mime_type != 'text/html')
  377 + end
  378 +
  379 + def download_headers
  380 + {}
  381 + end
  382 +
373 383 named_scope :native_translations, :conditions => { :translation_of_id => nil }
374 384  
375 385 def translatable?
... ...
app/models/category.rb
... ... @@ -7,13 +7,13 @@ class Category &lt; ActiveRecord::Base
7 7 :slug => 1,
8 8 }
9 9  
10   - validates_exclusion_of :slug, :in => [ 'index' ], :message => N_('%{fn} cannot be like that.').fix_i18n
  10 + validates_exclusion_of :slug, :in => [ 'index' ], :message => N_('{fn} cannot be like that.').fix_i18n
11 11 validates_presence_of :name, :environment_id
12   - validates_uniqueness_of :slug,:scope => [ :environment_id, :parent_id ], :message => N_('%{fn} is already being used by another category.').fix_i18n
  12 + validates_uniqueness_of :slug,:scope => [ :environment_id, :parent_id ], :message => N_('{fn} is already being used by another category.').fix_i18n
13 13 belongs_to :environment
14 14  
15 15 validates_inclusion_of :display_color, :in => 1..15, :allow_nil => true
16   - validates_uniqueness_of :display_color, :scope => :environment_id, :if => (lambda { |cat| ! cat.display_color.nil? }), :message => N_('%{fn} was already assigned to another category.').fix_i18n
  16 + validates_uniqueness_of :display_color, :scope => :environment_id, :if => (lambda { |cat| ! cat.display_color.nil? }), :message => N_('{fn} was already assigned to another category.').fix_i18n
17 17  
18 18 # Finds all top level categories for a given environment.
19 19 named_scope :top_level_for, lambda { |environment|
... ...
app/models/comment.rb
... ... @@ -27,7 +27,7 @@ class Comment &lt; ActiveRecord::Base
27 27 validates_presence_of :author_id, :if => (lambda { |rec| rec.name.blank? && rec.email.blank? })
28 28 validates_each :name do |rec,attribute,value|
29 29 if rec.author_id && (!rec.name.blank? || !rec.email.blank?)
30   - rec.errors.add(:name, _('%{fn} can only be informed for unauthenticated authors').fix_i18n)
  30 + rec.errors.add(:name, _('{fn} can only be informed for unauthenticated authors').fix_i18n)
31 31 end
32 32 end
33 33  
... ...
app/models/create_enterprise.rb
... ... @@ -40,12 +40,12 @@ class CreateEnterprise &lt; Task
40 40  
41 41 if self.region && self.target
42 42 unless self.region.validators.include?(self.target) || self.target_type == "Environment"
43   - self.errors.add(:target, _('%{fn} is not a validator for the chosen region').fix_i18n)
  43 + self.errors.add(:target, _('{fn} is not a validator for the chosen region').fix_i18n)
44 44 end
45 45 end
46 46  
47 47 if self.status != Task::Status::CANCELLED && self.identifier && Profile.exists?(:identifier => self.identifier)
48   - self.errors.add(:identifier, _('%{fn} is already being as identifier by another enterprise, organization or person.').fix_i18n)
  48 + self.errors.add(:identifier, _('{fn} is already being as identifier by another enterprise, organization or person.').fix_i18n)
49 49 end
50 50 end
51 51  
... ...
app/models/domain.rb
... ... @@ -10,14 +10,14 @@ class Domain &lt; ActiveRecord::Base
10 10  
11 11 # <tt>name</tt> must be sequences of alphanumeric characters (a to z,
12 12 # 0 to 9), plus '_' or '-', separated by dots. Letters must be lowercase.
13   - validates_format_of :name, :with => /^([a-z0-9_-]+\.)+[a-z0-9_-]+$/, :message => N_('%{fn} must be composed of sequences of lowercase letters (a to z), numbers (0 to 9), "_" and "-", separated by dots.').fix_i18n
  13 + validates_format_of :name, :with => /^([a-z0-9_-]+\.)+[a-z0-9_-]+$/, :message => N_('{fn} must be composed of sequences of lowercase letters (a to z), numbers (0 to 9), "_" and "-", separated by dots.').fix_i18n
14 14  
15 15 # checks validations that could not be expressed using Rails' predefined
16 16 # validations. In particular:
17 17 # * <tt>name</tt> must not start with 'www.'
18 18 def validate
19 19 if self.name =~ /^www\./
20   - self.errors.add(:name, _('%{fn} must not start with www.').fix_i18n)
  20 + self.errors.add(:name, _('{fn} must not start with www.').fix_i18n)
21 21 end
22 22 end
23 23  
... ...
app/models/environment.rb
... ... @@ -170,7 +170,7 @@ class Environment &lt; ActiveRecord::Base
170 170  
171 171 # One Environment can be reached by many domains
172 172 has_many :domains, :as => :owner
173   - has_many :profiles
  173 + has_many :profiles, :dependent => :destroy
174 174  
175 175 has_many :organizations
176 176 has_many :enterprises
... ... @@ -785,13 +785,6 @@ class Environment &lt; ActiveRecord::Base
785 785 self.save!
786 786 end
787 787  
788   - after_destroy :destroy_templates
789   - def destroy_templates
790   - [enterprise_template, inactive_enterprise_template, community_template, person_template].compact.each do |template|
791   - template.destroy
792   - end
793   - end
794   -
795 788 after_create :create_default_licenses
796 789 def create_default_licenses
797 790 License.create!(:name => 'CC (by)', :url => 'http://creativecommons.org/licenses/by/3.0/legalcode', :environment => self)
... ...
app/models/event.rb
... ... @@ -25,7 +25,7 @@ class Event &lt; Article
25 25  
26 26 validates_each :start_date do |event,field,value|
27 27 if event.end_date && event.start_date && event.start_date > event.end_date
28   - event.errors.add(:start_date, _('%{fn} cannot come before end date.').fix_i18n)
  28 + event.errors.add(:start_date, _('{fn} cannot come before end date.').fix_i18n)
29 29 end
30 30 end
31 31  
... ...
app/models/image.rb
... ... @@ -17,7 +17,7 @@ class Image &lt; ActiveRecord::Base
17 17 :icon => '20x20!' },
18 18 :max_size => 5.megabytes # remember to update validate message below
19 19  
20   - validates_attachment :size => N_("%{fn} of uploaded file was larger than the maximum size of 5.0 MB").fix_i18n
  20 + validates_attachment :size => N_("{fn} of uploaded file was larger than the maximum size of 5.0 MB").fix_i18n
21 21  
22 22 delay_attachment_fu_thumbnails
23 23  
... ...
app/models/person.rb
... ... @@ -240,7 +240,7 @@ class Person &lt; Profile
240 240  
241 241 validates_each :email, :on => :update do |record,attr,value|
242 242 if User.find(:first, :conditions => ['email = ? and id != ? and environment_id = ?', value, record.user.id, record.environment.id])
243   - record.errors.add(attr, _('%{fn} is already used by other user').fix_i18n)
  243 + record.errors.add(attr, _('{fn} is already used by other user').fix_i18n)
244 244 end
245 245 end
246 246  
... ...
app/models/uploaded_file.rb
... ... @@ -72,7 +72,7 @@ class UploadedFile &lt; Article
72 72 :thumbnail_class => Thumbnail,
73 73 :max_size => self.max_size
74 74  
75   - validates_attachment :size => N_("%{fn} of uploaded file was larger than the maximum size of %{size}").sub('%{size}', self.max_size.to_humanreadable).fix_i18n
  75 + validates_attachment :size => N_("{fn} of uploaded file was larger than the maximum size of %{size}").sub('%{size}', self.max_size.to_humanreadable).fix_i18n
76 76  
77 77 delay_attachment_fu_thumbnails
78 78  
... ... @@ -112,6 +112,12 @@ class UploadedFile &lt; Article
112 112 self.name = self.filename
113 113 end
114 114  
  115 + def download_headers
  116 + {
  117 + 'Content-Disposition' => "attachment; filename=\"#{self.filename}\"",
  118 + }
  119 + end
  120 +
115 121 def data
116 122 File.read(self.full_filename)
117 123 end
... ...
app/models/user.rb
... ... @@ -114,7 +114,7 @@ class User &lt; ActiveRecord::Base
114 114 before_save :encrypt_password
115 115 validates_format_of :email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda {|user| !user.email.blank?})
116 116  
117   - validates_inclusion_of :terms_accepted, :in => [ '1' ], :if => lambda { |u| ! u.terms_of_use.blank? }, :message => N_('%{fn} must be checked in order to signup.').fix_i18n
  117 + validates_inclusion_of :terms_accepted, :in => [ '1' ], :if => lambda { |u| ! u.terms_of_use.blank? }, :message => N_('{fn} must be checked in order to signup.').fix_i18n
118 118  
119 119 # Authenticates a user by their login name or email and unencrypted password. Returns the user or nil.
120 120 def self.authenticate(login, password, environment = nil)
... ...
app/views/catalog/index.rhtml
... ... @@ -92,7 +92,7 @@
92 92 <% end %>
93 93 <% product.price_details.each do |i| %>
94 94 <div class="search-product-input-dots-to-price">
95   - <div class="search-product-input-name"><%= i.production_cost.name %></div>
  95 + <div class="search-product-input-name"><%= i.name %></div>
96 96 <%= price_span i.price, :class => 'search-product-input-price' %>
97 97 </div>
98 98 <% end %>
... ...
app/views/profile/_person_profile.rhtml
... ... @@ -4,7 +4,7 @@
4 4 </tr>
5 5 <%= display_field(_('Sex:'), profile, :sex) { |gender| { 'male' => _('Male'), 'female' => _('Female') }[gender] } %>
6 6 <%= display_field(_('Date of birth:'), profile, :birth_date) { |date| show_date(date) }%>
7   - <%= display_field(_('Location:'), profile, :location, true) %>
  7 + <%= display_field _('Location:'), profile, :location %>
8 8  
9 9 <%= display_field(_('Type:'), profile, :privacy_setting, true) %>
10 10  
... ...
app/views/shared/_list_groups.html.erb
... ... @@ -12,7 +12,9 @@
12 12 <%= _('Members: %s') % group.members_count.to_s %> <br/>
13 13 <%= _('Created at: %s') % show_date(group.created_at) unless group.enterprise? %> <br/>
14 14 <% button_bar do %>
15   - <%= button 'menu-ctrl-panel', _('Control panel of this group'), group.admin_url %>
  15 + <% if user.has_permission?(:edit_profile, group) %>
  16 + <%= button 'menu-ctrl-panel', _('Control panel of this group'), group.admin_url %>
  17 + <% end %>
16 18 <%= button 'menu-logout', _('Leave community'), group.leave_url(true), :class => 'leave-community' %>
17 19 <% if (group.community? && user.has_permission?(:destroy_profile, group)) %>
18 20 <%= button 'delete', _('Remove'), { :controller => 'profile_editor', :action => 'destroy_profile', :profile => group.identifier } %>
... ...
db/migrate/20131121162641_remove_orphan_profiles.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +class RemoveOrphanProfiles < ActiveRecord::Migration
  2 + def self.up
  3 + profiles = Profile.joins('LEFT JOIN environments ON profiles.environment_id=environments.id').where('environments.id IS NULL')
  4 + profiles.map(&:destroy)
  5 + end
  6 +
  7 + def self.down
  8 + say 'This migration can not be reverted.'
  9 + end
  10 +end
... ...
debian/changelog
  1 +noosfero (0.45.0~rc20131202153818) squeeze-test; urgency=low
  2 +
  3 + * 0.45.0 RC2
  4 +
  5 + -- Rodrigo Souto <rodrigo@colivre.coop.br> Mon, 02 Dec 2013 15:38:28 -0300
  6 +
  7 +noosfero (0.44.6) unstable; urgency=low
  8 +
  9 + * 0.44.5 Addendum
  10 +
  11 + -- Rodrigo Souto <rodrigo@colivre.coop.br> Wed, 27 Nov 2013 16:45:02 -0300
  12 +
  13 +noosfero (0.44.5) unstable; urgency=low
  14 +
  15 + * Bugfix release
  16 +
  17 + -- Rodrigo Souto <rodrigo@colivre.coop.br> Tue, 26 Nov 2013 17:39:39 -0300
  18 +
1 19 noosfero (0.44.4) unstable; urgency=low
2 20  
3 21 * Bugfix release
... ...
features/manage_products.feature
... ... @@ -9,7 +9,7 @@ Feature: manage products
9 9 And the following enterprises
10 10 | identifier | owner | name | enabled |
11 11 | redemoinho | joaosilva | Rede Moinho | true |
12   - And feature "products_for_enterprises" is enabled on environment
  12 + And feature "products_for_enterprises" is enabled on environment
13 13  
14 14 Scenario: display "create new product" button
15 15 Given I am logged in as "joaosilva"
... ...
gitignore.example
... ... @@ -39,3 +39,6 @@ debian/*.debhelper
39 39 debian/files
40 40 debian/noosfero/
41 41 debian/noosfero-apache/
  42 +features/plugins/*
  43 +plugins/solr/config/solr.yml
  44 +/solr
... ...
lib/needs_profile.rb
... ... @@ -24,7 +24,7 @@ module NeedsProfile
24 24 @profile ||= environment.profiles.find_by_identifier(params[:profile])
25 25 if @profile
26 26 profile_hostname = @profile.hostname
27   - if profile_hostname && request.host == @environment.default_hostname
  27 + if profile_hostname && profile_hostname != request.host
28 28 params.delete(:profile)
29 29 redirect_to(Noosfero.url_options.merge(params).merge(:host => profile_hostname))
30 30 end
... ...
lib/noosfero.rb
... ... @@ -3,7 +3,7 @@ require &#39;fast_gettext&#39;
3 3  
4 4 module Noosfero
5 5 PROJECT = 'noosfero'
6   - VERSION = '0.44.4'
  6 + VERSION = '0.45.0~rc20131202153818'
7 7  
8 8 def self.pattern_for_controllers_in_directory(dir)
9 9 disjunction = controllers_in_directory(dir).join('|')
... ...
plugins/pg_search/lib/ext/active_record.rb
... ... @@ -2,8 +2,11 @@ require_dependency &#39;active_record&#39;
2 2  
3 3 class ActiveRecord::Base
4 4 def self.pg_search_plugin_search(query)
  5 + query.gsub!(/\|/,' ')
  6 + formatted_query = query.split.map{|w| w += ":*"}.join('|')
  7 +
5 8 if defined?(self::SEARCHABLE_FIELDS)
6   - where("to_tsvector('simple', #{pg_search_plugin_fields}) @@ to_tsquery('#{query}:*')")
  9 + where("to_tsvector('simple', #{pg_search_plugin_fields}) @@ to_tsquery('#{formatted_query}')")
7 10 else
8 11 raise "No searchable fields defined for #{self.name}"
9 12 end
... ...
plugins/pg_search/lib/pg_search_plugin.rb
... ... @@ -9,8 +9,9 @@ class PgSearchPlugin &lt; Noosfero::Plugin
9 9 end
10 10  
11 11 def find_by_contents(asset, scope, query, paginate_options={}, options={})
12   - return if query.blank?
13   - {:results => scope.pg_search_plugin_search(query).paginate(paginate_options)}
  12 + scope = scope.pg_search_plugin_search(query) unless query.blank?
  13 + scope = scope.send(options[:filter]) unless options[:filter]
  14 + {:results => scope.paginate(paginate_options)}
14 15 end
15 16  
16 17 end
... ...
plugins/pg_search/test/unit/pg_search_plugin_test.rb
... ... @@ -14,6 +14,13 @@ class PgSearchPluginTest &lt; ActiveSupport::TestCase
14 14 assert_includes search(Profile, 'water'), profile
15 15 end
16 16  
  17 + should 'locate one or more profiles' do
  18 + profile1 = fast_create(Profile, :identifier => 'administrator')
  19 + profile2 = fast_create(Profile, :identifier => 'debugger')
  20 + assert_includes search(Profile, 'admin deb'), profile1
  21 + assert_includes search(Profile, 'admin deb'), profile2
  22 + end
  23 +
17 24 # TODO This feature is available only on Postgresql 9.0
18 25 # http://www.postgresql.org/docs/9.0/static/unaccent.html
19 26 # should 'ignore accents' do
... ...
plugins/shopping_cart/controllers/shopping_cart_plugin_controller.rb
... ... @@ -94,10 +94,12 @@ class ShoppingCartPluginController &lt; PublicController
94 94 end
95 95  
96 96 def buy
97   - @cart = cart
98   - @enterprise = environment.enterprises.find(cart[:enterprise_id])
99   - @settings = Noosfero::Plugin::Settings.new(@enterprise, ShoppingCartPlugin)
100   - render :layout => false
  97 + if validate_cart_presence
  98 + @cart = cart
  99 + @enterprise = environment.enterprises.find(cart[:enterprise_id])
  100 + @settings = Noosfero::Plugin::Settings.new(@enterprise, ShoppingCartPlugin)
  101 + render :layout => false
  102 + end
101 103 end
102 104  
103 105 def send_request
... ...
plugins/shopping_cart/controllers/shopping_cart_plugin_myprofile_controller.rb
... ... @@ -63,7 +63,7 @@ class ShoppingCartPluginMyprofileController &lt; MyProfileController
63 63  
64 64 def treat_delivery_options(params)
65 65 result = {}
66   - return result if params.nil? || params[:delivery_options].nil?
  66 + return result if params.nil? || params[:options].nil?
67 67 params[:options].size.times do |counter|
68 68 if params[:options][counter].present? && params[:prices][counter].present?
69 69 result[params[:options][counter]] = params[:prices][counter]
... ...
plugins/shopping_cart/test/functional/shopping_cart_plugin_controller_test.rb
... ... @@ -187,6 +187,12 @@ class ShoppingCartPluginControllerTest &lt; ActionController::TestCase
187 187 assert !cart?, "cart expected to be empty!"
188 188 end
189 189  
  190 + should 'not allow buy without any cart' do
  191 + get :buy
  192 + assert !json_response[:ok]
  193 + assert_equal 2, json_response['error']['code']
  194 + end
  195 +
190 196 private
191 197  
192 198 def json_response
... ...
plugins/shopping_cart/test/functional/shopping_cart_plugin_myprofile_controller_test.rb
... ... @@ -51,6 +51,14 @@ class ShoppingCartPluginMyprofileControllerTest &lt; ActionController::TestCase
51 51 assert settings.delivery_price == price
52 52 end
53 53  
  54 + should 'be able to choose delivery_options' do
  55 + delivery_options = {:options => ['car', 'bike'], :prices => ['20', '5']}
  56 + post :edit, :profile => enterprise.identifier, :settings => {:delivery_options => delivery_options}
  57 +
  58 + assert_equal '20', settings.delivery_options['car']
  59 + assert_equal '5', settings.delivery_options['bike']
  60 + end
  61 +
54 62 should 'filter the reports correctly' do
55 63 another_enterprise = fast_create(Enterprise)
56 64 po1 = ShoppingCartPlugin::PurchaseOrder.create!(:seller => enterprise, :status => ShoppingCartPlugin::PurchaseOrder::Status::OPENED)
... ...
plugins/solr/lib/ext/article.rb
... ... @@ -24,7 +24,8 @@ class Article
24 24 {:slug => :text}, {:body => :text},
25 25 {:abstract => :text}, {:filename => :text},
26 26 # filtered fields
27   - {:solr_plugin_public => :boolean}, {:environment_id => :integer},
  27 + {:solr_plugin_public => :boolean}, {:published => :boolean},
  28 + {:environment_id => :integer},
28 29 {:profile_id => :integer}, :language,
29 30 {:solr_plugin_category_filter => :integer},
30 31 # ordered/query-boosted fields
... ...
plugins/solr/lib/ext/profile.rb
... ... @@ -28,7 +28,7 @@ class Profile
28 28 {:solr_plugin_category_filter => :integer},
29 29 # ordered/query-boosted fields
30 30 {:solr_plugin_name_sortable => :string}, {:user_id => :integer},
31   - :enabled, :active, :validated, :public_profile,
  31 + :enabled, :active, :validated, :public_profile, :visible,
32 32 {:lat => :float}, {:lng => :float},
33 33 :updated_at, :created_at,
34 34 ],
... ...
plugins/solr/lib/solr_plugin.rb
... ... @@ -18,16 +18,71 @@ class SolrPlugin &lt; Noosfero::Plugin
18 18 true
19 19 end
20 20  
  21 + def solr_search? empty_query, klass
  22 + not empty_query or klass == Product
  23 + end
  24 +
21 25 def find_by_contents(asset, scope, query, paginate_options={}, options={})
22 26 klass = asset_class(asset)
23   - category = options.delete(:category)
24   - filter = options.delete(:filter)
  27 + category = options[:category]
  28 + empty_query = empty_query? query, category
25 29  
26   - return if empty_query?(query, category) && klass != Product
  30 + unless solr_search? empty_query, klass
  31 + return options[:filter] ? {:results => scope.send(options[:filter]).paginate(paginate_options)} : nil
  32 + end
27 33  
28 34 solr_options = solr_options(class_asset(klass), category)
29   - solr_options.merge!(products_options(user)) if klass == Product && empty_query?(query, category)
30   - klass.find_by_contents(query, paginate_options, solr_options.merge(options))
  35 + solr_options[:filter_queries] ||= []
  36 + solr_options[:filter_queries] += scopes_to_solr_filters scope, klass, options
  37 + solr_options.merge! products_options(user) if klass == Product and empty_query
  38 + solr_options.merge! options.except(:category, :filter)
  39 +
  40 + scope.find_by_contents query, paginate_options, solr_options
  41 + end
  42 +
  43 + protected
  44 +
  45 + def scopes_to_solr_filters scope, klass = nil, options = {}
  46 + filter_queries = []
  47 + klass ||= scope.base_class
  48 + solr_fields = klass.configuration[:solr_fields].keys
  49 + scopes_applied = scope.scopes_applied.dup rescue [] #rescue association and class direct filtering
  50 +
  51 + scope.current_scoped_methods[:create].each do |attr, value|
  52 + next unless solr_fields.include? attr.to_sym
  53 +
  54 + # if the filter is present here, then prefer it
  55 + scopes_applied.reject!{ |name| name == attr.to_sym }
  56 +
  57 + filter_queries << "#{attr}:#{value}"
  58 + end
  59 +
  60 + scopes_applied.each do |name|
  61 + next if name.to_s == options[:filter].to_s
  62 +
  63 + has_value = name === Hash
  64 + if has_value
  65 + name, args = name.keys.first, name.values.first
  66 + value = args.first
  67 + end
  68 +
  69 + related_field = nil
  70 + related_field = name if solr_fields.include? name
  71 + related_field = "solr_plugin_#{name}" if solr_fields.include? :"solr_plugin_#{name}"
  72 +
  73 + if has_value
  74 + if related_field
  75 + filter_queries << "#{related_field}:#{value}"
  76 + else
  77 + filter_queries << klass.send("solr_filter_#{name}", *args)
  78 + end
  79 + else
  80 + raise "Undeclared solr field for scope #{name}" if related_field.nil?
  81 + filter_queries << "#{related_field}:true"
  82 + end
  83 + end
  84 +
  85 + filter_queries
31 86 end
32 87  
33 88 def method_missing method, *args, &block
... ...
plugins/solr/test/unit/solr_plugin/plugin_test.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +require "#{File.dirname(__FILE__)}/../../test_helper"
  2 +
  3 +class SolrPlugin::PluginTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @plugin = SolrPlugin.new
  7 + end
  8 + attr_reader :plugin
  9 +
  10 + should 'convert scopes to solr filters' do
  11 + person = create_user('test').person
  12 + result = plugin.send :scopes_to_solr_filters, person.files.public.published
  13 + assert_equal result, ["profile_id:#{person.id}", "published:'true'", "solr_plugin_public:true"]
  14 + end
  15 +
  16 +end
... ...
plugins/solr/vendor/plugins/named_scope_with_applied_names/init.rb 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +require_dependency 'active_record/named_scope'
  2 +
  3 +if Rails::VERSION::STRING < "2.3.99"
  4 +
  5 + module ::ActiveRecord
  6 +
  7 + module NamedScope
  8 +
  9 + module ClassMethods
  10 +
  11 + def named_scope_with_applied_names name, options = {}, &block
  12 + named_scope_without_applied_names name, options, &block
  13 +
  14 + name = name.to_sym
  15 + scopes[name] = lambda do |parent_scope, *args|
  16 + scope = Scope.new(parent_scope, case options
  17 + when Hash
  18 + options
  19 + when Proc
  20 + if self.model_name != parent_scope.model_name
  21 + options.bind(parent_scope).call(*args)
  22 + else
  23 + options.call(*args)
  24 + end
  25 + end, &block)
  26 + scope.scope_name = name
  27 + scope
  28 + end
  29 + end
  30 + alias_method_chain :named_scope, :applied_names
  31 + end
  32 +
  33 + class Scope
  34 + attr_accessor :scope_name, :scopes_applied
  35 +
  36 + def initialize_with_applied_names proxy_scope, options, &block
  37 + initialize_without_applied_names proxy_scope, options, &block
  38 + self.scopes_applied ||= []
  39 + self.scopes_applied += proxy_scope.send :scopes_applied if Scope === proxy_scope
  40 +
  41 + # unrelated bugfix: use if instead of unless
  42 + if (Scope === proxy_scope || ActiveRecord::Associations::AssociationCollection === proxy_scope)
  43 + @current_scoped_methods_when_defined = proxy_scope.send(:current_scoped_methods)
  44 + end
  45 + end
  46 + alias_method_chain :initialize, :applied_names
  47 +
  48 + def scope_name= name
  49 + @scope_name = name
  50 + self.scopes_applied << @scope_name
  51 + end
  52 +
  53 + end
  54 +
  55 + end
  56 + end
  57 +end
... ...
test/fixtures/files/500.html 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../public/500.html
0 2 \ No newline at end of file
... ...
test/functional/catalog_controller_test.rb
... ... @@ -235,4 +235,18 @@ class CatalogControllerTest &lt; ActionController::TestCase
235 235 assert_equal [pc2, pc1, pc4, pc3], assigns(:categories)
236 236 end
237 237  
  238 + should 'use price_detail name instead of production_cost name straight' do
  239 + p1 = fast_create(Product, :product_category_id => @product_category.id, :enterprise_id => @enterprise.id)
  240 + p2 = fast_create(Product, :product_category_id => @product_category.id, :enterprise_id => @enterprise.id)
  241 + Product.any_instance.stubs(:price_described?).returns(true)
  242 + production_cost = fast_create(ProductionCost)
  243 + pd1 = PriceDetail.create!(:product => p1, :production_cost => production_cost)
  244 + pd2 = PriceDetail.create!(:product => p2)
  245 +
  246 + get :index, :profile => @enterprise.identifier
  247 +
  248 + assert_tag :tag => 'div', :attributes => {:class => 'search-product-input-name'}, :content => production_cost.name
  249 + assert_tag :tag => 'div', :attributes => {:class => 'search-product-input-name'}, :content => 'Other costs'
  250 + end
  251 +
238 252 end
... ...
test/functional/content_viewer_controller_test.rb
... ... @@ -64,7 +64,20 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
64 64 assert_response :missing
65 65 end
66 66  
67   - should 'produce a download-like when article is not text/html' do
  67 + should 'produce a download-link when article is a uploaded file' do
  68 + profile = create_user('someone').person
  69 + html = UploadedFile.create! :uploaded_data => fixture_file_upload('/files/500.html', 'text/html'), :profile => profile
  70 + html.save!
  71 +
  72 + get :view_page, :profile => 'someone', :page => [ '500.html' ]
  73 +
  74 + assert_response :success
  75 + assert_match /^text\/html/, @response.headers['Content-Type']
  76 + assert @response.headers['Content-Disposition'].present?
  77 + assert_match /attachment/, @response.headers['Content-Disposition']
  78 + end
  79 +
  80 + should 'produce a download-link when article is not text/html' do
68 81  
69 82 # for example, RSS feeds
70 83 profile = create_user('someone').person
... ... @@ -1254,7 +1267,7 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
1254 1267  
1255 1268 get 'view_page', :profile => profile.identifier, :page => article.path.split('/')
1256 1269 assert_tag :tag => 'a', :attributes => { :href => "/#{profile.identifier}/#{article.path}?comment_page=2", :rel => 'next' }
1257   - end
  1270 + end
1258 1271  
1259 1272 should 'not escape acceptable HTML in list of blog posts' do
1260 1273 login_as('testinguser')
... ... @@ -1274,9 +1287,7 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
1274 1287 should 'display link to download of non-recognized file types on its page' do
1275 1288 file = UploadedFile.create!(:uploaded_data => fixture_file_upload('/files/test.txt', 'bin/unknown'), :profile => profile)
1276 1289 get :view_page, file.url.merge(:view=>:true)
1277   - assert_tag :tag => 'a',
1278   - :content => file.filename,
1279   - :attributes => { :href => file.public_filename }
  1290 + assert_match /this is a sample text file/, @response.body
1280 1291 end
1281 1292  
1282 1293 end
... ...
test/functional/memberships_controller_test.rb
... ... @@ -6,7 +6,7 @@ require &#39;memberships_controller&#39;
6 6 class MembershipsController; def rescue_action(e) raise e end; end
7 7  
8 8 class MembershipsControllerTest < ActionController::TestCase
9   -
  9 +
10 10 include ApplicationHelper
11 11  
12 12 def setup
... ... @@ -22,7 +22,7 @@ class MembershipsControllerTest &lt; ActionController::TestCase
22 22 def test_local_files_reference
23 23 assert_local_files_reference :get, :index, :profile => profile.identifier
24 24 end
25   -
  25 +
26 26 def test_valid_xhtml
27 27 assert_valid_xhtml
28 28 end
... ... @@ -258,4 +258,23 @@ class MembershipsControllerTest &lt; ActionController::TestCase
258 258 assert_tag :tag => 'a', :attributes => { :class => 'button icon-cancel with-text', :href => back_to }
259 259 end
260 260  
  261 + should 'only display control panel link to members with permission' do
  262 + c1 = fast_create(Community, :name => 'My own community')
  263 + c2 = fast_create(Community, :name => 'Not my community')
  264 +
  265 + owner = fast_create(Person)
  266 + c2.add_admin(owner)
  267 +
  268 + person = Person['testuser']
  269 + c1.add_admin(person)
  270 + c2.add_member(person)
  271 +
  272 + login_as('testuser')
  273 + get :index, :profile => 'testuser'
  274 +
  275 + assert_template 'index'
  276 + assert_no_tag :tag => 'a', :attributes => { :href => "/myprofile/#{c2.identifier}" }
  277 + assert_tag :tag => 'a', :attributes => { :href => "/myprofile/#{c1.identifier}" }
  278 + end
  279 +
261 280 end
... ...
test/functional/profile_controller_test.rb
... ... @@ -1383,6 +1383,41 @@ class ProfileControllerTest &lt; ActionController::TestCase
1383 1383 assert_equal "Comment successfully added.", assigns(:message)
1384 1384 end
1385 1385  
  1386 + should 'display comment in wall if user was removed' do
  1387 + UserStampSweeper.any_instance.stubs(:current_user).returns(profile)
  1388 + article = TinyMceArticle.create!(:profile => profile, :name => 'An article about free software')
  1389 + to_be_removed = create_user('removed_user').person
  1390 + comment = Comment.create!(:author => to_be_removed, :title => 'Test Comment', :body => 'My author does not exist =(', :source_id => article.id, :source_type => 'Article')
  1391 + to_be_removed.destroy
  1392 +
  1393 + login_as(profile.identifier)
  1394 + get :index, :profile => profile.identifier
  1395 +
  1396 + assert_tag :tag => 'span', :content => '(removed user)', :attributes => {:class => 'comment-user-status comment-user-status-wall icon-user-removed'}
  1397 + end
  1398 +
  1399 + should 'not display spam comments in wall' do
  1400 + UserStampSweeper.any_instance.stubs(:current_user).returns(profile)
  1401 + article = TinyMceArticle.create!(:profile => profile, :name => 'An article about spam\'s nutritional attributes')
  1402 + comment = Comment.create!(:author => profile, :title => 'Test Comment', :body => 'This article makes me hungry', :source_id => article.id, :source_type => 'Article')
  1403 + comment.spam!
  1404 + login_as(profile.identifier)
  1405 + get :index, :profile => profile.identifier
  1406 +
  1407 + assert !/This article makes me hungry/.match(@response.body), 'Spam comment was shown!'
  1408 + end
  1409 +
  1410 + should 'display comment in wall from non logged users' do
  1411 + UserStampSweeper.any_instance.stubs(:current_user).returns(profile)
  1412 + article = TinyMceArticle.create!(:profile => profile, :name => 'An article about free software')
  1413 + comment = Comment.create!(:name => 'outside user', :email => 'outside@localhost.localdomain', :title => 'Test Comment', :body => 'My author does not exist =(', :source_id => article.id, :source_type => 'Article')
  1414 +
  1415 + login_as(profile.identifier)
  1416 + get :index, :profile => profile.identifier
  1417 +
  1418 + assert_tag :tag => 'span', :content => '(unauthenticated user)', :attributes => {:class => 'comment-user-status comment-user-status-wall icon-user-unknown'}
  1419 + end
  1420 +
1386 1421 should 'add locale on mailing' do
1387 1422 community = fast_create(Community)
1388 1423 create_user_with_permission('profile_moderator_user', 'send_mail_to_members', community)
... ...
test/functional/profile_editor_controller_test.rb
... ... @@ -624,6 +624,7 @@ class ProfileEditorControllerTest &lt; ActionController::TestCase
624 624  
625 625 profile.domains << Domain.new(:name => 'myowndomain.net')
626 626 profile.environment.domains << Domain.new(:name => 'myenv.net')
  627 + ActionController::TestRequest.any_instance.stubs(:host).returns(profile.hostname)
627 628  
628 629 get :edit, :profile => profile.identifier
629 630 assert_tag :tag => 'select', :attributes => { :name => 'profile_data[preferred_domain_id]' }, :descendant => { :tag => "option", :content => 'myowndomain.net' }
... ... @@ -639,6 +640,7 @@ class ProfileEditorControllerTest &lt; ActionController::TestCase
639 640  
640 641 profile.domains << Domain.new(:name => 'myowndomain.net')
641 642 profile.environment.domains << Domain.new(:name => 'myenv.net')
  643 + ActionController::TestRequest.any_instance.stubs(:host).returns(profile.hostname)
642 644  
643 645 get :edit, :profile => profile.identifier
644 646 assert_tag :tag => "select", :attributes => { :name => 'profile_data[preferred_domain_id]'}, :descendant => { :tag => 'option', :content => '&lt;Select domain&gt;', :attributes => { :value => '' } }
... ... @@ -978,4 +980,13 @@ class ProfileEditorControllerTest &lt; ActionController::TestCase
978 980 assert_equal({}, person.reload.fields_privacy)
979 981 end
980 982  
  983 + should 'not redirect if the profile_hostname is the same as environment hostname' do
  984 + Person.any_instance.stubs(:hostname).returns('hostname.org')
  985 + Environment.any_instance.stubs(:default_hostname).returns('hostname.org')
  986 + ActionController::TestRequest.any_instance.stubs(:host).returns('hostname.org')
  987 + get :index, :profile => profile.identifier
  988 + assert_response :success
  989 + end
  990 +
  991 +
981 992 end
... ...
test/unit/application_helper_test.rb
... ... @@ -143,6 +143,18 @@ class ApplicationHelperTest &lt; ActiveSupport::TestCase
143 143 assert_tag_in_string rolename_for(member2, community), :tag => 'span', :content => 'Profile Member'
144 144 end
145 145  
  146 + should 'rolenames for a member admin' do
  147 + member1 = create_user('usertest1').person
  148 + member2 = create_user('usertest2').person
  149 + community = fast_create(Community, :name => 'new community', :identifier => 'new-community', :environment_id => Environment.default.id)
  150 + community.add_member(member1)
  151 + # member2 is both a admin and a member
  152 + community.add_member(member2)
  153 + community.add_admin(member2)
  154 + assert_tag_in_string rolename_for(member2, community), :tag => 'span', :content => 'Profile Member'
  155 + assert_tag_in_string rolename_for(member2, community), :tag => 'span', :content => 'Profile Administrator'
  156 + end
  157 +
146 158 should 'get theme from environment by default' do
147 159 @environment = mock
148 160 @environment.stubs(:theme).returns('my-environment-theme')
... ...
test/unit/environment_test.rb
... ... @@ -293,21 +293,6 @@ class EnvironmentTest &lt; ActiveSupport::TestCase
293 293 assert_equal blocks - env_blocks, Block.count
294 294 end
295 295  
296   - should 'destroy templates' do
297   - env = fast_create(Environment)
298   - templates = [mock, mock, mock, mock]
299   - templates.each do |item|
300   - item.expects(:destroy)
301   - end
302   -
303   - env.stubs(:person_template).returns(templates[0])
304   - env.stubs(:community_template).returns(templates[1])
305   - env.stubs(:enterprise_template).returns(templates[2])
306   - env.stubs(:inactive_enterprise_template).returns(templates[3])
307   -
308   - env.destroy
309   - end
310   -
311 296 should 'have boxes and blocks upon creation' do
312 297 Environment.any_instance.stubs(:create_templates) # avoid creating templates, it's expensive
313 298 environment = Environment.create!(:name => 'a test environment')
... ...
test/unit/profile_test.rb
... ... @@ -1901,4 +1901,11 @@ class ProfileTest &lt; ActiveSupport::TestCase
1901 1901 assert !profile.may_display_location_to?(user)
1902 1902 end
1903 1903  
  1904 + should 'destroy profile if its environment is destroyed' do
  1905 + environment = fast_create(Environment)
  1906 + profile = fast_create(Profile, :environment_id => environment.id)
  1907 +
  1908 + environment.destroy
  1909 + assert_raise(ActiveRecord::RecordNotFound) {profile.reload}
  1910 + end
1904 1911 end
... ...
vendor/plugins/action_tracker_has_comments/init.rb
... ... @@ -9,7 +9,18 @@ Rails.configuration.to_prepare do
9 9  
10 10 def conditions_for_comments
11 11 type, id = (self.target_type == 'Article' ? ['Article', self.target_id] : [self.class.to_s, self.id])
12   - "source_type = '#{type}' AND source_id = '#{id}' AND reply_of_id IS NULL"
  12 + "source_type = '#{type}' AND source_id = '#{id}' AND spam IS NOT TRUE AND reply_of_id IS NULL"
  13 + end
  14 +
  15 + def comments_as_thread
  16 + result = {}
  17 + root = []
  18 + self.comments.each do |c|
  19 + c.replies = []
  20 + result[c.id] ||= c
  21 + c.reply_of_id.nil? ? root << c : result[c.reply_of_id].replies << c
  22 + end
  23 + root
13 24 end
14 25  
15 26 end
... ...