Commit 32ea7f36badb289bbbb367a91ee537fa6fb4d25b

Authored by Rodrigo Souto
1 parent 06c13aae

Adding new hotspots

  1. organization_members: allows plugins to extend the list of members of
  an organization.

  2. has_permission?: allows plugins to add permission to some user to
  perform some action over a profile or environment. This hotspot only
  allows plugins to expand access, not to revoke access.

  3. new_community_hidden_fields and
  enterprise_registration_hidden_fields: allows plugins to include new
  hidden fields in the following forms.

  * Also including a monkey patch to include some features from the gem
    fake_arel that requires activesupport-2.3.14 and
    activerecord-2.3.14. This them allows OR with scopes without loading
    them.
app/models/person.rb
... ... @@ -3,9 +3,19 @@ class Person < Profile
3 3  
4 4 acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)}
5 5 acts_as_accessor
  6 + acts_as_having_hotspots
6 7  
7 8 named_scope :members_of, lambda { |resource| { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.resource_type = ? AND role_assignments.resource_id = ?', resource.class.base_class.name, resource.id ] } }
8 9  
  10 + def has_permission_with_plugins?(permission, profile)
  11 + permissions = [has_permission_without_plugins?(permission, profile)]
  12 + permissions += enabled_plugins.map do |plugin|
  13 + plugin.has_permission?(self, permission, profile)
  14 + end
  15 + permissions.include?(true)
  16 + end
  17 + alias_method_chain :has_permission?, :plugins
  18 +
9 19 def memberships
10 20 Profile.memberships_of(self)
11 21 end
... ...
app/models/profile.rb
... ... @@ -54,6 +54,7 @@ class Profile < ActiveRecord::Base
54 54 }
55 55  
56 56 acts_as_accessible
  57 + acts_as_having_hotspots
57 58  
58 59 named_scope :memberships_of, lambda { |person| { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.accessor_type = ? AND role_assignments.accessor_id = ?', person.class.base_class.name, person.id ] } }
59 60 #FIXME: these will work only if the subclass is already loaded
... ... @@ -61,13 +62,24 @@ class Profile < ActiveRecord::Base
61 62 named_scope :communities, lambda { {:conditions => (Community.send(:subclasses).map(&:name) << 'Community').map { |klass| "profiles.type = '#{klass}'"}.join(" OR ")} }
62 63  
63 64 def members
64   - Person.members_of(self)
  65 + scopes = dispatch_scopes(:organization_members, self)
  66 + scopes << Person.members_of(self)
  67 + scopes.size == 1 ? scopes.first : Person.or_scope(scopes)
65 68 end
66 69  
67 70 def members_count
68   - members.count('DISTINCT(profiles.id)')
  71 + members.count
69 72 end
70 73  
  74 + class << self
  75 + def count_with_distinct(*args)
  76 + options = args.last || {}
  77 + count_without_distinct(:id, {:distinct => true}.merge(options))
  78 + end
  79 + alias_method_chain :count, :distinct
  80 + end
  81 +
  82 +
71 83 def members_by_role(role)
72 84 Person.members_of(self).all(:conditions => ['role_assignments.role_id = ?', role.id])
73 85 end
... ...
app/models/profile_list_block.rb
... ... @@ -25,7 +25,7 @@ class ProfileListBlock &lt; Block
25 25 end
26 26  
27 27 def profile_count
28   - profiles.visible.count('DISTINCT(profiles.id)')
  28 + profiles.visible.count
29 29 end
30 30  
31 31 # the title of the block. Probably will be overriden in subclasses.
... ...
app/views/enterprise_registration/basic_information.rhtml
... ... @@ -28,6 +28,12 @@
28 28 <%= hidden_field_tag 'create_enterprise[target_id]', environment.id %>
29 29 <% end %>
30 30  
  31 + <% @plugins.dispatch(:enterprise_registration_hidden_fields).each do |field| %>
  32 + <% field.each do |key, value| %>
  33 + <%= f.hidden_field(key, :value => value) %>
  34 + <% end %>
  35 + <% end %>
  36 +
31 37 <% button_bar do %>
32 38 <%= submit_button('next', _('Next'), :cancel => {:profile => current_user.person.identifier, :action=>"enterprises", :controller=>"profile"}) %>
33 39 <% end %>
... ...
app/views/memberships/new_community.rhtml
... ... @@ -16,6 +16,12 @@
16 16  
17 17 <%= required f.text_field(:name) %>
18 18  
  19 + <% @plugins.dispatch(:new_community_hidden_fields).each do |field| %>
  20 + <% field.each do |key, value| %>
  21 + <%= f.hidden_field(key, :value => value) %>
  22 + <% end %>
  23 + <% end %>
  24 +
19 25 <%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => 'community', :profile => @community } %>
20 26  
21 27 <% f.fields_for :image_builder, @community.image do |i| %>
... ...
lib/noosfero/plugin.rb
... ... @@ -238,4 +238,29 @@ class Noosfero::Plugin
238 238 def comment_saved(comment)
239 239 end
240 240  
  241 + # -> Extends organization list of members
  242 + # returns = An instance of ActiveRecord::NamedScope::Scope retrieved through
  243 + # Person.members_of method.
  244 + def organization_members(organization)
  245 + nil
  246 + end
  247 +
  248 + # -> Extends person permission access
  249 + # returns = boolean
  250 + def has_permission?(person, permission, target)
  251 + nil
  252 + end
  253 +
  254 + # -> Adds hidden_fields to the new community view
  255 + # returns = {key => value}
  256 + def new_community_hidden_fields
  257 + nil
  258 + end
  259 +
  260 + # -> Adds hidden_fields to the enterprise registration view
  261 + # returns = {key => value}
  262 + def enterprise_registration_hidden_fields
  263 + nil
  264 + end
  265 +
241 266 end
... ...
test/functional/enterprise_registration_controller_test.rb
... ... @@ -178,4 +178,27 @@ all_fixtures
178 178 get :index
179 179 assert_equal assigns(:create_enterprise).target, environment
180 180 end
  181 +
  182 + should 'include hidden fields supplied by plugins on enterprise registration' do
  183 + class Plugin1 < Noosfero::Plugin
  184 + def enterprise_registration_hidden_fields
  185 + {'plugin1' => 'Plugin 1'}
  186 + end
  187 + end
  188 +
  189 + class Plugin2 < Noosfero::Plugin
  190 + def enterprise_registration_hidden_fields
  191 + {'plugin2' => 'Plugin 2'}
  192 + end
  193 + end
  194 +
  195 + environment = Environment.default
  196 + environment.enable_plugin(Plugin1.name)
  197 + environment.enable_plugin(Plugin2.name)
  198 +
  199 + get :index
  200 +
  201 + assert_tag :tag => 'input', :attributes => {:id => 'create_enterprise_plugin1', :type => 'hidden', :value => 'Plugin 1'}
  202 + assert_tag :tag => 'input', :attributes => {:id => 'create_enterprise_plugin2', :type => 'hidden', :value => 'Plugin 2'}
  203 + end
181 204 end
... ...
test/functional/memberships_controller_test.rb
... ... @@ -213,4 +213,27 @@ class MembershipsControllerTest &lt; ActionController::TestCase
213 213 assert_no_tag :tag => 'textarea', :attributes => {:name => 'community[description]'}
214 214 end
215 215  
  216 + should 'include hidden fields supplied by plugins on new community' do
  217 + class Plugin1 < Noosfero::Plugin
  218 + def new_community_hidden_fields
  219 + {'plugin1' => 'Plugin 1'}
  220 + end
  221 + end
  222 +
  223 + class Plugin2 < Noosfero::Plugin
  224 + def new_community_hidden_fields
  225 + {'plugin2' => 'Plugin 2'}
  226 + end
  227 + end
  228 +
  229 + environment = Environment.default
  230 + environment.enable_plugin(Plugin1.name)
  231 + environment.enable_plugin(Plugin2.name)
  232 +
  233 + get :new_community, :profile => profile.identifier
  234 +
  235 + assert_tag :tag => 'input', :attributes => {:id => 'community_plugin1', :type => 'hidden', :value => 'Plugin 1'}
  236 + assert_tag :tag => 'input', :attributes => {:id => 'community_plugin2', :type => 'hidden', :value => 'Plugin 2'}
  237 + end
  238 +
216 239 end
... ...
test/unit/person_test.rb
... ... @@ -1247,4 +1247,26 @@ class PersonTest &lt; ActiveSupport::TestCase
1247 1247 assert !person.visible
1248 1248 assert_not_equal password, person.user.password
1249 1249 end
  1250 +
  1251 + should 'allow plugins to extend person\'s permission access' do
  1252 + person = create_user('some-user').person
  1253 + class Plugin1 < Noosfero::Plugin
  1254 + def has_permission?(person, permission, target)
  1255 + true
  1256 + end
  1257 + end
  1258 +
  1259 + class Plugin2 < Noosfero::Plugin
  1260 + def has_permission?(person, permission, target)
  1261 + false
  1262 + end
  1263 + end
  1264 +
  1265 + e = Environment.default
  1266 + e.enable_plugin(Plugin1.name)
  1267 + e.enable_plugin(Plugin2.name)
  1268 + person.stubs('has_permission_without_plugins?').returns(false)
  1269 +
  1270 + assert person.has_permission?('bli', Profile.new)
  1271 + end
1250 1272 end
... ...
test/unit/profile_test.rb
... ... @@ -1768,6 +1768,37 @@ class ProfileTest &lt; ActiveSupport::TestCase
1768 1768 end
1769 1769 end
1770 1770  
  1771 + should 'merge members of plugins to original members' do
  1772 + original_community = fast_create(Community)
  1773 + community1 = fast_create(Community, :identifier => 'community1')
  1774 + community2 = fast_create(Community, :identifier => 'community2')
  1775 + original_member = fast_create(Person)
  1776 + plugin1_member = fast_create(Person)
  1777 + plugin2_member = fast_create(Person)
  1778 + original_community.add_member(original_member)
  1779 + community1.add_member(plugin1_member)
  1780 + community2.add_member(plugin2_member)
  1781 +
  1782 + class Plugin1 < Noosfero::Plugin
  1783 + def organization_members(profile)
  1784 + Person.members_of(Community.find_by_identifier('community1'))
  1785 + end
  1786 + end
  1787 +
  1788 + class Plugin2 < Noosfero::Plugin
  1789 + def organization_members(profile)
  1790 + Person.members_of(Community.find_by_identifier('community2'))
  1791 + end
  1792 + end
  1793 +
  1794 + original_community.stubs(:enabled_plugins).returns([Plugin1.new, Plugin2.new])
  1795 +
  1796 + assert_includes original_community.members, original_member
  1797 + assert_includes original_community.members, plugin1_member
  1798 + assert_includes original_community.members, plugin2_member
  1799 + assert 3, original_community.members.count
  1800 + end
  1801 +
1771 1802 private
1772 1803  
1773 1804 def assert_invalid_identifier(id)
... ...
vendor/plugins/monkey_patches/methods_from_fake_arel/init.rb 0 → 100644
... ... @@ -0,0 +1,71 @@
  1 +# monkey patch to add fake_arel select, or_scope and where methods
  2 +# this gem requires activesupport-2.3.14 and activerecord-2.3.14
  3 +#
  4 +# https://github.com/gammons/fake_arel
  5 +
  6 +module Rails3Finder
  7 + def self.included(base)
  8 + base.class_eval do
  9 +
  10 + # the default named scopes
  11 + named_scope :offset, lambda {|offset| {:offset => offset}}
  12 + named_scope :limit, lambda {|limit| {:limit => limit}}
  13 + named_scope :includes, lambda { |*includes| { :include => includes }}
  14 + named_scope :order, lambda {|*order| {:order => order.join(',') }}
  15 + named_scope :joins, lambda {|*join| {:joins => join } if join[0]}
  16 + named_scope :from, lambda {|*from| {:from => from }}
  17 + named_scope :having, lambda {|*having| {:having => having }}
  18 + named_scope :group, lambda {|*group| {:group => group.join(',') }}
  19 + named_scope :readonly, lambda {|readonly| {:readonly => readonly }}
  20 + named_scope :lock, lambda {|lock| {:lock => lock }}
  21 +
  22 + def self.select(value = Proc.new)
  23 + if block_given?
  24 + all.select {|*block_args| value.call(*block_args) }
  25 + else
  26 + self.scoped(:select => Array.wrap(value).join(','))
  27 + end
  28 + end
  29 +
  30 + __where_fn = lambda do |*where|
  31 + if where.is_a?(Array) and where.size == 1
  32 + {:conditions => where.first}
  33 + else
  34 + {:conditions => where}
  35 + end
  36 + end
  37 +
  38 + named_scope :where, __where_fn
  39 +
  40 + # Use carefully this method! It might get lost with different classes
  41 + # scopes or different types of joins.
  42 + def self.or_scope(*scopes)
  43 + where = []
  44 + joins = []
  45 + includes = []
  46 +
  47 + # for some reason, flatten is actually executing the scope
  48 + scopes = scopes[0] if scopes.size == 1
  49 + scopes.each do |s|
  50 + s = s.proxy_options
  51 + begin
  52 + where << merge_conditions(s[:conditions])
  53 + rescue NoMethodError
  54 + # I am ActiveRecord::Base. Only my subclasses define merge_conditions:
  55 + where << subclasses.first.merge_conditions(s[:conditions])
  56 + end
  57 + #where << merge_conditions(s[:conditions])
  58 + joins << s[:joins] unless s[:joins].nil?
  59 + includes << s[:include] unless s[:include].nil?
  60 + end
  61 + scoped = self
  62 + scoped = scoped.select("DISTINCT #{self.table_name}.*")
  63 + scoped = scoped.includes(includes.uniq.flatten) unless includes.blank?
  64 + scoped = scoped.joins(joins.uniq.flatten) unless joins.blank?
  65 + scoped.where(where.join(" OR "))
  66 + end
  67 + end
  68 + end
  69 +end
  70 +
  71 +ActiveRecord::Base.send :include, Rails3Finder
... ...