Commit 32ea7f36badb289bbbb367a91ee537fa6fb4d25b
1 parent
06c13aae
Exists in
master
and in
22 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 | 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
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 < 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 < 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 < 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 | ... | ... |