Commit f8137b7ade33cedeb5ffea051098f597d0194fb3

Authored by Rodrigo Souto
Committed by Antonio Terceiro
1 parent 19be5f1a

Plugins

Done:
  * Plugin loading process
  * Infra-structure for plugins registering and event activations.
  * Three fixed routes for plugins.
  * Methods to define plugins meta-information.
  * Interface for activation and deactivation of plugins in the
    environment panel.
  * Hotspots:
    - Buttons in the control panel.
    - Tabs in the profile (including expanded_template method).
    - New attributes for profile.
  * Possibility for the plugin to add new tables.
  * Mezuro prototype.

Missing:
  * Test integration.
  * Dependencies integration.
  * Possibility to add behaviours in noosfero base models.
Showing 54 changed files with 1257 additions and 186 deletions   Show diff stats
app/controllers/admin/plugins_controller.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +class PluginsController < AdminController
  2 +
  3 + def index
  4 + @active_plugins = Noosfero::Plugin.all.map {|plugin_name| eval(plugin_name)}.compact
  5 + end
  6 +
  7 + post_only :update
  8 + def update
  9 + params[:environment][:enabled_plugins].delete('')
  10 + if @environment.update_attributes(params[:environment])
  11 + session[:notice] = _('Plugins updated successfully.')
  12 + else
  13 + session[:error] = _('Plugins were not updated successfully.')
  14 + end
  15 + redirect_to :action => 'index'
  16 + end
  17 +
  18 +end
... ...
app/controllers/application.rb
... ... @@ -83,6 +83,7 @@ class ApplicationController &lt; ActionController::Base
83 83 include NeedsProfile
84 84  
85 85 before_filter :detect_stuff_by_domain
  86 + before_filter :init_noosfero_plugins
86 87 attr_reader :environment
87 88  
88 89 before_filter :load_terminology
... ... @@ -114,6 +115,10 @@ class ApplicationController &lt; ActionController::Base
114 115 end
115 116 end
116 117  
  118 + def init_noosfero_plugins
  119 + @plugins = Noosfero::Plugin::Manager.new(self)
  120 + end
  121 +
117 122 def load_terminology
118 123 # cache terminology for performance
119 124 @@terminology_cache ||= {}
... ...
app/helpers/profile_helper.rb
... ... @@ -15,4 +15,12 @@ module ProfileHelper
15 15 end
16 16 end
17 17  
  18 + def render_tabs(tabs)
  19 + titles = tabs.inject(''){ |result, tab| result << content_tag(:li, link_to(tab[:title], '#'+tab[:id]), :class => 'tab') }
  20 + contents = tabs.inject(''){ |result, tab| result << content_tag(:div, tab[:content], :id => tab[:id]) }
  21 +
  22 + content_tag :div, :class => 'ui-tabs' do
  23 + content_tag(:ul, titles) + contents
  24 + end
  25 + end
18 26 end
... ...
app/models/environment.rb
... ... @@ -220,6 +220,8 @@ class Environment &lt; ActiveRecord::Base
220 220  
221 221 settings_items :trusted_sites_for_iframe, :type => Array, :default => ['itheora.org', 'tv.softwarelivre.org', 'stream.softwarelivre.org']
222 222  
  223 + settings_items :enabled_plugins, :type => Array, :default => []
  224 +
223 225 def news_amount_by_folder=(amount)
224 226 settings[:news_amount_by_folder] = amount.to_i
225 227 end
... ...
app/views/admin_panel/index.rhtml
... ... @@ -6,6 +6,7 @@
6 6 <tr><td><%= link_to _('Edit site info'), :action => 'site_info' %></td></tr>
7 7 <tr><td><%= link_to __('Edit message for disabled enterprises'), :action => 'message_for_disabled_enterprise' %></td></tr>
8 8 <tr><td><%= link_to _('Enable/disable features'), :controller => 'features' %></td></tr>
  9 + <tr><td><%= link_to _('Enable/disable plugins'), :controller => 'plugins' %></td></tr>
9 10 <tr><td><%= link_to _('Edit sideboxes'), :controller => 'environment_design'%></td></tr>
10 11 <tr><td><%= link_to _('Manage Categories'), :controller => 'categories'%></td></tr>
11 12 <tr><td><%= link_to _('Manage User roles'), :controller => 'role' %></td></tr>
... ...
app/views/plugins/index.rhtml 0 → 100644
... ... @@ -0,0 +1,29 @@
  1 +<h1><%= _('Manage plugins') %></h1>
  2 +<%= _('Here you can enable or disable any plugin of your environment.')%>
  3 +
  4 +<% labelled_form_for(:environment, @environment, :url => {:action => 'update'}) do |f| %>
  5 +
  6 +<table>
  7 + <tr>
  8 + <th><%= _('Plugin') %></th>
  9 + <th><%= _('Description') %></th>
  10 + <th><%= _('Enabled?') %></th>
  11 + </tr>
  12 + <%= hidden_field_tag('environment[enabled_plugins][]', '') %>
  13 + <% @active_plugins.each do |plugin| %>
  14 + <tr>
  15 + <td><%= plugin.plugin_name %></td>
  16 + <td><%= plugin.plugin_description %></td>
  17 + <td><%= check_box_tag "environment[enabled_plugins][]", plugin, @environment.enabled_plugins.include?(plugin.to_s), :id => plugin.plugin_name %></td>
  18 + </tr>
  19 + <% end %>
  20 +</table>
  21 +
  22 +<div>
  23 + <% button_bar do %>
  24 + <%= submit_button('save', _('Save changes')) %>
  25 + <%= button :back, _('Back to admin panel'), :controller => 'admin_panel', :action => 'index' %>
  26 + <% end %>
  27 +</div>
  28 +
  29 +<% end %>
... ...
app/views/profile/_organization.rhtml
... ... @@ -1,62 +0,0 @@
1   -
2   -<tr>
3   - <td colspan='2'>
4   - <div class='ui-tabs'>
5   -
6   - <ul>
7   - <% if logged_in? && current_person.follows?(@profile) %>
8   - <li class='tab'><a href='#profile-wall'><%= _('Wall') %></a></li>
9   - <% end %>
10   - <li class='tab'><a href='#profile-network'><%= _('What\'s new') %></a></li>
11   - <li class='tab'><a href='#community-profile'><%= _('Profile') %></a></li>
12   - </ul>
13   -
14   - <% if logged_in? && current_person.follows?(@profile) %>
15   - <%= render :partial => 'profile_wall' %>
16   - <% end %>
17   - <%= render :partial => 'profile_network' %>
18   -
19   - <div id='community-profile'>
20   - <table>
21   - <tr>
22   - <th colspan='2'><%= _('Basic information')%></th>
23   - </tr>
24   -
25   - <tr>
26   - <td class='field-name'><%= _('Members') %></td>
27   - <td>
28   - <%= link_to profile.members.count, :controller => 'profile', :action => 'members' %>
29   - </td>
30   - </tr>
31   -
32   - <%= display_field(_('Type:'), profile, :privacy_setting, true) %>
33   -
34   - <%= display_field(_('Location:'), profile, :location, true) %>
35   -
36   - <tr>
37   - <td class='field-name'><%= _('Created at:') %></td>
38   - <td><%= show_date(profile.created_at) %></td>
39   - </tr>
40   -
41   - <% if profile.kind_of?(Enterprise) && !profile.environment.enabled?('disable_products_for_enterprises') %>
42   - <tr>
43   - <td></td>
44   - <td>
45   - <%= link_to _('Products/Services'), :controller => 'catalog', :action => 'index' %>
46   - </td>
47   - </tr>
48   - <% end %>
49   -
50   - <tr>
51   - <td class='field-name'><%= _('Administrators:') %></td>
52   - <td>
53   - <%= profile.admins.map { |admin| link_to(admin.short_name, admin.url)}.join(', ') %>
54   - </td>
55   - </tr>
56   -
57   - <%= render :partial => 'common' %>
58   - </table>
59   - </div>
60   - </div>
61   - </td>
62   -</tr>
app/views/profile/_organization_profile.rhtml 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +<table>
  2 + <tr>
  3 + <th colspan='2'><%= _('Basic information')%></th>
  4 + </tr>
  5 +
  6 + <tr>
  7 + <td class='field-name'><%= _('Members') %></td>
  8 + <td>
  9 + <%= link_to profile.members.count, :controller => 'profile', :action => 'members' %>
  10 + </td>
  11 + </tr>
  12 +
  13 + <%= display_field(_('Type:'), profile, :privacy_setting, true) %>
  14 +
  15 + <%= display_field(_('Location:'), profile, :location, true) %>
  16 +
  17 + <tr>
  18 + <td class='field-name'><%= _('Created at:') %></td>
  19 + <td><%= show_date(profile.created_at) %></td>
  20 + </tr>
  21 +
  22 + <% if profile.kind_of?(Enterprise) && !profile.environment.enabled?('disable_products_for_enterprises') %>
  23 + <tr>
  24 + <td></td>
  25 + <td>
  26 + <%= link_to _('Products/Services'), :controller => 'catalog', :action => 'index' %>
  27 + </td>
  28 + </tr>
  29 + <% end %>
  30 +
  31 + <tr>
  32 + <td class='field-name'><%= _('Administrators:') %></td>
  33 + <td>
  34 + <%= profile.admins.map { |admin| link_to(admin.short_name, admin.url)}.join(', ') %>
  35 + </td>
  36 + </tr>
  37 +
  38 + <%= render :partial => 'common' %>
  39 +</table>
... ...
app/views/profile/_person.rhtml
... ... @@ -1,104 +0,0 @@
1   -<tr>
2   - <td colspan='2'>
3   - <div class='ui-tabs'>
4   -
5   - <ul>
6   - <% if logged_in? && current_person.follows?(@profile) %>
7   - <li class='tab'><a href='#profile-wall'><%= _('Wall') %></a></li>
8   - <li class='tab'><a href='#profile-network'><%= _('Network') %></a></li>
9   - <% end %>
10   - <% if @profile.public? || (logged_in? && current_person.follows?(@profile)) %>
11   - <li class='tab'><a href='#profile-activity'><%= _('Activity') %></a></li>
12   - <% end %>
13   - <li class='tab'><a href='#person-profile'><%= _('Profile') %></a></li>
14   - </ul>
15   -
16   - <% if logged_in? && current_person.follows?(@profile) %>
17   - <%= render :partial => 'profile_wall' %>
18   - <%= render :partial => 'profile_network' %>
19   - <% end %>
20   -
21   - <% if @profile.public? || (logged_in? && current_person.follows?(@profile)) %>
22   - <%= render :partial => 'profile_activity' %>
23   - <% end %>
24   -
25   - <div id='person-profile'>
26   - <table>
27   - <tr>
28   - <th colspan='2'><%= _('Basic information')%></th>
29   - </tr>
30   - <%= display_field(_('Sex:'), profile, :sex) { |gender| { 'male' => _('Male'), 'female' => _('Female') }[gender] } %>
31   - <%= display_field(_('Date of birth:'), profile, :birth_date) { |date| show_date(date) }%>
32   - <%= display_field(_('Location:'), profile, :location, true) %>
33   -
34   - <%= display_field(_('Type:'), profile, :privacy_setting, true) %>
35   -
36   - <tr>
37   - <td class='field-name'><%= _('Created at:') %></td>
38   - <td><%= show_date(profile.created_at) %></td>
39   - </tr>
40   -
41   - <% if profile == user || profile.friends.include?(user) %>
42   - <tr>
43   - <th colspan='2'><%= _('Contact')%></th>
44   - </tr>
45   - <%= display_field(_('Address:'), profile, :address) %>
46   - <%= display_field(_('ZIP code:'), profile, :zip_code) %>
47   - <%= display_field(_('Contact phone:'), profile, :contact_phone) %>
48   - <%= display_field(_('e-Mail:'), profile, :email, true) { |email| link_to_email(email) } %>
49   - <% end %>
50   -
51   - <% cache_timeout(profile.relationships_cache_key, 4.hours.from_now) do %>
52   - <% if !(profile.organization.blank? && profile.organization_website.blank?) && (profile.active_fields.include?('organization') || profile.active_fields.include?('organization_website')) %>
53   - <tr>
54   - <th colspan='2'><%= _('Work')%></th>
55   - </tr>
56   - <% end %>
57   - <%= display_field(_('Organization:'), profile, :organization) %>
58   - <%= display_field(_('Organization website:'), profile, :organization_website) { |url| link_to(url, url) }%>
59   -
60   -
61   - <% if !environment.enabled?('disable_asset_enterprises') && !profile.enterprises.empty? %>
62   - <tr>
63   - <th colspan='2'><%= __('Enterprises') %></th>
64   - </tr>
65   - <% profile.enterprises.each do |item| %>
66   - <tr>
67   - <td></td>
68   - <td><%= button 'menu-enterprise', item.name, item.url %></td>
69   - </tr>
70   - <% end %>
71   - <% end %>
72   -
73   - <tr>
74   - <th colspan='2'><%= _('Network')%></th>
75   - </tr>
76   - <tr>
77   - <td><%= __('Friends') + ':' %></td>
78   - <td><%= link_to profile.friends.count, { :controller => 'profile', :action => 'friends' } %></td>
79   - </tr>
80   - <tr>
81   - <td><%= __('Communities') + ':' %></td>
82   - <td><%= link_to profile.communities.count, :controller => "profile", :action => 'communities' %></td>
83   - </tr>
84   -
85   - <% if !environment.enabled?('disable_categories') && !profile.interests.empty? %>
86   - <tr>
87   - <th colspan='2'><%= _('Interests') %></th>
88   - </tr>
89   - <% profile.interests.each do |item| %>
90   - <tr>
91   - <td></td>
92   - <td><%= link_to item.name, :controller => 'search', :action => 'category_index', :category_path => item.explode_path %></td>
93   - </tr>
94   - <% end %>
95   - <% end %>
96   -
97   - <%= render :partial => 'common' %>
98   -
99   - </table>
100   - <% end %>
101   - </div>
102   - </div>
103   - </td>
104   -</tr>
app/views/profile/_person_profile.rhtml 0 → 100644
... ... @@ -0,0 +1,76 @@
  1 +<table>
  2 + <tr>
  3 + <th colspan='2'><%= _('Basic information')%></th>
  4 + </tr>
  5 + <%= display_field(_('Sex:'), profile, :sex) { |gender| { 'male' => _('Male'), 'female' => _('Female') }[gender] } %>
  6 + <%= display_field(_('Date of birth:'), profile, :birth_date) { |date| show_date(date) }%>
  7 + <%= display_field(_('Location:'), profile, :location, true) %>
  8 +
  9 + <%= display_field(_('Type:'), profile, :privacy_setting, true) %>
  10 +
  11 + <tr>
  12 + <td class='field-name'><%= _('Created at:') %></td>
  13 + <td><%= show_date(profile.created_at) %></td>
  14 + </tr>
  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 %>
  25 +
  26 + <% cache_timeout(profile.relationships_cache_key, 4.hours.from_now) 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 +
  35 +
  36 + <% if !environment.enabled?('disable_asset_enterprises') && !profile.enterprises.empty? %>
  37 + <tr>
  38 + <th colspan='2'><%= __('Enterprises') %></th>
  39 + </tr>
  40 + <% profile.enterprises.each do |item| %>
  41 + <tr>
  42 + <td></td>
  43 + <td><%= button 'menu-enterprise', item.name, item.url %></td>
  44 + </tr>
  45 + <% end %>
  46 + <% end %>
  47 +
  48 + <tr>
  49 + <th colspan='2'><%= _('Network')%></th>
  50 + </tr>
  51 + <tr>
  52 + <td><%= __('Friends') + ':' %></td>
  53 + <td><%= link_to profile.friends.count, { :controller => 'profile', :action => 'friends' } %></td>
  54 + </tr>
  55 + <tr>
  56 + <td><%= __('Communities') + ':' %></td>
  57 + <td><%= link_to profile.communities.count, :controller => "profile", :action => 'communities' %></td>
  58 + </tr>
  59 +
  60 + <% if !environment.enabled?('disable_categories') && !profile.interests.empty? %>
  61 + <tr>
  62 + <th colspan='2'><%= _('Interests') %></th>
  63 + </tr>
  64 + <% profile.interests.each do |item| %>
  65 + <tr>
  66 + <td></td>
  67 + <td><%= link_to item.name, :controller => 'search', :action => 'category_index', :category_path => item.explode_path %></td>
  68 + </tr>
  69 + <% end %>
  70 + <% end %>
  71 +
  72 + <%= render :partial => 'common' %>
  73 +
  74 + </table>
  75 +<% end %>
  76 +
... ...
app/views/profile/_profile.rhtml 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +<tr>
  2 + <td colspan='2'>
  3 +
  4 + <% plugins_tabs = @plugins.map(:profile_tabs) %>
  5 +
  6 + <% tabs = plugins_tabs.select { |tab| tab[:start] } %>
  7 +
  8 + <% if logged_in? && current_person.follows?(@profile) %>
  9 + <% tabs << {:title => _('Wall'), :id => 'profile-wall', :content => (render :partial => 'profile_wall')} %>
  10 + <% end %>
  11 +
  12 + <% if @profile.organization? %>
  13 + <% tabs << {:title => _('What\'s new'), :id => 'profile-network', :content => (render :partial => 'profile_network')} %>
  14 + <% tabs << {:title => _('Profile'), :id => 'organization-profile', :content => (render :partial => 'organization_profile')} %>
  15 + <% elsif @profile.person? %>
  16 + <% if logged_in? && current_person.follows?(@profile) %>
  17 + <% tabs << {:title => _('Network'), :id => 'profile-network', :content => (render :partial => 'profile_network')} %>
  18 + <% end %>
  19 +
  20 + <% if @profile.public? || (logged_in? && current_person.follows?(@profile)) %>
  21 + <% tabs << {:title => _('Activity'), :id => 'profile-activity', :content => (render :partial => 'profile_activity')} %>
  22 + <% end %>
  23 +
  24 + <% tabs << {:title => _('Profile'), :id => 'person-profile', :content => (render :partial => 'person_profile')} %>
  25 + <% end %>
  26 +
  27 + <% tabs += plugins_tabs.select { |tab| !tab[:start] } %>
  28 +
  29 + <%= render_tabs(tabs) %>
  30 +
  31 + </td>
  32 +</tr>
... ...
app/views/profile/_profile_network.rhtml
1   -<div id='profile-network'>
2   - <h3><%= _("%s's network activity") % @profile.name %></h3>
3   - <ul>
4   - <%= render :partial => 'profile_network_activities', :locals => {:network_activities => @network_activities} %>
5   - </ul>
6   -</div>
  1 +<h3><%= _("%s's network activity") % @profile.name %></h3>
  2 +<ul>
  3 + <%= render :partial => 'profile_network_activities', :locals => {:network_activities => @network_activities} %>
  4 +</ul>
... ...
app/views/profile/_profile_wall.rhtml
1   -<div id='profile-wall'>
2   - <h3><%= _("%s's wall") % @profile.name %></h3>
3   - <div id='leave_scrap'>
4   - <%= flash[:error] %>
5   - <% form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap', :tab_action => 'wall' }, :update => 'profile_scraps', :success => "$('leave_scrap_content').value=''" do %>
6   - <%= limited_text_area :scrap, :content, 420, 'leave_scrap_content', :cols => 50, :rows => 2 %>
7   - <%= submit_button :scrap, _('Leave a scrap') %>
8   - <% end %>
9   - </div>
10   - <div id='leave_scrap_response'></div>
11   - <ul id='profile_scraps'>
12   - <%= render :partial => 'profile_scraps', :locals => {:scraps => @wall_items} %>
13   - </ul>
  1 +<h3><%= _("%s's wall") % @profile.name %></h3>
  2 +<div id='leave_scrap'>
  3 + <%= flash[:error] %>
  4 + <% form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap', :tab_action => 'wall' }, :update => 'profile_scraps', :success => "$('leave_scrap_content').value=''" do %>
  5 + <%= limited_text_area :scrap, :content, 420, 'leave_scrap_content', :cols => 50, :rows => 2 %>
  6 + <%= submit_button :scrap, _('Leave a scrap') %>
  7 + <% end %>
14 8 </div>
  9 +<div id='leave_scrap_response'></div>
  10 +<ul id='profile_scraps'>
  11 + <%= render :partial => 'profile_scraps', :locals => {:scraps => @wall_items} %>
  12 +</ul>
... ...
app/views/profile/index.rhtml
... ... @@ -13,5 +13,5 @@
13 13 <% end %>
14 14  
15 15 <table class='profile'>
16   - <%= render :partial => partial_for_class(profile.class) %>
  16 + <%= render :partial => 'profile' %>
17 17 </table>
... ...
app/views/profile_editor/index.rhtml
... ... @@ -65,6 +65,11 @@
65 65 <% end %>
66 66  
67 67 <%= control_panel_button(_('Manage my groups'), 'groups', :controller => 'memberships') if profile.person? %>
  68 +
  69 + <% @plugins.map(:control_panel_buttons).each do |button| %>
  70 + <%= control_panel_button(button[:title], button[:icon], button[:url]) %>
  71 + <% end %>
  72 +
68 73 <% end %>
69 74  
70 75 <% if profile.person? && environment.enabled?('enterprise_activation') %>
... ...
config/initializers/plugins.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +require 'noosfero/plugin'
  2 +require 'noosfero/plugin/manager'
  3 +require 'noosfero/plugin/context'
  4 +require 'noosfero/plugin/active_record'
  5 +Noosfero::Plugin.init_system
... ...
config/plugins/mezuro 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../plugins/mezuro
0 2 \ No newline at end of file
... ...
config/routes.rb
... ... @@ -109,6 +109,12 @@ ActionController::Routing::Routes.draw do |map|
109 109 map.system 'system', :controller => 'system'
110 110 map.system 'system/:controller/:action/:id', :controller => Noosfero.pattern_for_controllers_in_directory('system')
111 111  
  112 + ######################################################
  113 + # plugin routes
  114 + ######################################################
  115 + plugins_routes = File.join(Rails.root + '/lib/noosfero/plugin/routes.rb')
  116 + eval(IO.read(plugins_routes), binding, plugins_routes)
  117 +
112 118 # cache stuff - hack
113 119 map.cache 'public/:action/:id', :controller => 'public'
114 120  
... ...
features/plugins.feature 0 → 100644
... ... @@ -0,0 +1,59 @@
  1 +Feature: plugins
  2 + As a noosfero\'s developer
  3 + I want to create hot spots that a plugin should use
  4 + As a plugins\' developer
  5 + I want to create plugins that uses noosfero\'s hot spots
  6 + As an admin of a noosfero environment
  7 + I want to activate and deactivate some plugins
  8 + As a user
  9 + I want to use the features implemented by the plugins
  10 +
  11 + Background:
  12 + Given the following users
  13 + | login | name |
  14 + | joaosilva | Joao Silva |
  15 + And I am logged in as "joaosilva"
  16 + And the following plugin
  17 + | klass |
  18 + | TestPlugin |
  19 + And the following events of TestPlugin
  20 + | event | body |
  21 + | control_panel_buttons | lambda { {:title => 'Test plugin button', :icon => '', :url => ''} } |
  22 + | profile_tabs | lambda { {:title => 'Test plugin tab', :id => 'test_plugin', :content => 'Test plugin random content'} } |
  23 +
  24 + Scenario: a user must see the plugin\'s button in the control panel if the plugin is enabled
  25 + Given plugin TestPlugin is enabled on environment
  26 + And I go to Joao Silva's control panel
  27 + Then I should see "Test plugin button"
  28 +
  29 + Scenario: a user must see the plugin\'s tab in in the profile if the plugin is enabled
  30 + Given plugin TestPlugin is enabled on environment
  31 + And I am on Joao Silva's profile
  32 + Then I should see "Test plugin tab"
  33 +
  34 + Scenario: a user must not see the plugin\'s button in the control panel if the plugin is disabled
  35 + Given plugin TestPlugin is disabled on environment
  36 + And I go to Joao Silva's control panel
  37 + Then I should not see "Test plugin button"
  38 +
  39 + Scenario: a user must not see the plugin\'s tab in in the profile if the plugin is disabled
  40 + Given plugin TestPlugin is disabled on environment
  41 + And I am on Joao Silva's profile
  42 + Then I should not see "Test plugin tab"
  43 +
  44 + Scenario: an admin should be able to deactivate a plugin
  45 + Given plugin TestPlugin is enabled on environment
  46 + And I am logged in as admin
  47 + When I go to the Control panel
  48 + Then I should see "Test plugin button"
  49 + When I go to the profile
  50 + Then I should see "Test plugin tab"
  51 + And I go to the environment control panel
  52 + And I follow "Enable/disable plugins"
  53 + And I uncheck "Test plugin"
  54 + And I press "Save changes"
  55 + When I go to the Control panel
  56 + Then I should not see "Test plugin button"
  57 + When I go to the profile
  58 + Then I should not see "Test plugin tab"
  59 +
... ...
features/step_definitions/noosfero_steps.rb
... ... @@ -146,6 +146,29 @@ Given /^the following certifiers$/ do |table|
146 146 end
147 147 end
148 148  
  149 +Given /^the following plugin?$/ do |table|
  150 + table.hashes.each do |row|
  151 + row = row.dup
  152 + klass_name = row.delete('klass')
  153 + eval("class #{klass_name} < Noosfero::Plugin; end;") unless eval("defined?(#{klass_name})")
  154 + end
  155 +end
  156 +
  157 +Given /^the following events of (.+)$/ do |plugin,table|
  158 + klass = eval(plugin)
  159 + table.hashes.each do |row|
  160 + row = row.dup
  161 + event = row.delete('event').to_sym
  162 + body = eval(row.delete('body'))
  163 +
  164 + klass.class_eval do
  165 + define_method(event) do
  166 + body.call
  167 + end
  168 + end
  169 + end
  170 +end
  171 +
149 172 Given /^I am logged in as "(.+)"$/ do |username|
150 173 visit('/account/logout')
151 174 visit('/account/login')
... ... @@ -184,6 +207,16 @@ Given /^feature &quot;(.+)&quot; is (enabled|disabled) on environment$/ do |feature, statu
184 207 e.save
185 208 end
186 209  
  210 +Given /^plugin (.+) is (enabled|disabled) on environment$/ do |plugin, status|
  211 + e = Environment.default
  212 + if status == 'enabled'
  213 + e.enabled_plugins += [plugin]
  214 + else
  215 + e.enabled_plugins -= [plugin]
  216 + end
  217 + e.save!
  218 +end
  219 +
187 220 Given /^organization_approval_method is "(.+)" on environment$/ do |approval_method|
188 221 e = Environment.default
189 222 e.organization_approval_method = approval_method
... ...
features/support/paths.rb
... ... @@ -33,6 +33,9 @@ module NavigationHelpers
33 33 when /^(.*)'s profile/
34 34 '/profile/%s' % Profile.find_by_name($1).identifier
35 35  
  36 + when /^the profile$/
  37 + '/profile/%s' % User.find_by_id(session[:user]).login
  38 +
36 39 when /^login page$/
37 40 '/account/login'
38 41  
... ... @@ -45,6 +48,9 @@ module NavigationHelpers
45 48 when /^the Control panel$/
46 49 '/myprofile/%s' % User.find_by_id(session[:user]).login
47 50  
  51 + when /the environment control panel/
  52 + '/admin'
  53 +
48 54 when /^the search page$/
49 55 '/search'
50 56  
... ...
lib/noosfero/plugin.rb 0 → 100644
... ... @@ -0,0 +1,75 @@
  1 +require 'noosfero'
  2 +class Noosfero::Plugin
  3 +
  4 + attr_accessor :context
  5 +
  6 + class << self
  7 +
  8 + def init_system
  9 + Dir.glob(File.join(Rails.root, 'config', 'plugins', '*')).select do |entry|
  10 + File.directory?(entry)
  11 + end.each do |dir|
  12 + Rails.configuration.controller_paths << File.join(dir, 'controllers')
  13 + Dependencies.load_paths << File.join(dir, 'controllers')
  14 + [ Dependencies.load_paths, $:].each do |path|
  15 + path << File.join(dir, 'models')
  16 + path << File.join(dir, 'lib')
  17 + end
  18 +
  19 + plugin_name = File.basename(dir).camelize + 'Plugin'
  20 + plugin_name.constantize # load the plugin
  21 + end
  22 + end
  23 +
  24 + def all
  25 + @all ||= []
  26 + end
  27 +
  28 + def inherited(subclass)
  29 + all << subclass.to_s unless all.include?(subclass.to_s)
  30 + end
  31 +
  32 + # Here the developer should specify the meta-informations that the plugin can
  33 + # inform.
  34 + def plugin_name
  35 + self.to_s.underscore.humanize
  36 + end
  37 +
  38 + def plugin_description
  39 + _("No description informed.")
  40 + end
  41 +
  42 + end
  43 +
  44 + def expanded_template(original_path, file_path, locals = {})
  45 + while(File.basename(File.dirname(original_path)) != 'plugins')
  46 + original_path = File.dirname(original_path)
  47 + end
  48 +
  49 + ERB.new(File.read("#{original_path}/#{file_path}")).result(binding)
  50 + end
  51 +
  52 + # Here the developer should specify the events to which the plugins can
  53 + # register to. Must be explicitly defined its returning
  54 + # variables.
  55 +
  56 + # -> Adds buttons to the control panel
  57 + # returns = { :title => title, :icon => icon, :url => url }
  58 + # title = name that will be displayed.
  59 + # icon = css class name (for customized icons include them in a css file).
  60 + # url = url or route to which the button will redirect.
  61 + def control_panel_buttons
  62 + nil
  63 + end
  64 +
  65 + # -> Adds tabs to the profile
  66 + # returns = { :title => title, :id => id, :content => content, :start => start }
  67 + # title = name that will be displayed.
  68 + # id = div id.
  69 + # content = content of the tab (use expanded_template method to import content from another file).
  70 + # start = boolean that specifies if the tab must come before noosfero tabs (optional).
  71 + def profile_tabs
  72 + nil
  73 + end
  74 +
  75 +end
... ...
lib/noosfero/plugin/active_record.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class Noosfero::Plugin::ActiveRecord < ActiveRecord::Base
  2 + def self.inherited(child)
  3 + child.table_name = child.name.gsub('::','_').underscore.pluralize
  4 + end
  5 +end
... ...
lib/noosfero/plugin/context.rb 0 → 100644
... ... @@ -0,0 +1,29 @@
  1 +class Noosfero::Plugin::Context
  2 +
  3 + def initialize(controller)
  4 + @controller = controller
  5 + end
  6 +
  7 + # Here the developer should define the interface to important context
  8 + # information from the controller to the plugins to access
  9 + def profile
  10 + @profile ||= @controller.send(:profile)
  11 + end
  12 +
  13 + def request
  14 + @request ||= @controller.send(:request)
  15 + end
  16 +
  17 + def response
  18 + @response ||= @controller.send(:response)
  19 + end
  20 +
  21 + def environment
  22 + @environment ||= @controller.send(:environment)
  23 + end
  24 +
  25 + def params
  26 + @params ||= @controller.send(:params)
  27 + end
  28 +
  29 +end
... ...
lib/noosfero/plugin/manager.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +class Noosfero::Plugin::Manager
  2 +
  3 + attr_reader :context
  4 +
  5 + def initialize(controller)
  6 + @context = Noosfero::Plugin::Context.new(controller)
  7 + end
  8 +
  9 + def map(event)
  10 + enabled_plugins.map { |plugin| plugin.send(event) }.compact.flatten
  11 + end
  12 +
  13 + def enabled_plugins
  14 + @enabled_plugins ||= (Noosfero::Plugin.all & context.environment.enabled_plugins).map do |plugin|
  15 + p = eval(plugin).new
  16 + p.context = context
  17 + p
  18 + end
  19 + end
  20 +
  21 +end
... ...
lib/noosfero/plugin/routes.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +Dir.glob(File.join(Rails.root, 'config', 'plugins', '*', 'controllers')) do |dir|
  2 + plugin_name = File.basename(File.dirname(dir))
  3 + map.connect 'plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_environment'
  4 + map.connect 'profile/:profile/plugins/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_profile'
  5 + map.connect 'myprofile/:profile/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_myprofile'
  6 +end
  7 +
... ...
lib/tasks/plugins.rake 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +namespace :plugins do
  2 + task :migrate do
  3 + Dir.glob(File.join(Rails.root, 'config', 'plugins', '*', 'db', 'migrate')).each do |path|
  4 + ActiveRecord::Migrator.migrate(path, ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
  5 + end
  6 + end
  7 +end
  8 +
  9 +task 'db:migrate' => 'plugins:migrate'
... ...
plugins/mezuro/README 0 → 100644
... ... @@ -0,0 +1 @@
  1 +This plugin depends on ruby binds for subversion. Checkout http://packages.debian.org/lenny/libsvn-ruby and http://subversion.tigris.org/ for further information.
... ...
plugins/mezuro/controllers/mezuro_plugin_myprofile_controller.rb 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +class MezuroPluginMyprofileController < MyProfileController
  2 + append_view_path File.join(File.dirname(__FILE__) + '/../views')
  3 +
  4 + def index
  5 + @projects = MezuroPlugin::Project.by_profile(profile)
  6 + end
  7 +
  8 + def new
  9 + @project = MezuroPlugin::Project.new
  10 + end
  11 +
  12 + def create
  13 + @project = MezuroPlugin::Project.new(params[:project])
  14 + if @project.save
  15 + session[:notice] = _('Project successfully registered')
  16 + redirect_to :action => 'index'
  17 + else
  18 + render :action => 'new'
  19 + end
  20 + end
  21 +
  22 + def edit
  23 + @project = MezuroPlugin::Project.find(params[:id])
  24 + end
  25 +
  26 + def update
  27 + @project = MezuroPlugin::Project.find(params[:id])
  28 + if @project.update_attributes(params[:project])
  29 + session[:notice] = _('Project successfully updated')
  30 + redirect_to :action => 'index'
  31 + else
  32 + render :action => 'edit'
  33 + end
  34 + end
  35 +
  36 + def show
  37 + @project = MezuroPlugin::Project.find_by_identifier params[:identifier]
  38 + @total_metrics = @project.total_metrics if @project != nil
  39 + @statistical_metrics = @project.statistical_metrics if @project != nil
  40 + @svn_error = @project.svn_error if (@project != nil && @project.svn_error)
  41 + end
  42 +
  43 + def destroy
  44 + @project = MezuroPlugin::Project.by_profile(profile).find(params[:id])
  45 + if request.post?
  46 + if @project.destroy
  47 + session[:notice] = _('Project successfully removed.')
  48 + else
  49 + session[:notice] = _('Project was not successfully removed.')
  50 + end
  51 + redirect_to :action => 'index'
  52 + end
  53 + end
  54 +
  55 +end
... ...
plugins/mezuro/db/migrate/20101209151530_create_projects.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +class CreateProjects < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :mezuro_plugin_projects do |t|
  4 + t.string :name
  5 + t.string :identifier
  6 + t.string :personal_webpage
  7 + t.text :description
  8 + t.string :repository_url
  9 + t.string :svn_error
  10 + t.boolean :with_tab
  11 + t.references :profile
  12 +
  13 + t.timestamps
  14 + end
  15 + end
  16 +
  17 + def self.down
  18 + drop_table :mezuro_plugin_projects
  19 + end
  20 +end
... ...
plugins/mezuro/db/migrate/20101209151640_create_metrics.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +class CreateMetrics < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :mezuro_plugin_metrics do |t|
  4 + t.string :name
  5 + t.float :value
  6 + t.integer :metricable_id
  7 + t.string :metricable_type
  8 +
  9 + t.timestamps
  10 + end
  11 + end
  12 +
  13 + def self.down
  14 + drop_table :mezuro_plugin_metrics
  15 + end
  16 +end
... ...
plugins/mezuro/init.rb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +require "mezuro_plugin"
... ...
plugins/mezuro/lib/mezuro_plugin.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +class MezuroPlugin < Noosfero::Plugin
  2 +
  3 + def self.plugin_name
  4 + "Mezuro"
  5 + end
  6 +
  7 + def self.plugin_description
  8 + _("A metric analizer plugin.")
  9 + end
  10 +
  11 + def control_panel_buttons
  12 + if context.profile.community?
  13 + { :title => 'Mezuro projects', :icon => 'mezuro', :url => {:controller => 'mezuro_plugin_myprofile', :action => 'index'} }
  14 + end
  15 + end
  16 +
  17 + def profile_tabs
  18 + if context.profile.community? && !MezuroPlugin::Project.by_profile(context.profile).blank?
  19 + MezuroPlugin::Project.by_profile(context.profile).with_tab.map do |project|
  20 + { :title => 'Mezuro ' + project.name,
  21 + :id => 'mezuro-project-'+project.identifier,
  22 + :content => expanded_template(__FILE__,"views/show.html.erb",{:current_project => project}) }
  23 + end
  24 + end
  25 + end
  26 +
  27 +end
... ...
plugins/mezuro/lib/mezuro_plugin/analizo_extractor.rb 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +class MezuroPlugin::AnalizoExtractor < Noosfero::Plugin::ActiveRecord
  2 + attr_reader :string_output, :hash_output
  3 +
  4 + def initialize project
  5 + @project = project
  6 + end
  7 +
  8 + def perform
  9 + run_analizo
  10 + create_hash
  11 + save_metrics
  12 + end
  13 +
  14 + def run_analizo
  15 + project_path = "#{RAILS_ROOT}/tmp/#{@project.identifier}"
  16 + @string_output = `analizo metrics #{project_path}`
  17 + end
  18 +
  19 + def create_hash
  20 + @hash_output = {}
  21 + first_line = true
  22 +
  23 + @string_output.lines.each do |line|
  24 + if line =~ /---/
  25 + if first_line
  26 + first_line = false
  27 + else
  28 + break
  29 + end
  30 + end
  31 +
  32 + if line =~ /(\S+): (~|(\d+)(\.\d+)?).*/
  33 + @hash_output[$1.to_sym] = $2
  34 + end
  35 + end
  36 + end
  37 +
  38 + def save_metrics
  39 + @hash_output.each do | key, value |
  40 + MezuroPlugin::Metric.create(:name => key.to_s, :value => value.to_f, :metricable => @project)
  41 + end
  42 + end
  43 +end
... ...
plugins/mezuro/lib/mezuro_plugin/calculate_metrics_job.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +class MezuroPlugin::CalculateMetricsJob < Struct.new(:project_id)
  2 + def perform
  3 + project = MezuroPlugin::Project.find project_id
  4 + project.calculate_metrics
  5 + end
  6 +end
  7 +
... ...
plugins/mezuro/lib/mezuro_plugin/metric.rb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +class MezuroPlugin::Metric < Noosfero::Plugin::ActiveRecord
  2 + validates_presence_of :name, :metricable_id, :metricable_type
  3 +
  4 + belongs_to :metricable, :polymorphic => true
  5 + before_save :round_value
  6 +
  7 + def initialize params
  8 + params[:value] = nil if params[:value] == '~'
  9 + super params
  10 + end
  11 +
  12 + def round_value
  13 + if self.value
  14 + multiplied = self.value * 100
  15 + rounded = multiplied.round
  16 + self.value = rounded / 100.0
  17 + end
  18 + end
  19 +end
... ...
plugins/mezuro/lib/mezuro_plugin/project.rb 0 → 100644
... ... @@ -0,0 +1,74 @@
  1 +class MezuroPlugin::Project < Noosfero::Plugin::ActiveRecord
  2 + has_many :metrics, :as => :metricable
  3 +
  4 + validates_presence_of :name, :repository_url, :identifier
  5 + validates_format_of :identifier, :with => /^[a-z0-9|\-|\.]*$/, :message => "Identifier can only have a combination of lower case, number, hyphen and dot!"
  6 + validates_uniqueness_of :identifier
  7 +
  8 + named_scope :with_tab, :conditions => {:with_tab => true}
  9 + named_scope :by_profile, lambda {|profile| {:conditions => {:profile_id => profile.id}}}
  10 +
  11 +
  12 + after_create :asynchronous_calculate_metrics
  13 +
  14 + def calculate_metrics
  15 + begin
  16 + download_source_code
  17 + extractor = MezuroPlugin::AnalizoExtractor.new self
  18 + extractor.perform
  19 + rescue Svn::Error => error
  20 + update_attribute :svn_error, error.error_message
  21 + end
  22 + end
  23 +
  24 + def asynchronous_calculate_metrics
  25 + Delayed::Job.enqueue MezuroPlugin::CalculateMetricsJob.new(id)
  26 + end
  27 +
  28 + def download_source_code
  29 + download_prepare
  30 + Svn::Client::Context.new.checkout(repository_url, "#{RAILS_ROOT}/tmp/#{identifier}")
  31 + end
  32 +
  33 + def download_prepare
  34 + project_path = "#{RAILS_ROOT}/tmp/#{identifier}"
  35 + FileUtils.rm_r project_path if (File.exists? project_path)
  36 + end
  37 +
  38 + def metrics_calculated?
  39 + return !metrics.empty?
  40 + end
  41 +
  42 + def total_metrics
  43 + total_metrics = metrics.select do |metric|
  44 + metric.name.start_with?("total")
  45 + end
  46 + return total_metrics.sort_by {|metric| metric.name}
  47 + end
  48 +
  49 + def statistical_metrics
  50 + statistical_metrics = collect_statistical_metrics
  51 +
  52 + hash = {}
  53 + statistical_metrics.each do |metric|
  54 + insert_metric_in_hash metric, hash
  55 + end
  56 + hash
  57 + end
  58 +
  59 + def collect_statistical_metrics
  60 + statistical_metrics = metrics.select do |metric|
  61 + not metric.name.start_with?("total")
  62 + end
  63 + statistical_metrics.sort_by {|metric| metric.name}
  64 + end
  65 +
  66 + def insert_metric_in_hash metric, hash
  67 + metric_name, metric_statistic = metric.name.split("_")
  68 + unless hash.key?(metric_name)
  69 + hash[metric_name] = {metric_statistic => metric.value}
  70 + else
  71 + hash[metric_name][metric_statistic] = metric.value
  72 + end
  73 + end
  74 +end
... ...
plugins/mezuro/lib/tasks/cucumber.rake 0 → 100644
... ... @@ -0,0 +1,47 @@
  1 +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
  2 +# It is recommended to regenerate this file in the future when you upgrade to a
  3 +# newer version of cucumber-rails. Consider adding your own code to a new file
  4 +# instead of editing this one. Cucumber will automatically load all features/**/*.rb
  5 +# files.
  6 +
  7 +
  8 +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks
  9 +
  10 +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
  11 +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil?
  12 +
  13 +begin
  14 + require 'cucumber/rake/task'
  15 +
  16 + namespace :cucumber do
  17 + Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
  18 + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
  19 + t.fork = true # You may get faster startup if you set this to false
  20 + t.profile = 'default'
  21 + end
  22 +
  23 + Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
  24 + t.binary = vendored_cucumber_bin
  25 + t.fork = true # You may get faster startup if you set this to false
  26 + t.profile = 'wip'
  27 + end
  28 +
  29 + desc 'Run all features'
  30 + task :all => [:ok, :wip]
  31 + end
  32 + desc 'Alias for cucumber:ok'
  33 + task :cucumber => 'cucumber:ok'
  34 +
  35 + task :default => :cucumber
  36 +
  37 + task :features => :cucumber do
  38 + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
  39 + end
  40 +rescue LoadError
  41 + desc 'cucumber rake task not available (cucumber not installed)'
  42 + task :cucumber do
  43 + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
  44 + end
  45 +end
  46 +
  47 +end
... ...
plugins/mezuro/lib/tasks/rspec.rake 0 → 100644
... ... @@ -0,0 +1,144 @@
  1 +gem 'test-unit', '1.2.3' if RUBY_VERSION.to_f >= 1.9
  2 +rspec_gem_dir = nil
  3 +Dir["#{RAILS_ROOT}/vendor/gems/*"].each do |subdir|
  4 + rspec_gem_dir = subdir if subdir.gsub("#{RAILS_ROOT}/vendor/gems/","") =~ /^(\w+-)?rspec-(\d+)/ && File.exist?("#{subdir}/lib/spec/rake/spectask.rb")
  5 +end
  6 +rspec_plugin_dir = File.expand_path(File.dirname(__FILE__) + '/../../vendor/plugins/rspec')
  7 +
  8 +if rspec_gem_dir && (test ?d, rspec_plugin_dir)
  9 + raise "\n#{'*'*50}\nYou have rspec installed in both vendor/gems and vendor/plugins\nPlease pick one and dispose of the other.\n#{'*'*50}\n\n"
  10 +end
  11 +
  12 +if rspec_gem_dir
  13 + $LOAD_PATH.unshift("#{rspec_gem_dir}/lib")
  14 +elsif File.exist?(rspec_plugin_dir)
  15 + $LOAD_PATH.unshift("#{rspec_plugin_dir}/lib")
  16 +end
  17 +
  18 +# Don't load rspec if running "rake gems:*"
  19 +unless ARGV.any? {|a| a =~ /^gems/}
  20 +
  21 +begin
  22 + require 'spec/rake/spectask'
  23 +rescue MissingSourceFile
  24 + module Spec
  25 + module Rake
  26 + class SpecTask
  27 + def initialize(name)
  28 + task name do
  29 + # if rspec-rails is a configured gem, this will output helpful material and exit ...
  30 + require File.expand_path(File.join(File.dirname(__FILE__),"..","..","config","environment"))
  31 +
  32 + # ... otherwise, do this:
  33 + raise <<-MSG
  34 +
  35 +#{"*" * 80}
  36 +* You are trying to run an rspec rake task defined in
  37 +* #{__FILE__},
  38 +* but rspec can not be found in vendor/gems, vendor/plugins or system gems.
  39 +#{"*" * 80}
  40 +MSG
  41 + end
  42 + end
  43 + end
  44 + end
  45 + end
  46 +end
  47 +
  48 +Rake.application.instance_variable_get('@tasks').delete('default')
  49 +
  50 +spec_prereq = File.exist?(File.join(RAILS_ROOT, 'config', 'database.yml')) ? "db:test:prepare" : :noop
  51 +task :noop do
  52 +end
  53 +
  54 +task :default => :spec
  55 +task :stats => "spec:statsetup"
  56 +
  57 +desc "Run all specs in spec directory (excluding plugin specs)"
  58 +Spec::Rake::SpecTask.new(:spec => spec_prereq) do |t|
  59 + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
  60 + t.spec_files = FileList['spec/**/*_spec.rb']
  61 +end
  62 +
  63 +namespace :spec do
  64 + desc "Run all specs in spec directory with RCov (excluding plugin specs)"
  65 + Spec::Rake::SpecTask.new(:rcov) do |t|
  66 + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
  67 + t.spec_files = FileList['spec/**/*_spec.rb']
  68 + t.rcov = true
  69 + t.rcov_opts = lambda do
  70 + IO.readlines("#{RAILS_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
  71 + end
  72 + end
  73 +
  74 + desc "Print Specdoc for all specs (excluding plugin specs)"
  75 + Spec::Rake::SpecTask.new(:doc) do |t|
  76 + t.spec_opts = ["--format", "specdoc", "--dry-run"]
  77 + t.spec_files = FileList['spec/**/*_spec.rb']
  78 + end
  79 +
  80 + desc "Print Specdoc for all plugin examples"
  81 + Spec::Rake::SpecTask.new(:plugin_doc) do |t|
  82 + t.spec_opts = ["--format", "specdoc", "--dry-run"]
  83 + t.spec_files = FileList['vendor/plugins/**/spec/**/*_spec.rb'].exclude('vendor/plugins/rspec/*')
  84 + end
  85 +
  86 + [:models, :controllers, :views, :helpers, :lib, :integration].each do |sub|
  87 + desc "Run the code examples in spec/#{sub}"
  88 + Spec::Rake::SpecTask.new(sub => spec_prereq) do |t|
  89 + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
  90 + t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
  91 + end
  92 + end
  93 +
  94 + desc "Run the code examples in vendor/plugins (except RSpec's own)"
  95 + Spec::Rake::SpecTask.new(:plugins => spec_prereq) do |t|
  96 + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
  97 + t.spec_files = FileList['vendor/plugins/**/spec/**/*_spec.rb'].exclude('vendor/plugins/rspec/*').exclude("vendor/plugins/rspec-rails/*")
  98 + end
  99 +
  100 + namespace :plugins do
  101 + desc "Runs the examples for rspec_on_rails"
  102 + Spec::Rake::SpecTask.new(:rspec_on_rails) do |t|
  103 + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
  104 + t.spec_files = FileList['vendor/plugins/rspec-rails/spec/**/*_spec.rb']
  105 + end
  106 + end
  107 +
  108 + # Setup specs for stats
  109 + task :statsetup do
  110 + require 'code_statistics'
  111 + ::STATS_DIRECTORIES << %w(Model\ specs spec/models) if File.exist?('spec/models')
  112 + ::STATS_DIRECTORIES << %w(View\ specs spec/views) if File.exist?('spec/views')
  113 + ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers) if File.exist?('spec/controllers')
  114 + ::STATS_DIRECTORIES << %w(Helper\ specs spec/helpers) if File.exist?('spec/helpers')
  115 + ::STATS_DIRECTORIES << %w(Library\ specs spec/lib) if File.exist?('spec/lib')
  116 + ::STATS_DIRECTORIES << %w(Routing\ specs spec/routing) if File.exist?('spec/routing')
  117 + ::STATS_DIRECTORIES << %w(Integration\ specs spec/integration) if File.exist?('spec/integration')
  118 + ::CodeStatistics::TEST_TYPES << "Model specs" if File.exist?('spec/models')
  119 + ::CodeStatistics::TEST_TYPES << "View specs" if File.exist?('spec/views')
  120 + ::CodeStatistics::TEST_TYPES << "Controller specs" if File.exist?('spec/controllers')
  121 + ::CodeStatistics::TEST_TYPES << "Helper specs" if File.exist?('spec/helpers')
  122 + ::CodeStatistics::TEST_TYPES << "Library specs" if File.exist?('spec/lib')
  123 + ::CodeStatistics::TEST_TYPES << "Routing specs" if File.exist?('spec/routing')
  124 + ::CodeStatistics::TEST_TYPES << "Integration specs" if File.exist?('spec/integration')
  125 + end
  126 +
  127 + namespace :db do
  128 + namespace :fixtures do
  129 + desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z."
  130 + task :load => :environment do
  131 + ActiveRecord::Base.establish_connection(Rails.env)
  132 + base_dir = File.join(Rails.root, 'spec', 'fixtures')
  133 + fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir
  134 +
  135 + require 'active_record/fixtures'
  136 + (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir.glob(File.join(fixtures_dir, '*.{yml,csv}'))).each do |fixture_file|
  137 + Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*'))
  138 + end
  139 + end
  140 + end
  141 + end
  142 +end
  143 +
  144 +end
... ...
plugins/mezuro/public/images/mezuro.gif 0 → 100644

2 KB

plugins/mezuro/public/images/mezuro.png 0 → 100644

3.65 KB

plugins/mezuro/public/images/minus.png 0 → 100644

215 Bytes

plugins/mezuro/public/images/plus.png 0 → 100644

400 Bytes

plugins/mezuro/public/stylesheets/base_layout.css 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +.controller-profile_editor a.control-panel-mezuro {background-image: url(../images/control-panel/mezuro.png)}
  2 +.controller-profile_editor .msie6 a.control-panel-edit-location {background-image: url(../images/control-panel/mezuro.gif)}
... ...
plugins/mezuro/views/mezuro_plugin_myprofile/_form.html.erb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +<%= error_messages_for 'project' %>
  2 +
  3 +<% labelled_form_for :project, @project, :url => @url do |f| %>
  4 + <%= hidden_field_tag 'project[profile_id]', profile.id %>
  5 + <%= hidden_field_tag 'id', @project.id %>
  6 +
  7 + <%= required_fields_message %>
  8 +
  9 + <%= required f.text_field(:name) %>
  10 + <%= required f.text_field(:repository_url) %>
  11 + <%= required f.text_field(:identifier) %>
  12 + <%= f.check_box(:with_tab) %>
  13 + <%= f.text_area(:description, :size => '30x3') %>
  14 +
  15 + <% button_bar do %>
  16 + <%= submit_button(:save, @submit_button, :cancel => {:action => 'index'})%>
  17 + <% end %>
  18 +<% end %>
  19 +
  20 +<%= javascript_tag "$('project_name').focus();" %>
... ...
plugins/mezuro/views/mezuro_plugin_myprofile/edit.html.erb 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +<h1> <%= _("Edit project") %> </h1>
  2 +<% @url = {:action => 'update'} %>
  3 +<% @submit_button = _("Update project") %>
  4 +<%= render :partial => 'form' %>
... ...
plugins/mezuro/views/mezuro_plugin_myprofile/index.html.erb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +<h1> <%= _("%s's Mezuro projects") % profile.name %> </h1>
  2 +
  3 +<% if @projects.blank? %>
  4 + <%= _("%s has no projects registered.") % profile.name %>
  5 +<% else %>
  6 + <table>
  7 + <tr>
  8 + <th><%= _('Project') %></th>
  9 + <th><%= _('Actions') %></th>
  10 + </tr>
  11 +
  12 + <% @projects.each do |project| %>
  13 + <tr>
  14 + <td><%= project.name %></td>
  15 + <td class="article-controls">
  16 + <%= button_without_text :edit, _('Edit'), :action => 'edit', :id => project.id %>
  17 + <%= button_without_text :delete, _('Delete'), { :action => 'destroy', :id => project.id },
  18 + :method => :post,
  19 + :confirm => _("Are you sure you want to remove this project?") %>
  20 + </td>
  21 + </tr>
  22 + <% end %>
  23 + </table>
  24 +<% end %>
  25 +
  26 +<% button_bar do %>
  27 + <%= button :new, _('Register a new project'), :action => 'new' %>
  28 +<% end %>
... ...
plugins/mezuro/views/mezuro_plugin_myprofile/new.html.erb 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +<h1> <%= _("New project") %> </h1>
  2 +<% @url = {:action => 'create'} %>
  3 +<% @submit_button = _("Register project") %>
  4 +<%= render :partial => 'form' %>
... ...
plugins/mezuro/views/show.html.erb 0 → 100644
... ... @@ -0,0 +1,81 @@
  1 +<% @project = locals[:current_project] %>
  2 +<% @total_metrics = @project.total_metrics if @project != nil %>
  3 +<% @statistical_metrics = @project.statistical_metrics if @project != nil %>
  4 +<% @svn_error = @project.svn_error if (@project != nil && @project.svn_error) %>
  5 +
  6 +<h1> <%= @project.name %>'s Info</h1>
  7 +<table id="show_info">
  8 + <tr>
  9 + <td><%= _("Name") %></td>
  10 + <td><%= @project.name %></td>
  11 + </tr>
  12 + <% if (@project.description != nil && @project.description != "" ) %>
  13 + <tr>
  14 + <td><%= _("Description") %></td>
  15 + <td><%= @project.description %></td>
  16 + </tr>
  17 + <% end %>
  18 + <tr>
  19 + <td><%= _("Repository address") %></td>
  20 + <td><%= @project.repository_url %></td>
  21 + </tr>
  22 +</table>
  23 +
  24 +<br />
  25 +
  26 +
  27 +<% if @svn_error %>
  28 + <h3>ERROR</h3>
  29 + <div id="svn_error">
  30 + <%= @svn_error %>
  31 + </div>
  32 + <br />
  33 + <br />
  34 + <p>
  35 + <%= _("Possible causes:") %>
  36 + <ul>
  37 + <li>
  38 + <%= _("Server is down") %>
  39 + </li>
  40 + <li>
  41 + <% _("URL invalid, in this case create another project with the correct URL
  42 + (Sorry for the incovenience, we're working for a better solution)") %>
  43 + </li>
  44 + </ul>
  45 + </p>
  46 +<%else%>
  47 + <h3> <%= _("Metric Results") %> </h3>
  48 + <% if @project.metrics_calculated? %>
  49 + <h3> <% _("Total Metrics") %> </h3>
  50 + <table id="total_metrics">
  51 + <% @total_metrics.each_with_index do |metric, index| %>
  52 + <tr id="tr_<%= metric.name %>" class="d<%= index % 2 %>">
  53 + <td class="metric_name"> <%= metric.name %> </td>
  54 + <td class="metric_value"> <%= metric.value %> </td>
  55 + </tr>
  56 + <% end %>
  57 + </table>
  58 +
  59 + <h3> <%= _("Statistical Metrics") %> </h3>
  60 + <% @statistical_metrics.each_key do |metric_name| %>
  61 + <div id="statistical_metrics">
  62 + <%= "#{metric_name}_average: #{@statistical_metrics[metric_name]["average"]}" %>
  63 + <ul>
  64 + <% @statistical_metrics[metric_name].each do |stat_name, stat_value| %>
  65 + <% if stat_name != "average" %>
  66 + <li>
  67 + <%= "#{metric_name}_#{stat_name}: #{stat_value}" %>
  68 + </li>
  69 + <% end %>
  70 + <% end %>
  71 + </ul>
  72 + </div>
  73 + <% end %>
  74 +
  75 + <% else %>
  76 + <div id="progress_message">
  77 + <%= _("Wait a moment while the metrics are calculated.<br/>
  78 + Reload the page manually in a few moment. ") %>
  79 + </div>
  80 + <% end %>
  81 +<% end %>
... ...
test/functional/plugins_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,68 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +require 'plugins_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class PluginsController; def rescue_action(e) raise e end; end
  6 +
  7 +class PluginsControllerTest < Test::Unit::TestCase
  8 +
  9 + all_fixtures
  10 + def setup
  11 + @controller = PluginsController.new
  12 + @request = ActionController::TestRequest.new
  13 + @request.stubs(:ssl?).returns(true)
  14 + @response = ActionController::TestResponse.new
  15 + @environment = Environment.default
  16 + login_as(create_admin_user(@environment))
  17 + end
  18 + attr_reader :environment
  19 +
  20 + def test_local_files_reference
  21 + assert_local_files_reference
  22 + end
  23 +
  24 + def test_valid_xhtml
  25 + assert_valid_xhtml
  26 + end
  27 +
  28 + should 'list system active plugins' do
  29 + class Plugin1 < Noosfero::Plugin
  30 + class << self
  31 + def plugin_name
  32 + "Plugin1"
  33 + end
  34 + def plugin_description
  35 + "This plugin is from hell!"
  36 + end
  37 + end
  38 + end
  39 +
  40 + class Plugin2 < Noosfero::Plugin
  41 + class << self
  42 + def plugin_name
  43 + "Plugin2"
  44 + end
  45 + def plugin_description
  46 + "This plugin is from heaven!"
  47 + end
  48 + end
  49 + end
  50 +
  51 + Noosfero::Plugin.stubs(:all).returns([Plugin1.to_s,Plugin2.to_s])
  52 +
  53 + get :index
  54 +
  55 + assert_tag :tag => 'td', :content => /#{Plugin1.plugin_name}/
  56 + assert_tag :tag => 'td', :content => /#{Plugin1.plugin_description}/
  57 + assert_tag :tag => 'td', :content => /#{Plugin2.plugin_name}/
  58 + assert_tag :tag => 'td', :content => /#{Plugin2.plugin_description}/
  59 + end
  60 +
  61 + should 'enable or disable plugins' do
  62 + assert_not_equal ['Plugin1'], environment.enabled_plugins
  63 + post :update, :environment => { :enabled_plugins => ['Plugin1']}
  64 + environment.reload
  65 + assert_equal ['Plugin1'], environment.enabled_plugins
  66 + end
  67 +
  68 +end
... ...
test/functional/profile_controller_test.rb
... ... @@ -1113,4 +1113,18 @@ class ProfileControllerTest &lt; Test::Unit::TestCase
1113 1113 assert_equal '', @response.body
1114 1114 end
1115 1115  
  1116 + should 'display plugins tabs' do
  1117 + plugin1_tab = {:title => 'Plugin1 tab', :id => 'plugin1_tab', :content => 'Content from plugin1.'}
  1118 + plugin2_tab = {:title => 'Plugin2 tab', :id => 'plugin2_tab', :content => 'Content from plugin2.'}
  1119 + tabs = [plugin1_tab, plugin2_tab]
  1120 + plugins = mock()
  1121 + plugins.stubs(:map).with(:profile_tabs).returns(tabs)
  1122 + Noosfero::Plugin::Manager.stubs(:new).returns(plugins)
  1123 +
  1124 + get :index, :profile => profile.identifier
  1125 +
  1126 + assert_tag :tag => 'a', :content => /#{plugin1_tab[:title]}/, :attributes => {:href => /#{plugin1_tab[:id]}/}
  1127 + assert_tag :tag => 'div', :content => /#{plugin1_tab[:content]}/, :attributes => {:id => /#{plugin1_tab[:id]}/}
  1128 + end
  1129 +
1116 1130 end
... ...
test/functional/profile_editor_controller_test.rb
... ... @@ -858,4 +858,18 @@ class ProfileEditorControllerTest &lt; Test::Unit::TestCase
858 858 end
859 859 end
860 860  
  861 + should 'display plugins buttons on the control panel' do
  862 + plugin1_button = {:title => "Plugin1 button", :icon => 'plugin1_icon', :url => 'plugin1_url'}
  863 + plugin2_button = {:title => "Plugin2 button", :icon => 'plugin2_icon', :url => 'plugin2_url'}
  864 + buttons = [plugin1_button, plugin2_button]
  865 + plugins = mock()
  866 + plugins.stubs(:map).with(:control_panel_buttons).returns(buttons)
  867 + Noosfero::Plugin::Manager.stubs(:new).returns(plugins)
  868 +
  869 + get :index, :profile => profile.identifier
  870 +
  871 + assert_tag :tag => 'a', :content => plugin1_button[:title], :attributes => {:class => /#{plugin1_button[:icon]}/, :href => /#{plugin1_button[:url]}/}
  872 + assert_tag :tag => 'a', :content => plugin2_button[:title], :attributes => {:class => /#{plugin2_button[:icon]}/, :href => /#{plugin2_button[:url]}/}
  873 + end
  874 +
861 875 end
... ...
test/unit/manager_test.rb 0 → 100644
... ... @@ -0,0 +1,58 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class ManagerTest < Test::Unit::TestCase
  4 +
  5 + def setup
  6 + @environment = Environment.default
  7 + @controller = mock()
  8 + @controller.stubs(:profile).returns()
  9 + @controller.stubs(:request).returns()
  10 + @controller.stubs(:response).returns()
  11 + @controller.stubs(:environment).returns(@environment)
  12 + @controller.stubs(:params).returns()
  13 + @manager = Noosfero::Plugin::Manager.new(@controller)
  14 + end
  15 + attr_reader :environment
  16 + attr_reader :manager
  17 +
  18 + should 'return the intersection between environment\'s enabled plugins and system available plugins' do
  19 + class Plugin1 < Noosfero::Plugin; end;
  20 + class Plugin2 < Noosfero::Plugin; end;
  21 + class Plugin3 < Noosfero::Plugin; end;
  22 + class Plugin4 < Noosfero::Plugin; end;
  23 + environment.stubs(:enabled_plugins).returns([Plugin1.to_s, Plugin2.to_s, Plugin4.to_s])
  24 + Noosfero::Plugin.stubs(:all).returns([Plugin1.to_s, Plugin3.to_s, Plugin4.to_s])
  25 + plugins = manager.enabled_plugins.map { |instance| instance.class.to_s }
  26 + assert_equal [Plugin1.to_s, Plugin4.to_s], plugins
  27 + end
  28 +
  29 + should 'map events to registered plugins' do
  30 +
  31 + class Noosfero::Plugin
  32 + def random_event
  33 + nil
  34 + end
  35 + end
  36 +
  37 + class Plugin1 < Noosfero::Plugin
  38 + def random_event
  39 + 'Plugin 1 action.'
  40 + end
  41 + end
  42 +
  43 + class Plugin2 < Noosfero::Plugin
  44 + def random_event
  45 + 'Plugin 2 action.'
  46 + end
  47 + end
  48 +
  49 + environment.stubs(:enabled_plugins).returns([Plugin1.to_s, Plugin2.to_s])
  50 +
  51 + p1 = Plugin1.new
  52 + p2 = Plugin2.new
  53 +
  54 + assert_equal [p1.random_event, p2.random_event], manager.map(:random_event)
  55 + end
  56 +
  57 +end
  58 +
... ...
test/unit/plugin_test.rb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class PluginTest < Test::Unit::TestCase
  4 +
  5 + def setup
  6 + @environment = Environment.default
  7 + end
  8 + attr_reader :environment
  9 +
  10 + should 'keep the list of all loaded subclasses' do
  11 + class Plugin1 < Noosfero::Plugin
  12 + end
  13 +
  14 + class Plugin2 < Noosfero::Plugin
  15 + end
  16 +
  17 + assert_includes Noosfero::Plugin.all, Plugin1.to_s
  18 + assert_includes Noosfero::Plugin.all, Plugin1.to_s
  19 + end
  20 +
  21 +end
  22 +
  23 +
... ...