Commit 32ea7f36badb289bbbb367a91ee537fa6fb4d25b
1 parent
06c13aae
Exists in
master
and in
29 other branches
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.
Showing
11 changed files
with
232 additions
and
3 deletions
Show diff stats
app/models/person.rb
@@ -3,9 +3,19 @@ class Person < Profile | @@ -3,9 +3,19 @@ class Person < Profile | ||
3 | 3 | ||
4 | acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)} | 4 | acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)} |
5 | acts_as_accessor | 5 | acts_as_accessor |
6 | + acts_as_having_hotspots | ||
6 | 7 | ||
7 | 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 | 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 | def memberships | 19 | def memberships |
10 | Profile.memberships_of(self) | 20 | Profile.memberships_of(self) |
11 | end | 21 | end |
app/models/profile.rb
@@ -54,6 +54,7 @@ class Profile < ActiveRecord::Base | @@ -54,6 +54,7 @@ class Profile < ActiveRecord::Base | ||
54 | } | 54 | } |
55 | 55 | ||
56 | acts_as_accessible | 56 | acts_as_accessible |
57 | + acts_as_having_hotspots | ||
57 | 58 | ||
58 | 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 | 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 | #FIXME: these will work only if the subclass is already loaded | 60 | #FIXME: these will work only if the subclass is already loaded |
@@ -61,13 +62,24 @@ class Profile < ActiveRecord::Base | @@ -61,13 +62,24 @@ class Profile < ActiveRecord::Base | ||
61 | named_scope :communities, lambda { {:conditions => (Community.send(:subclasses).map(&:name) << 'Community').map { |klass| "profiles.type = '#{klass}'"}.join(" OR ")} } | 62 | named_scope :communities, lambda { {:conditions => (Community.send(:subclasses).map(&:name) << 'Community').map { |klass| "profiles.type = '#{klass}'"}.join(" OR ")} } |
62 | 63 | ||
63 | def members | 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 | end | 68 | end |
66 | 69 | ||
67 | def members_count | 70 | def members_count |
68 | - members.count('DISTINCT(profiles.id)') | 71 | + members.count |
69 | end | 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 | def members_by_role(role) | 83 | def members_by_role(role) |
72 | Person.members_of(self).all(:conditions => ['role_assignments.role_id = ?', role.id]) | 84 | Person.members_of(self).all(:conditions => ['role_assignments.role_id = ?', role.id]) |
73 | end | 85 | end |
app/models/profile_list_block.rb
@@ -25,7 +25,7 @@ class ProfileListBlock < Block | @@ -25,7 +25,7 @@ class ProfileListBlock < Block | ||
25 | end | 25 | end |
26 | 26 | ||
27 | def profile_count | 27 | def profile_count |
28 | - profiles.visible.count('DISTINCT(profiles.id)') | 28 | + profiles.visible.count |
29 | end | 29 | end |
30 | 30 | ||
31 | # the title of the block. Probably will be overriden in subclasses. | 31 | # the title of the block. Probably will be overriden in subclasses. |
app/views/enterprise_registration/basic_information.rhtml
@@ -28,6 +28,12 @@ | @@ -28,6 +28,12 @@ | ||
28 | <%= hidden_field_tag 'create_enterprise[target_id]', environment.id %> | 28 | <%= hidden_field_tag 'create_enterprise[target_id]', environment.id %> |
29 | <% end %> | 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 | <% button_bar do %> | 37 | <% button_bar do %> |
32 | <%= submit_button('next', _('Next'), :cancel => {:profile => current_user.person.identifier, :action=>"enterprises", :controller=>"profile"}) %> | 38 | <%= submit_button('next', _('Next'), :cancel => {:profile => current_user.person.identifier, :action=>"enterprises", :controller=>"profile"}) %> |
33 | <% end %> | 39 | <% end %> |
app/views/memberships/new_community.rhtml
@@ -16,6 +16,12 @@ | @@ -16,6 +16,12 @@ | ||
16 | 16 | ||
17 | <%= required f.text_field(:name) %> | 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 | <%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => 'community', :profile => @community } %> | 25 | <%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => 'community', :profile => @community } %> |
20 | 26 | ||
21 | <% f.fields_for :image_builder, @community.image do |i| %> | 27 | <% f.fields_for :image_builder, @community.image do |i| %> |
lib/noosfero/plugin.rb
@@ -238,4 +238,29 @@ class Noosfero::Plugin | @@ -238,4 +238,29 @@ class Noosfero::Plugin | ||
238 | def comment_saved(comment) | 238 | def comment_saved(comment) |
239 | end | 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 | end | 266 | end |
test/functional/enterprise_registration_controller_test.rb
@@ -178,4 +178,27 @@ all_fixtures | @@ -178,4 +178,27 @@ all_fixtures | ||
178 | get :index | 178 | get :index |
179 | assert_equal assigns(:create_enterprise).target, environment | 179 | assert_equal assigns(:create_enterprise).target, environment |
180 | end | 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 | end | 204 | end |
test/functional/memberships_controller_test.rb
@@ -213,4 +213,27 @@ class MembershipsControllerTest < ActionController::TestCase | @@ -213,4 +213,27 @@ class MembershipsControllerTest < ActionController::TestCase | ||
213 | assert_no_tag :tag => 'textarea', :attributes => {:name => 'community[description]'} | 213 | assert_no_tag :tag => 'textarea', :attributes => {:name => 'community[description]'} |
214 | end | 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 | end | 239 | end |
test/unit/person_test.rb
@@ -1247,4 +1247,26 @@ class PersonTest < ActiveSupport::TestCase | @@ -1247,4 +1247,26 @@ class PersonTest < ActiveSupport::TestCase | ||
1247 | assert !person.visible | 1247 | assert !person.visible |
1248 | assert_not_equal password, person.user.password | 1248 | assert_not_equal password, person.user.password |
1249 | end | 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 | end | 1272 | end |
test/unit/profile_test.rb
@@ -1768,6 +1768,37 @@ class ProfileTest < ActiveSupport::TestCase | @@ -1768,6 +1768,37 @@ class ProfileTest < ActiveSupport::TestCase | ||
1768 | end | 1768 | end |
1769 | end | 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 | private | 1802 | private |
1772 | 1803 | ||
1773 | def assert_invalid_identifier(id) | 1804 | def assert_invalid_identifier(id) |
vendor/plugins/monkey_patches/methods_from_fake_arel/init.rb
0 → 100644
@@ -0,0 +1,71 @@ | @@ -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 |