From 619223c680bd8fcedc16ed16ceb432223922df94 Mon Sep 17 00:00:00 2001 From: Rodrigo Souto Date: Mon, 10 Oct 2011 16:25:02 -0300 Subject: [PATCH] Bsc Plugin --- app/controllers/application.rb | 15 +++++++++++++++ app/controllers/my_profile/manage_products_controller.rb | 8 ++++++++ app/controllers/my_profile/profile_members_controller.rb | 73 +++++++++++++++++++++++++++++++++++++++++++++---------------------------- app/helpers/application_helper.rb | 20 ++++++++++++++++++-- app/helpers/catalog_helper.rb | 5 ++++- app/models/community.rb | 4 ++++ app/models/enterprise.rb | 9 +++++++++ app/models/environment.rb | 12 ++++++++++++ app/models/organization.rb | 14 +++++++++++--- app/models/person.rb | 4 ++++ app/models/product.rb | 4 ++++ app/models/profile.rb | 17 ++++++++++++----- app/views/admin_panel/index.rhtml | 3 +++ app/views/layouts/_javascript.rhtml | 2 +- app/views/manage_products/index.rhtml | 2 +- app/views/memberships/index.rhtml | 2 +- app/views/profile/_profile.rhtml | 3 ++- app/views/profile/profile_info.rjs | 2 +- app/views/profile_editor/_moderation.html.erb | 35 +++++++++++++++++++++++++++++++++++ app/views/profile_editor/_organization.rhtml | 40 +++------------------------------------- app/views/profile_editor/index.rhtml | 10 +++++----- app/views/profile_members/_find_users.rhtml | 1 - app/views/profile_members/_index_buttons.rhtml | 3 +++ app/views/profile_members/_manage_roles.html.erb | 42 ++++++++++++++++++++++++++++++++++++++++++ app/views/profile_members/add_members.rhtml | 31 ++----------------------------- app/views/profile_members/find_users.rhtml | 35 ----------------------------------- app/views/profile_members/last_admin.rhtml | 27 ++++----------------------- app/views/search/_product.rhtml | 12 +++++++++--- db/migrate/20111004184103_add_field_validated_to_enterprises.rb | 9 +++++++++ db/migrate/20111004184104_add_cnpj_to_enterprises.rb | 9 +++++++++ db/schema.rb | 4 +++- features/activate_enterprise.feature | 2 +- features/bsc.feature | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ features/last_administrator_leaving.feature | 10 +++++----- features/manage_products.feature | 6 ++++++ features/plugins.feature | 2 +- features/step_definitions/noosfero_steps.rb | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- features/unblock_button.feature | 6 +++--- lib/noosfero/plugin.rb | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- lib/noosfero/plugin/context.rb | 4 ++++ plugins/bsc/controllers/bsc_plugin_environment_controller.rb | 35 +++++++++++++++++++++++++++++++++++ plugins/bsc/controllers/bsc_plugin_myprofile_controller.rb | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/db/migrate/20110609143043_add_bsc_to_enterprise.rb | 9 +++++++++ plugins/bsc/db/migrate/20110610145112_add_bsc_fields.rb | 9 +++++++++ plugins/bsc/db/migrate/20110614183624_add_bsc_to_tasks.rb | 9 +++++++++ plugins/bsc/lib/bsc_plugin.rb | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/lib/bsc_plugin/associate_enterprise.rb | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/lib/bsc_plugin/bsc.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/lib/bsc_plugin/mailer.rb | 13 +++++++++++++ plugins/bsc/lib/ext/enterprise.rb | 11 +++++++++++ plugins/bsc/lib/ext/product.rb | 11 +++++++++++ plugins/bsc/public/images/manage-bsc-enterprises-icon.png | Bin 0 -> 978 bytes plugins/bsc/public/images/manage-bsc-enterprises.gif | Bin 0 -> 2096 bytes plugins/bsc/public/images/manage-bsc-enterprises.png | Bin 0 -> 3931 bytes plugins/bsc/public/images/manage-bsc-enterprises.svg | 1309 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/public/images/transfer-ownership.png | Bin 0 -> 4090 bytes plugins/bsc/public/images/transfer-ownership.svg | 1965 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/public/style.css | 12 ++++++++++++ plugins/bsc/test/functional/bsc_plugin_environment_controller_test.rb | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/test/functional/bsc_plugin_myprofile_controller_test.rb | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/test/unit/bsc_plugin/associate_enterprise_test.rb | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/test/unit/bsc_plugin/bsc_test.rb | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/test/unit/bsc_plugin/enterprise_test.rb | 26 ++++++++++++++++++++++++++ plugins/bsc/test/unit/bsc_plugin/product_test.rb | 14 ++++++++++++++ plugins/bsc/test/unit/bsc_plugin_test.rb | 36 ++++++++++++++++++++++++++++++++++++ plugins/bsc/views/bsc_plugin/mailer/admin_notification.html.erb | 1 + plugins/bsc/views/bsc_plugin_environment/new.html.erb | 11 +++++++++++ plugins/bsc/views/bsc_plugin_environment/validate_enterprises.html.erb | 31 +++++++++++++++++++++++++++++++ plugins/bsc/views/bsc_plugin_myprofile/_similar_enterprises.html.erb | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/views/bsc_plugin_myprofile/create_enterprise.html.erb | 21 +++++++++++++++++++++ plugins/bsc/views/bsc_plugin_myprofile/manage_associated_enterprises.html.erb | 43 +++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/views/bsc_plugin_myprofile/transfer_ownership.html.erb | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/bsc/views/profile/_profile_tab.html.erb | 6 ++++++ plugins/bsc/views/profile_editor/bsc_plugin/_bsc.html.erb | 2 ++ plugins/bsc/views/shared/_fields.html.erb | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/mezuro/lib/mezuro_plugin.rb | 2 +- plugins/mezuro/views/profile/_project_tab.html.erb | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/mezuro/views/show.html.erb | 81 --------------------------------------------------------------------------------- public/javascripts/jquery.tokeninput.js | 793 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ public/stylesheets/token-input-facebook.css | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ public/stylesheets/token-input-mac.css | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ public/stylesheets/token-input.css | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ script/sample-profiles | 1 + test/functional/account_controller_test.rb | 22 +++++++++++++--------- test/functional/admin_panel_controller_test.rb | 16 ++++++++++++++++ test/functional/application_controller_test.rb | 65 ++++++++++++++++++++++++++++------------------------------------- test/functional/catalog_controller_test.rb | 30 +++++++++++++++++------------- test/functional/enterprise_registration_controller_test.rb | 1 - test/functional/manage_products_controller_test.rb | 12 ++++++++++++ test/functional/profile_controller_test.rb | 32 ++++++++++++++++++++++---------- test/functional/profile_members_controller_test.rb | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------ test/functional/search_controller_test.rb | 55 ++++++++++++++++++++++++++++++++++++++++++------------- test/unit/application_helper_test.rb | 34 +++++++++++----------------------- test/unit/enterprise_test.rb | 14 ++++++++++++++ test/unit/environment_test.rb | 13 +++++++++++++ test/unit/organization_test.rb | 22 ++++++++++++++++++++++ test/unit/profile_test.rb | 14 ++++++++++++++ vendor/plugins/validates_as_cnpj/LEIAME | 13 +++++++++++++ vendor/plugins/validates_as_cnpj/init.rb | 17 +++++++++++++++++ vendor/plugins/validates_as_cnpj/lib/validates_as_cnpj.rb | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/validates_as_cnpj/test/abstract_unit.rb | 7 +++++++ vendor/plugins/validates_as_cnpj/test/cnpj_test.rb | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/validates_as_cnpj/test/connection.rb | 6 ++++++ vendor/plugins/validates_as_cnpj/test/fixtures/schema.rb | 6 ++++++ 104 files changed, 6763 insertions(+), 434 deletions(-) create mode 100644 app/views/profile_editor/_moderation.html.erb delete mode 120000 app/views/profile_members/_find_users.rhtml create mode 100644 app/views/profile_members/_manage_roles.html.erb delete mode 100644 app/views/profile_members/find_users.rhtml create mode 100644 db/migrate/20111004184103_add_field_validated_to_enterprises.rb create mode 100644 db/migrate/20111004184104_add_cnpj_to_enterprises.rb create mode 100644 features/bsc.feature create mode 100644 plugins/bsc/controllers/bsc_plugin_environment_controller.rb create mode 100644 plugins/bsc/controllers/bsc_plugin_myprofile_controller.rb create mode 100644 plugins/bsc/db/migrate/20110609143043_add_bsc_to_enterprise.rb create mode 100644 plugins/bsc/db/migrate/20110610145112_add_bsc_fields.rb create mode 100644 plugins/bsc/db/migrate/20110614183624_add_bsc_to_tasks.rb create mode 100644 plugins/bsc/lib/bsc_plugin.rb create mode 100644 plugins/bsc/lib/bsc_plugin/associate_enterprise.rb create mode 100644 plugins/bsc/lib/bsc_plugin/bsc.rb create mode 100644 plugins/bsc/lib/bsc_plugin/mailer.rb create mode 100644 plugins/bsc/lib/ext/enterprise.rb create mode 100644 plugins/bsc/lib/ext/product.rb create mode 100644 plugins/bsc/public/images/manage-bsc-enterprises-icon.png create mode 100644 plugins/bsc/public/images/manage-bsc-enterprises.gif create mode 100644 plugins/bsc/public/images/manage-bsc-enterprises.png create mode 100644 plugins/bsc/public/images/manage-bsc-enterprises.svg create mode 100644 plugins/bsc/public/images/transfer-ownership.png create mode 100644 plugins/bsc/public/images/transfer-ownership.svg create mode 100644 plugins/bsc/public/style.css create mode 100644 plugins/bsc/test/functional/bsc_plugin_environment_controller_test.rb create mode 100644 plugins/bsc/test/functional/bsc_plugin_myprofile_controller_test.rb create mode 100644 plugins/bsc/test/unit/bsc_plugin/associate_enterprise_test.rb create mode 100644 plugins/bsc/test/unit/bsc_plugin/bsc_test.rb create mode 100644 plugins/bsc/test/unit/bsc_plugin/enterprise_test.rb create mode 100644 plugins/bsc/test/unit/bsc_plugin/product_test.rb create mode 100644 plugins/bsc/test/unit/bsc_plugin_test.rb create mode 100644 plugins/bsc/views/bsc_plugin/mailer/admin_notification.html.erb create mode 100644 plugins/bsc/views/bsc_plugin_environment/new.html.erb create mode 100644 plugins/bsc/views/bsc_plugin_environment/validate_enterprises.html.erb create mode 100644 plugins/bsc/views/bsc_plugin_myprofile/_similar_enterprises.html.erb create mode 100644 plugins/bsc/views/bsc_plugin_myprofile/create_enterprise.html.erb create mode 100644 plugins/bsc/views/bsc_plugin_myprofile/manage_associated_enterprises.html.erb create mode 100644 plugins/bsc/views/bsc_plugin_myprofile/transfer_ownership.html.erb create mode 100644 plugins/bsc/views/profile/_profile_tab.html.erb create mode 100644 plugins/bsc/views/profile_editor/bsc_plugin/_bsc.html.erb create mode 100644 plugins/bsc/views/shared/_fields.html.erb create mode 100644 plugins/mezuro/views/profile/_project_tab.html.erb delete mode 100644 plugins/mezuro/views/show.html.erb create mode 100644 public/javascripts/jquery.tokeninput.js create mode 100644 public/stylesheets/token-input-facebook.css create mode 100644 public/stylesheets/token-input-mac.css create mode 100644 public/stylesheets/token-input.css create mode 100755 vendor/plugins/validates_as_cnpj/LEIAME create mode 100755 vendor/plugins/validates_as_cnpj/init.rb create mode 100755 vendor/plugins/validates_as_cnpj/lib/validates_as_cnpj.rb create mode 100755 vendor/plugins/validates_as_cnpj/test/abstract_unit.rb create mode 100755 vendor/plugins/validates_as_cnpj/test/cnpj_test.rb create mode 100755 vendor/plugins/validates_as_cnpj/test/connection.rb create mode 100755 vendor/plugins/validates_as_cnpj/test/fixtures/schema.rb diff --git a/app/controllers/application.rb b/app/controllers/application.rb index cd6ba4d..49574d5 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -128,6 +128,21 @@ class ApplicationController < ActionController::Base def init_noosfero_plugins @plugins = Noosfero::Plugin::Manager.new(self) + @plugins.enabled_plugins.map(&:class).each do |plugin| + prepend_view_path(plugin.view_path) + end + init_noosfero_plugins_controller_filters + end + + # This is a generic method that initialize any possible filter defined by a + # plugin to the current controller being initialized. + def init_noosfero_plugins_controller_filters + @plugins.enabled_plugins.each do |plugin| + plugin.send(self.class.name.underscore + '_filters').each do |plugin_filter| + self.class.send(plugin_filter[:type], plugin.class.name.underscore + '_' + plugin_filter[:method_name], (plugin_filter[:options] || {})) + self.class.send(:define_method, plugin.class.name.underscore + '_' + plugin_filter[:method_name], plugin_filter[:block]) + end + end end def load_terminology diff --git a/app/controllers/my_profile/manage_products_controller.rb b/app/controllers/my_profile/manage_products_controller.rb index 099fbaf..d031db1 100644 --- a/app/controllers/my_profile/manage_products_controller.rb +++ b/app/controllers/my_profile/manage_products_controller.rb @@ -4,6 +4,7 @@ class ManageProductsController < ApplicationController protect 'manage_products', :profile, :except => [:show] before_filter :check_environment_feature before_filter :login_required, :except => [:show] + before_filter :create_product?, :only => [:new] protected @@ -14,6 +15,13 @@ class ManageProductsController < ApplicationController end end + def create_product? + if !profile.create_product? + render_access_denied + return + end + end + public def index diff --git a/app/controllers/my_profile/profile_members_controller.rb b/app/controllers/my_profile/profile_members_controller.rb index 758987b..7695d80 100644 --- a/app/controllers/my_profile/profile_members_controller.rb +++ b/app/controllers/my_profile/profile_members_controller.rb @@ -1,6 +1,5 @@ class ProfileMembersController < MyProfileController protect 'manage_memberships', :profile - no_design_blocks def index @members = profile.members @@ -15,29 +14,23 @@ class ProfileMembersController < MyProfileController rescue ActiveRecord::RecordNotFound @person = nil end - if !params[:confirmation] && @person && @person.is_last_admin_leaving?(profile, @roles) - redirect_to :action => :last_admin, :roles => params[:roles], :person => @person - else - if @person && @person.define_roles(@roles, profile) + + if @person + if@person.is_last_admin_leaving?(profile, @roles) + redirect_to :action => :last_admin + elsif @person.define_roles(@roles, profile) session[:notice] = _('Roles successfuly updated') + redirect_to :controller => 'profile_editor' else session[:notice] = _('Couldn\'t change the roles') - end - if params[:confirmation] - redirect_to profile.url - else - redirect_to :action => :index + redirect_to :action => 'index' end end end def last_admin - @person = params[:person] - @roles = params[:roles] || [] - @members = profile.members.select {|member| !profile.admins.include?(member)} - @title = _('Current admins') - @collection = :profile_admins - @remove_action = {:action => 'remove_admin'} + @roles = [Profile::Roles.admin(environment.id)] + @pre_population = [].to_json end def add_role @@ -90,6 +83,7 @@ class ProfileMembersController < MyProfileController end def add_members + @roles = Profile::Roles.organization_member_roles(environment.id) end def add_member @@ -122,19 +116,42 @@ class ProfileMembersController < MyProfileController render :layout => false end - def find_users - if !params[:query] || params[:query].length <= 2 - @users_found = [] - elsif params[:scope] == 'all_users' - @users_found = Person.find_by_contents(params[:query] + '*').select {|user| !profile.members.include?(user)} - @button_alt = _('Add member') - @add_action = {:action => 'add_member'} - elsif params[:scope] == 'new_admins' - @users_found = Person.find_by_contents(params[:query] + '*').select {|user| profile.members.include?(user) && !profile.admins.include?(user)} - @button_alt = _('Add member') - @add_action = {:action => 'add_admin'} + def search_user + role = Role.find(params[:role]) + render :text => environment.people.find(:all, :conditions => ['LOWER(name) LIKE ? OR LOWER(identifier) LIKE ?', "%#{params['q_'+role.key]}%", "%#{params['q_'+role.key]}%"]). + select { |person| !profile.members_by_role(role).include?(person) }. + map {|person| {:id => person.id, :name => person.name} }. + to_json + end + + def save_associations + error = false + roles = Profile::Roles.organization_member_roles(environment.id) + roles.select { |role| params['q_'+role.key] }.each do |role| + people = [Person.find(params['q_'+role.key].split(','))].flatten + to_remove = profile.members_by_role(role) - people + to_add = people - profile.members_by_role(role) + + begin + to_remove.each { |person| profile.disaffiliate(person, role) } + to_add.each { |person| profile.affiliate(person, role) } + rescue Exception => ex + logger.info ex + error = true + end + end + + if error + session[:notice] = _('The members list couldn\'t be updated. Please contact the administrator.') + redirect_to :action => 'add_members' + else + if profile.admins.blank? && !params[:last_admin] + redirect_to :action => 'last_admin' + else + session[:notice] = _('The members list was updated.') + redirect_to :controller => 'profile_editor' + end end - render :layout => false end def send_mail diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 13ca0b7..b813cde 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -298,6 +298,17 @@ module ApplicationHelper partial_for_task_class(klass.superclass, action) end + def view_for_profile_actions(klass) + raise ArgumentError, 'No profile actions view for this class.' if klass.nil? + + name = klass.name.underscore + VIEW_EXTENSIONS.each do |ext| + return "blocks/profile_info_actions/"+name+ext if File.exists?(File.join(RAILS_ROOT, 'app', 'views', 'blocks', 'profile_info_actions', name+ext)) + end + + view_for_profile_actions(klass.superclass) + end + def user @controller.send(:user) end @@ -970,7 +981,8 @@ module ApplicationHelper 'lightbox', 'colorpicker', pngfix_stylesheet_path, - ] + ] + + tokeninput_stylesheets end # DEPRECATED. Do not use this· @@ -982,6 +994,10 @@ module ApplicationHelper 'iepngfix/iepngfix.css' end + def tokeninput_stylesheets + ['token-input', 'token-input-facebook', 'token-input-mac'] + end + def noosfero_layout_features render :file => 'shared/noosfero_layout_features' end @@ -1127,7 +1143,7 @@ module ApplicationHelper def manage_enterprises if user && !user.enterprises.empty? enterprises_link = user.enterprises.map do |enterprise| - link_to(content_tag('strong', [_('Manage %s') % enterprise.short_name(25)]), @environment.top_url + "/myprofile/#{enterprise.identifier}", :class => "icon-menu-enterprise", :title => [_('Manage %s') % enterprise.short_name]) + link_to(content_tag('strong', [_('Manage %s') % enterprise.short_name(25)]), @environment.top_url + "/myprofile/#{enterprise.identifier}", :class => "icon-menu-"+enterprise.class.identification.underscore, :title => [_('Manage %s') % enterprise.short_name]) end render :partial => 'shared/manage_enterprises', :locals => {:enterprises_link => enterprises_link} end diff --git a/app/helpers/catalog_helper.rb b/app/helpers/catalog_helper.rb index aa2ca7b..5134026 100644 --- a/app/helpers/catalog_helper.rb +++ b/app/helpers/catalog_helper.rb @@ -6,14 +6,17 @@ include ManageProductsHelper def display_products_list(profile, products) data = '' extra_content = [] + extra_content_list = [] products.each { |product| extra_content = @plugins.map(:catalog_item_extras, product).collect { |content| instance_eval(&content) } if @plugins + extra_content_list = @plugins.map(:catalog_list_item_extras, product).collect { |content| instance_eval(&content) } if @plugins data << content_tag('li', link_to_product(product, :class => 'product-pic', :style => 'background-image:url(%s)' % product.default_image(:portrait) ) + content_tag('h3', link_to_product(product)) + content_tag('ul', (product.price ? content_tag('li', _('Price: %s') % ( "%.2f" % product.price), :class => 'product_price') : '') + - content_tag('li', product_category_name(profile, product.product_category), :class => 'product_category') + content_tag('li', product_category_name(profile, product.product_category), :class => 'product_category') + + extra_content_list.map { |content| content_tag('li', content)}.join("\n") ) + (product.description ? content_tag('div', txt2html(product.description), diff --git a/app/models/community.rb b/app/models/community.rb index f18e2b7..3c3f7e7 100644 --- a/app/models/community.rb +++ b/app/models/community.rb @@ -75,4 +75,8 @@ class Community < Organization end end + def control_panel_settings_button + {:title => __('Community Info and settings'), :icon => 'edit-profile-group'} + end + end diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index aa07583..d7b9f96 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -147,6 +147,7 @@ class Enterprise < Organization end before_create do |enterprise| + enterprise.validated = enterprise.environment.enabled?('enterprises_are_validated_when_created') if enterprise.environment.enabled?('enterprises_are_disabled_when_created') enterprise.enabled = false end @@ -167,4 +168,12 @@ class Enterprise < Organization enable_contact_us end + def control_panel_settings_button + {:title => __('Enterprise Info and settings'), :icon => 'edit-profile-enterprise'} + end + + def create_product? + true + end + end diff --git a/app/models/environment.rb b/app/models/environment.rb index 3153c99..d5afe6e 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -117,6 +117,7 @@ class Environment < ActiveRecord::Base 'enable_organization_url_change' => _("Allow organizations to change their URL"), 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"), 'enterprises_are_disabled_when_created' => __('Enterprises are disabled when created'), + 'enterprises_are_validated_when_created' => __('Enterprises are validated when created'), 'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'), 'xmpp_chat' => _('XMPP/Jabber based chat'), 'show_zoom_button_on_article_images' => _('Show a zoom link on all article images') @@ -255,11 +256,22 @@ class Environment < ActiveRecord::Base self.settings["#{feature}_enabled".to_sym] = true end + def enable_plugin(plugin) + self.enabled_plugins += [plugin] + self.enabled_plugins.uniq! + self.save! + end + # Disables a feature identified by its name def disable(feature) self.settings["#{feature}_enabled".to_sym] = false end + def disable_plugin(plugin) + self.enabled_plugins.delete(plugin) + self.save! + end + # Tells if a feature, identified by its name, is enabled def enabled?(feature) self.settings["#{feature}_enabled".to_sym] == true diff --git a/app/models/organization.rb b/app/models/organization.rb index 49b6666..eeacd48 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -95,12 +95,12 @@ class Organization < Profile [] end - N_('Display name'); N_('Description'); N_('Contact person'); N_('Contact email'); N_('Acronym'); N_('Foundation year'); N_('Legal form'); N_('Economic activity'); N_('Management information'); N_('Validated'); N_('Tag list') - settings_items :display_name, :description, :contact_person, :contact_email, :acronym, :foundation_year, :legal_form, :economic_activity, :management_information, :validated, :cnpj + N_('Display name'); N_('Description'); N_('Contact person'); N_('Contact email'); N_('Acronym'); N_('Foundation year'); N_('Legal form'); N_('Economic activity'); N_('Management information'); N_('Tag list') + settings_items :display_name, :description, :contact_person, :contact_email, :acronym, :foundation_year, :legal_form, :economic_activity, :management_information validates_format_of :foundation_year, :with => Noosfero::Constants::INTEGER_FORMAT - validates_format_of :contact_email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda { |org| !org.contact_email.blank? }) + validates_as_cnpj :cnpj xss_terminate :only => [ :acronym, :contact_person, :contact_email, :legal_form, :economic_activity, :management_information ], :on => 'validation' @@ -149,4 +149,12 @@ class Organization < Profile false end + def members_to_json + members.map { |member| {:id => member.id, :name => member.name} }.to_json + end + + def members_by_role_to_json(role) + members_by_role(role).map { |member| {:id => member.id, :name => member.name} }.to_json + end + end diff --git a/app/models/person.rb b/app/models/person.rb index d8b48b9..4a92355 100644 --- a/app/models/person.rb +++ b/app/models/person.rb @@ -389,6 +389,10 @@ class Person < Profile leave_hash.to_json end + def control_panel_settings_button + {:title => _('Profile Info and settings'), :icon => 'edit-profile'} + end + protected def followed_by?(profile) diff --git a/app/models/product.rb b/app/models/product.rb index b16e371..44bc76c 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -149,4 +149,8 @@ class Product < ActiveRecord::Base unit.blank? ? name : "#{name} - #{unit.name.downcase}" end + def display_supplier_on_search? + true + end + end diff --git a/app/models/profile.rb b/app/models/profile.rb index fe75be5..5e181d8 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -52,8 +52,9 @@ class Profile < ActiveRecord::Base acts_as_accessible 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 ] } } - named_scope :enterprises, :conditions => "profiles.type = 'Enterprise'" - named_scope :communities, :conditions => "profiles.type = 'Community'" + #FIXME: these will work only if the subclass is already loaded + named_scope :enterprises, lambda { {:conditions => (Enterprise.send(:subclasses).map(&:name) << 'Enterprise').map { |klass| "profiles.type = '#{klass}'"}.join(" OR ")} } + named_scope :communities, lambda { {:conditions => (Community.send(:subclasses).map(&:name) << 'Community').map { |klass| "profiles.type = '#{klass}'"}.join(" OR ")} } def members Person.members_of(self) @@ -564,9 +565,7 @@ private :generate_url, :url_options if self.closed? && members_count > 0 AddMember.create!(:person => person, :organization => self) unless self.already_request_membership?(person) else - if members_count == 0 - self.affiliate(person, Profile::Roles.admin(environment.id)) - end + self.affiliate(person, Profile::Roles.admin(environment.id)) if members_count == 0 self.affiliate(person, Profile::Roles.member(environment.id)) end else @@ -817,6 +816,14 @@ private :generate_url, :url_options end end + def control_panel_settings_button + {:title => _('Profile Info and settings'), :icon => 'edit-profile'} + end + + def self.identification + name + end + protected def followed_by?(person) diff --git a/app/views/admin_panel/index.rhtml b/app/views/admin_panel/index.rhtml index 813d41a..0ee58d6 100644 --- a/app/views/admin_panel/index.rhtml +++ b/app/views/admin_panel/index.rhtml @@ -16,4 +16,7 @@ <%= link_to _('Manage Fields'), :controller => 'features', :action => 'manage_fields' %> <%= link_to _('Set Portal'), :action => 'set_portal_community' %> <%= link_to _('Terms of use'), :action => 'terms_of_use' %> + <% @plugins.map(:admin_panel_links).each do |link| %> + <%= link_to link[:title], link[:url] %> + <% end %> diff --git a/app/views/layouts/_javascript.rhtml b/app/views/layouts/_javascript.rhtml index 086a9c8..cc9e3eb 100644 --- a/app/views/layouts/_javascript.rhtml +++ b/app/views/layouts/_javascript.rhtml @@ -1 +1 @@ -<%= javascript_include_tag :defaults, 'jquery-latest.js', 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery.cookie', 'reflection', 'add-and-join', :cache => 'cache-general' %> +<%= javascript_include_tag :defaults, 'jquery-latest.js', 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery.cookie', 'reflection', 'add-and-join', 'jquery.tokeninput', :cache => 'cache-general' %> diff --git a/app/views/manage_products/index.rhtml b/app/views/manage_products/index.rhtml index 16ae385..c276294 100644 --- a/app/views/manage_products/index.rhtml +++ b/app/views/manage_products/index.rhtml @@ -25,6 +25,6 @@ <%= pagination_links @products %> <% button_bar do %> - <%= button :add, _('New product or service'), :action => 'new' %> + <%= button :add, _('New product or service'), :action => 'new' if @profile.create_product? %> <%= button :back, _('Back'), { :controller => 'profile_editor', :profile => @profile.identifier, :action => 'index' } %> <% end %> diff --git a/app/views/memberships/index.rhtml b/app/views/memberships/index.rhtml index 887089f..5a2f37e 100644 --- a/app/views/memberships/index.rhtml +++ b/app/views/memberships/index.rhtml @@ -17,7 +17,7 @@ <%= membership.name %>
<%= _('Role: %s') % rolename_for(profile, membership) %>
- <%= _('Type: %s') % getterm(membership.class.name) %>
+ <%= _('Type: %s') % getterm(membership.class.identification) %>
<%= _('Description: %s') % membership.description + '
' if membership.community? %> <%= _('Members: %s') % membership.members.size.to_s %>
<%= _('Created at: %s') % show_date(membership.created_at) unless membership.enterprise? %>
diff --git a/app/views/profile/_profile.rhtml b/app/views/profile/_profile.rhtml index ab4dfe1..388cc8d 100644 --- a/app/views/profile/_profile.rhtml +++ b/app/views/profile/_profile.rhtml @@ -1,7 +1,8 @@ - <% plugins_tabs = @plugins.map(:profile_tabs) %> + <% plugins_tabs = @plugins.map(:profile_tabs). + map { |tab| {:title => tab[:title], :id => tab[:id], :content => instance_eval(&tab[:content]), :start => tab[:title]} }%> <% tabs = plugins_tabs.select { |tab| tab[:start] } %> diff --git a/app/views/profile/profile_info.rjs b/app/views/profile/profile_info.rjs index e39b3e3..b402006 100644 --- a/app/views/profile/profile_info.rjs +++ b/app/views/profile/profile_info.rjs @@ -3,4 +3,4 @@ if !user.nil? and user.has_permission?('edit_profile', profile) else page.hide "profile-admin-url-#{@block.id}" end -page.replace_html "profile-info-options-#{@block.id}", :file => 'blocks/profile_info_actions/' + @block.owner.class.name.underscore +page.replace_html "profile-info-options-#{@block.id}", :file => view_for_profile_actions(@block.owner.class) diff --git a/app/views/profile_editor/_moderation.html.erb b/app/views/profile_editor/_moderation.html.erb new file mode 100644 index 0000000..45539a8 --- /dev/null +++ b/app/views/profile_editor/_moderation.html.erb @@ -0,0 +1,35 @@ +

<%= _('Moderation options') %>

+<% if profile.community? %> +
+ <%= _('New members must be approved:')%> +
+
+ <%= radio_button 'profile_data', 'closed', 'true', :style => 'float: left' %> +
+ <%= _('Before joining this group (a moderator has to accept the member in pending request before member can access the intranet and/or the website).') %> +
+
+
+ <%= radio_button 'profile_data', 'closed', 'false', :style => 'float: left' %> +
+ <%= _('After joining this group (a moderator can always desactivate access for users later).') %> +
+
+
+<% end %> +
+ <%= _('New articles posted by members of this group must be approved:')%> + +
+
+ <%= radio_button 'profile_data', 'moderated_articles', 'true', :style => 'float: left' %> +
+ <%= _('Before being published in this group (a moderator has to accept the article in pending request before the article be listed as a article of this group).') %> +
+
+
+ <%= radio_button 'profile_data', 'moderated_articles', 'false', :style => 'float: left' %> +
+ <%= _('After being published in this group (a moderator can always remove publicated articles later).') %> +
+
diff --git a/app/views/profile_editor/_organization.rhtml b/app/views/profile_editor/_organization.rhtml index 6676f3c..e89ba28 100644 --- a/app/views/profile_editor/_organization.rhtml +++ b/app/views/profile_editor/_organization.rhtml @@ -58,42 +58,8 @@ <% end %> - <%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => 'profile_data', :profile => @profile } %> +<%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => 'profile_data', :profile => @profile } %> - <%= labelled_check_box(_('Enable "contact us"'), 'profile_data[enable_contact_us]', "1", @profile.enable_contact_us) if @profile.enterprise? %> +<%= labelled_check_box(_('Enable "contact us"'), 'profile_data[enable_contact_us]', "1", @profile.enable_contact_us) if @profile.enterprise? %> -

<%= _('Moderation options') %>

- <% if profile.community? %> -
- <%= _('New members must be approved:')%> -
-
- <%= radio_button 'profile_data', 'closed', 'true', :style => 'float: left' %> -
- <%= _('Before joining this group (a moderator has to accept the member in pending request before member can access the intranet and/or the website).') %> -
-
-
- <%= radio_button 'profile_data', 'closed', 'false', :style => 'float: left' %> -
- <%= _('After joining this group (a moderator can always desactivate access for users later).') %> -
-
-
- <% end %> -
- <%= _('New articles posted by members of this group must be approved:')%> - -
-
- <%= radio_button 'profile_data', 'moderated_articles', 'true', :style => 'float: left' %> -
- <%= _('Before being published in this group (a moderator has to accept the article in pending request before the article be listed as a article of this group).') %> -
-
-
- <%= radio_button 'profile_data', 'moderated_articles', 'false', :style => 'float: left' %> -
- <%= _('After being published in this group (a moderator can always remove publicated articles later).') %> -
-
+<%= render :partial => 'moderation', :locals => { :profile => @profile } %> diff --git a/app/views/profile_editor/index.rhtml b/app/views/profile_editor/index.rhtml index 99b4804..4ff40f5 100644 --- a/app/views/profile_editor/index.rhtml +++ b/app/views/profile_editor/index.rhtml @@ -10,9 +10,9 @@ <% control_panel do %> - <%= control_panel_button(_('Profile Info and settings'), 'edit-profile', :controller => 'profile_editor', :action => 'edit') if profile.person? %> - <%= control_panel_button(__('Community Info and settings'), 'edit-profile-group', :controller => 'profile_editor', :action => 'edit') if profile.community? %> - <%= control_panel_button(__('Enterprise Info and settings'), 'edit-profile-enterprise', :controller => 'profile_editor', :action => 'edit') if profile.enterprise? %> + <%= control_panel_button(profile.control_panel_settings_button[:title], + profile.control_panel_settings_button[:icon], + :controller => 'profile_editor', :action => 'edit') %> <%= control_panel_button(_('Location'), 'edit-location', :controller => 'maps', :action => 'edit_location') %> @@ -58,9 +58,9 @@ <% if profile.enterprise? %> <% if profile.enabled? %> - <%= control_panel_button(__('Disable Enterprise'), 'disable', :action => 'disable') %> + <%= control_panel_button(__('Disable'), 'disable', :action => 'disable') %> <% else %> - <%= control_panel_button(__('Enable Enterprise'), 'enable', :action => 'enable') %> + <%= control_panel_button(__('Enable'), 'enable', :action => 'enable') %> <% end %> <% end %> diff --git a/app/views/profile_members/_find_users.rhtml b/app/views/profile_members/_find_users.rhtml deleted file mode 120000 index aa4a9e4..0000000 --- a/app/views/profile_members/_find_users.rhtml +++ /dev/null @@ -1 +0,0 @@ -find_users.rhtml \ No newline at end of file diff --git a/app/views/profile_members/_index_buttons.rhtml b/app/views/profile_members/_index_buttons.rhtml index 49e1f19..c81ad88 100644 --- a/app/views/profile_members/_index_buttons.rhtml +++ b/app/views/profile_members/_index_buttons.rhtml @@ -5,4 +5,7 @@ <%= button :search, _('Invite your friends to join %s') % profile.short_name, :controller => 'invite', :action => 'select_address_book' %> <% end %> <%= button :send, _('Send e-mail to members'), :action => 'send_mail' %> + <% @plugins.map(:manage_members_extra_buttons).each do |plugin_button| %> + <%= button plugin_button[:icon], plugin_button[:title], plugin_button[:url] %> + <% end %> <% end %> diff --git a/app/views/profile_members/_manage_roles.html.erb b/app/views/profile_members/_manage_roles.html.erb new file mode 100644 index 0000000..49d756d --- /dev/null +++ b/app/views/profile_members/_manage_roles.html.erb @@ -0,0 +1,42 @@ +<% form_tag :action => 'save_associations' do %> + <% @roles.each do |role|%> + <%= content_tag('p', content_tag('b', role.name.pluralize+':'), :style => 'margin-top: 30px; margin-bottom: 0px;') %> + <%= text_field_tag('q_'+role.key, nil, :id => 'search_'+role.key) %> + <%= hidden_field_tag(:last_admin, true) if from == 'last_admin'%> + <% end %> + + <% button_bar(:style => 'margin-top: 30px;') do %> + <%= submit_button('save', _('Save'))%> + <%= button('cancel', _('Cancel'), {:controller => 'profile_editor'})%> + <% end %> +<% end %> + +<% @roles.each do |role| %> + <% search_url = url_for(:action => 'search_user', :profile => profile.identifier, :role => role.id) %> + <% @pre_population ||= profile.members_by_role_to_json(role) %> + +<% end %> + + diff --git a/app/views/profile_members/add_members.rhtml b/app/views/profile_members/add_members.rhtml index 1f7c8c8..6c33eaf 100644 --- a/app/views/profile_members/add_members.rhtml +++ b/app/views/profile_members/add_members.rhtml @@ -1,30 +1,3 @@ -

<%= _('Add members to %s') % profile.name %>

+

<%= _('Add members to %s') % profile.name %>

-<% form_remote_tag :url => {:action => 'find_users', :profile => profile.identifier, :scope => 'all_users'}, :update => 'users-list', :loading => '$("users-list").addClassName("loading")', :complete => '$("users-list").removeClassName("loading")' do %> - <%= text_field_tag('query', '', :autocomplete => 'off') %> - <%= submit_tag(_('Search')) %> -<% end %> - -<%= observe_field('query', :url => {:action => 'find_users', :profile => profile.identifier, :scope => 'all_users'}, :update => 'users-list', :frequency => 1, :with => 'query', :condition => '$("query").value.length > 2', :loading => '$("users-list").addClassName("loading")', :complete => '$("users-list").removeClassName("loading")') %> -<%= observe_field('query', :frequency => 1, :condition => '$("query").value.length <= 2', :function => '$("users-list").update($("empty-query").innerHTML)') %> - -
- <%= render :partial => 'find_users' %> -
- - - -
- <%= render :partial => 'members_list' %> -
-<%= drop_receiving_element('members-list', - :url => {:action => 'add_member', :profile => profile.identifier}, - :before => '$("tr-" + element.id).hide()', - :loading => '$("members-list").addClassName("loading")', - :update => 'members-list', - :success => '$("tr-" + element.id).hide(); $(element.id).show();', - :complete => '$("members-list").removeClassName("loading")') %> - -
+<%= render :partial => 'manage_roles', :locals => {:from => 'add_members'} %> diff --git a/app/views/profile_members/find_users.rhtml b/app/views/profile_members/find_users.rhtml deleted file mode 100644 index 2584376..0000000 --- a/app/views/profile_members/find_users.rhtml +++ /dev/null @@ -1,35 +0,0 @@ -

<%= _('Users') %>

- - - <% @users_found.each do |user| %> - - - - - <% end if @users_found %> - <% if !params[:query] || params[:query].length <= 2 %> - - - - <% end %> -
<%= _('Name') %>
-
- <%= image_tag('/images/grip-clue.png') %> - <%= profile_image(user, :icon) %> - <%= [link_to_profile(user.short_name + " (#{user.identifier})", user.identifier, :title => user.name), - (user.sex ? gettext(user.sex.capitalize) : _('Sex not informed')), - user.location.empty? ? nil : user.location ].compact.join(' — ') %> -
- <%= draggable_element(user.identifier, :revert => true) %> -
- <%= button_to_remote_without_text(:add, @button_alt, - { :loading => '$("members-list").addClassName("loading")', - :update => 'members-list', - :url => {:id => user.id, :profile => profile.identifier}.merge(@add_action), - :success => "$('tr-#{user.identifier}').hide()", - :complete => '$("members-list").removeClassName("loading")'}) %> - - -
- <%= _('You must type at least 3 characters') %> -
diff --git a/app/views/profile_members/last_admin.rhtml b/app/views/profile_members/last_admin.rhtml index db3404b..680ed60 100644 --- a/app/views/profile_members/last_admin.rhtml +++ b/app/views/profile_members/last_admin.rhtml @@ -1,27 +1,8 @@

<%= _('Last administrator leaving %s') % profile.name %>

-<% if profile.members_count > 1 %> -
- <%= _('Since you are the last administrator, you must choose at least one member to administer this community.') % profile.name %> -
+
+ <%= _('Since you are the last administrator, you must choose at least one member to administer this community.') % profile.name %> +
- <%= render :partial => 'add_admins' %> +<%= render :partial => 'manage_roles', :locals => {:from => 'last_admin'} %> - <% form_tag :action => 'update_roles', :roles => @roles, :person => @person, :confirmation => profile.admins.count > 1 do %> - <% button_bar do %> - <%= submit_button(:save, _("Leave administration and save"), :cancel => {:action => 'index'}) %> - <% end %> - <% end %> - -<% else %> - -
- <%= _('Since you are the last administrator and there is no other member in this community, the next user to join this community will assume the administrator role.') % profile.name %> -
- - <% form_tag :action => 'update_roles', :roles => @roles, :person => @person, :confirmation => true do %> - <% button_bar do %> - <%= submit_button(:ok, _("Ok, I want to leave"), :cancel => {:action => 'index'}) %> - <% end %> - <% end %> -<% end %> diff --git a/app/views/search/_product.rhtml b/app/views/search/_product.rhtml index 613bc20..265720e 100644 --- a/app/views/search/_product.rhtml +++ b/app/views/search/_product.rhtml @@ -6,6 +6,7 @@ product_item_pos += 1 %> <% extra_content = @plugins.map(:asset_product_extras, product, product.enterprise).collect { |content| instance_eval(&content) } %> +<% extra_properties = @plugins.map(:asset_product_properties, product)%>
  • <%= link_to_product product, :class => 'product-pic', :style => 'background-image:url(%s)' % product.default_image(:minor) %> @@ -14,10 +15,15 @@ product_item_pos += 1
    • <%= _('Price: %s') % (product.price ? product.price : _('Not informed')) %>
    • - <% if product.enterprise %> -
    • <%= _('Suplier: %s') % link_to_homepage(product.enterprise.name, product.enterprise.identifier) %>
    • + <% if product.enterprise && product.display_supplier_on_search? %> +
    • <%= _('Supplier: %s') % link_to_homepage(product.enterprise.name, product.enterprise.identifier) %>
    • + <% end %> + +
    • <%= _('Category:') + ' ' + link_to_product_category(product.product_category) %>
    • + + <% extra_properties.each do |property| %> +
    • <%= property[:name] + ': ' + instance_eval(&property[:content]) %>
    • <% end %> -
    • <%=_('Category:') + ' ' + link_to_product_category(product.product_category) %>
    <%= extra_content.join('\n') %> diff --git a/db/migrate/20111004184103_add_field_validated_to_enterprises.rb b/db/migrate/20111004184103_add_field_validated_to_enterprises.rb new file mode 100644 index 0000000..8286d7e --- /dev/null +++ b/db/migrate/20111004184103_add_field_validated_to_enterprises.rb @@ -0,0 +1,9 @@ +class AddFieldValidatedToEnterprises < ActiveRecord::Migration + def self.up + add_column :profiles, :validated, :boolean, :default => true + end + + def self.down + add_column :profiles, :validated + end +end diff --git a/db/migrate/20111004184104_add_cnpj_to_enterprises.rb b/db/migrate/20111004184104_add_cnpj_to_enterprises.rb new file mode 100644 index 0000000..21d1884 --- /dev/null +++ b/db/migrate/20111004184104_add_cnpj_to_enterprises.rb @@ -0,0 +1,9 @@ +class AddCnpjToEnterprises < ActiveRecord::Migration + def self.up + add_column :profiles, :cnpj, :string + end + + def self.down + remove_column :profiles, :cnpj + end +end diff --git a/db/schema.rb b/db/schema.rb index 022950d..47eb7cf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -9,7 +9,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110824192153) do +ActiveRecord::Schema.define(:version => 20111004184104) do create_table "action_tracker", :force => true do |t| t.integer "user_id" @@ -378,6 +378,8 @@ ActiveRecord::Schema.define(:version => 20110824192153) do t.datetime "updated_at" t.boolean "visible", :default => true t.integer "image_id" + t.boolean "validated", :default => true + t.string "cnpj" end add_index "profiles", ["environment_id"], :name => "index_profiles_on_environment_id" diff --git a/features/activate_enterprise.feature b/features/activate_enterprise.feature index d3842d2..3ccdae5 100644 --- a/features/activate_enterprise.feature +++ b/features/activate_enterprise.feature @@ -55,7 +55,7 @@ Feature: activate enterprise Given feature "enterprise_activation" is enabled on environment And the following enterprises | identifier | name | enabled | cnpj | - | services-provider | Services Provider | false | 00000000000000 | + | services-provider | Services Provider | false | 94.132.024/0001-48 | And I am on Joao Silva's control panel And I fill in "Enterprise activation code" with code of "Services Provider" And I press "Activate" diff --git a/features/bsc.feature b/features/bsc.feature new file mode 100644 index 0000000..2de0414 --- /dev/null +++ b/features/bsc.feature @@ -0,0 +1,164 @@ +Feature: bsc + + Background: + Given "Bsc" plugin is enabled + + Scenario: display link to bsc creation on admin panel when bsc plugin active + Given I am logged in as admin + When I am on the environment control panel + Then I should see "Create Bsc" + When "Bsc" plugin is disabled + And I am on the environment control panel + Then I should not see "Create Bsc" + + Scenario: be able to create a bsc + Given I am logged in as admin + And I am on the environment control panel + And I follow "Create Bsc" + And I fill in the following: + | Business name | Sample Bsc | + | Company name | Sample Bsc | + | profile_data_identifier | sample-identifier | + | Cnpj | 07.970.746/0001-77 | + When I press "Save" + Then there should be a profile named "Sample Bsc" + + Scenario: display a button on bsc control panel to manage associated enterprises + Given the folllowing "bsc" from "bsc_plugin" + | business_name | identifier | company_name | cnpj | + | Bsc Test | bsc-test | Bsc Test Ltda | 94.132.024/0001-48 | + And I am logged in as admin + When I am on Bsc Test's control panel + Then I should see "Manage associated enterprises" + + Scenario: display a button on bsc control panel to transfer ownership + Given the folllowing "bsc" from "bsc_plugin" + | business_name | identifier | company_name | cnpj | + | Bsc Test | bsc-test | Bsc Test Ltda | 94.132.024/0001-48 | + And I am logged in as admin + When I am on Bsc Test's control panel + Then I should see "Transfer ownership" + + Scenario: create a new enterprise already associated with a bsc + Given the following user + | login | name | + | pedro-silva | Pedro Silva | + And the folllowing "bsc" from "bsc_plugin" + | business_name | identifier | company_name | cnpj | owner | + | Bsc Test | bsc-test | Bsc Test Ltda | 94.132.024/0001-48 | pedro-silva | + And organization_approval_method is "none" on environment + And I am logged in as "pedro-silva" + And I am on Bsc Test's control panel + And I follow "Manage associated enterprises" + And I follow "Add new enterprise" + And I fill in the following: + | Name | Associated Enterprise | + | Address | associated-enterprise | + When I press "Save" + Then "Associated Enterprise" should be associated with "Bsc Test" + + Scenario: do not display "add new product" button + Given the following user + | login | name | + | pedro-silva | Pedro Silva | + And the folllowing "bsc" from "bsc_plugin" + | business_name | identifier | company_name | cnpj | owner | + | Bsc Test | bsc-test | Bsc Test Ltda | 94.132.024/0001-48 | pedro-silva | + And feature "disable_products_for_enterprises" is disabled on environment + And I am logged in as "pedro-silva" + And I am on Bsc Test's control panel + When I follow "Manage Products and Services" + Then I should not see "New product or service" + + Scenario: display bsc's enterprises' products name on the bsc catalog + Given the following user + | login | name | + | pedro-silva | Pedro Silva | + And the folllowing "bsc" from "bsc_plugin" + | business_name | identifier | company_name | cnpj | owner | + | Bsc Test | bsc-test | Bsc Test Ltda | 94.132.024/0001-48 | pedro-silva | + And the following enterprise + | identifier | name | + | sample-enterprise | Sample Enterprise | + And the following product_category + | name | + | bike | + And the following products + | owner | category | name | + | sample-enterprise | bike | Master Bike | + And "Sample Enterprise" is associated with "Bsc Test" + And I am logged in as "pedro-silva" + When I go to Bsc Test's products page + Then I should see "Master Bike" + And I should see "Sample Enterprise" + + Scenario: display enterprise name linked only if person is member of any Bsc + Given the folllowing "bsc" from "bsc_plugin" + | business_name | identifier | company_name | cnpj | + | Bsc Test | bsc-test | Bsc Test Ltda | 94.132.024/0001-48 | + | Another Bsc | another-bsc | Another Bsc Test Ltda | 07.970.746/0001-77 | + And the following enterprise + | identifier | name | + | sample-enterprise | Sample Enterprise | + And the following product_category + | name | + | bike | + And the following products + | owner | category | name | + | sample-enterprise | bike | Master Bike | + And "Sample Enterprise" is associated with "Bsc Test" + And the folllowing "bsc" from "bsc_plugin" + | business_name | identifier | company_name | cnpj | + And the following user + | login | name | + | pedro | Pedro Souto | + | maria | Maria Souto | + And pedro is member of another-bsc + And I am logged in as "pedro" + When I go to Bsc Test's products page + Then I should see "Sample Enterprise" + And I should see "Sample Enterprise" within "a.bsc-catalog-enterprise-link" + But I am logged in as "maria" + When I go to Bsc Test's products page + Then I should see "Sample Enterprise" + #TODO -> test that it's not a link + + Scenario: allow only environment administrators to delete bsc profile + Given the folllowing "bsc" from "bsc_plugin" + | business_name | identifier | company_name | cnpj | + | Bsc Test | bsc-test | Bsc Test Ltda | 94.132.024/0001-48 | + And the following user + | login | name | + | pedro | Pedro Souto | + And "Pedro Souto" is admin of "Bsc Test" + And I am logged in as "pedro" + And I am on Bsc Test's control panel + And I follow "Bsc info and settings" + When I follow "Delete profile" + Then I should see "Access denied" + And "Bsc Test" profile should exist + But I am logged in as admin + And I am on Bsc Test's control panel + And I follow "Bsc info and settings" + When I follow "Delete profile" + Then I should see "Deleting profile Bsc Test" + And I follow "Yes, I am sure" + Then "Bsc Test" profile should not exist + + # Like we can believe that selenium is going to work... + @selenium + Scenario: list already associated enterprises on manage associated enterprises + Given the folllowing "bsc" from "bsc_plugin" + | business_name | identifier | company_name | cnpj | + | Bsc Test | bsc-test | Bsc Test Ltda | 94.132.024/0001-48 | + And the following enterprises + | identifier | name | + | enterprise-1 | Enterprise 1 | + | enterprise-2 | Enterprise 2 | + And "Enterprise 1" is associated with "Bsc Test" + And "Enterprise 2" is associated with "Bsc Test" + And I am logged in as admin + And I am on Bsc Test's control panel + When I follow "Manage associated enterprises" + Then I should see "Enterprise 1" + And I should see "Enterprise 2" diff --git a/features/last_administrator_leaving.feature b/features/last_administrator_leaving.feature index 350a344..58f6fef 100644 --- a/features/last_administrator_leaving.feature +++ b/features/last_administrator_leaving.feature @@ -11,7 +11,7 @@ Feature: remove administrator role And the following community | name | identifier | | Nice people | nice-people | - And "Joao Silva" is a member of "Nice people" + And "Joao Silva" is admin of "Nice people" And I am logged in as "joaosilva" Scenario: the last administrator removes his administrator role and must choose the new administrator @@ -28,8 +28,8 @@ Feature: remove administrator role And I uncheck "Profile Administrator" And I uncheck "Profile Member" When I press "Save changes" - Then I should see "Since you are the last administrator and there is no other member in this community" - And I press "Ok, I want to leave" + Then I should see "Since you are the last administrator, you must choose at least one member to administer this community." + And I press "Save" And I am logged in as "mariasouza" When I go to Nice people's join page Then "Maria Souza" should be admin of "Nice people" @@ -41,8 +41,8 @@ Feature: remove administrator role And I uncheck "Profile Administrator" And I uncheck "Profile Member" When I press "Save changes" - Then I should see "Since you are the last administrator and there is no other member in this community" - And I press "Ok, I want to leave" + Then I should see "Since you are the last administrator, you must choose at least one member to administer this community." + And I press "Save" And I am logged in as "mariasouza" When I go to Nice people's join page Then "Maria Souza" should be admin of "Nice people" diff --git a/features/manage_products.feature b/features/manage_products.feature index 58a8b7a..6089ff5 100644 --- a/features/manage_products.feature +++ b/features/manage_products.feature @@ -11,6 +11,12 @@ Feature: manage products | redemoinho | joaosilva | Rede Moinho | true | And feature "disable_products_for_enterprises" is disabled on environment + Scenario: display "create new product" button + Given I am logged in as "joaosilva" + And I am on Rede Moinho's control panel + When I follow "Manage Products and Services" + Then I should see "New product or service" + Scenario: paginate public listing products and services Given the following product_category | name | diff --git a/features/plugins.feature b/features/plugins.feature index 726a01c..0ed7d1f 100644 --- a/features/plugins.feature +++ b/features/plugins.feature @@ -19,7 +19,7 @@ Feature: plugins And the following events of TestPlugin | event | body | | control_panel_buttons | lambda { {:title => 'Test plugin button', :icon => '', :url => ''} } | - | profile_tabs | lambda { {:title => 'Test plugin tab', :id => 'test_plugin', :content => 'Test plugin random content'} } | + | profile_tabs | lambda { {:title => 'Test plugin tab', :id => 'test_plugin', :content => lambda {'Test plugin random content'} } } | Scenario: a user must see the plugin\'s button in the control panel if the plugin is enabled Given plugin TestPlugin is enabled on environment diff --git a/features/step_definitions/noosfero_steps.rb b/features/step_definitions/noosfero_steps.rb index b5a6ac7..573d70b 100644 --- a/features/step_definitions/noosfero_steps.rb +++ b/features/step_definitions/noosfero_steps.rb @@ -36,6 +36,36 @@ Given /^the following (community|communities|enterprises?|organizations?)$/ do | end end +Given /^"([^\"]*)" is associated with "([^\"]*)"$/ do |enterprise, bsc| + enterprise = Enterprise.find_by_name(enterprise) || Enterprise[enterprise] + bsc = BscPlugin::Bsc.find_by_name(bsc) || BscPlugin::Bsc[bsc] + + bsc.enterprises << enterprise +end + +Then /^"([^\"]*)" should be associated with "([^\"]*)"$/ do |enterprise, bsc| + enterprise = Enterprise.find_by_name(enterprise) || Enterprise[enterprise] + bsc = BscPlugin::Bsc.find_by_name(bsc) || BscPlugin::Bsc[bsc] + + bsc.enterprises.should include(enterprise) +end + +Given /^the folllowing "([^\"]*)" from "([^\"]*)"$/ do |kind, plugin, table| + klass = (plugin.camelize+'::'+kind.singularize.camelize).constantize + table.hashes.each do |row| + owner = row.delete("owner") + domain = row.delete("domain") + organization = klass.create!(row) + if owner + organization.add_admin(Profile[owner]) + end + if domain + d = Domain.new :name => domain, :owner => organization + d.save(false) + end + end +end + Given /^the following blocks$/ do |table| table.hashes.map{|item| item.dup}.each do |item| klass = item.delete('type') @@ -298,12 +328,12 @@ Given /^"(.+)" is friend of "(.+)"$/ do |person, friend| Person[person].add_friend(Person[friend]) end -Given /^(.+) is blocked$/ do |enterprise_name| +Given /^enterprise "([^\"]*)" is blocked$/ do |enterprise_name| enterprise = Enterprise.find_by_name(enterprise_name) enterprise.block end -Given /^(.+) is disabled$/ do |enterprise_name| +Given /^enterprise "([^\"]*)" is disabled$/ do |enterprise_name| enterprise = Enterprise.find_by_name(enterprise_name) enterprise.enabled = false enterprise.save @@ -428,3 +458,33 @@ Then /^I should receive an e-mail on (.*)$/ do |address| last_mail = ActionMailer::Base.deliveries.last last_mail['to'].to_s.should == address end + +Given /^"([^\"]*)" plugin is (enabled|disabled)$/ do |plugin_name, status| + environment = Environment.default + environment.send(status.chop + '_plugin', plugin_name+'Plugin') +end + +Then /^there should be an? (.+) named "([^\"]*)"$/ do |klass_name, profile_name| + klass = klass_name.camelize.constantize + klass.find_by_name(profile_name).nil?.should be_false +end + +Then /^"([^\"]*)" profile should exist$/ do |profile_selector| + profile = nil + begin + profile = Profile.find_by_name(profile_selector) + profile.nil?.should be_false + rescue + profile.nil?.should be_false + end +end + +Then /^"([^\"]*)" profile should not exist$/ do |profile_selector| + profile = nil + begin + profile = Profile.find_by_name(profile_selector) + profile.nil?.should be_true + rescue + profile.nil?.should be_true + end +end diff --git a/features/unblock_button.feature b/features/unblock_button.feature index 33c6f08..f4c5ce6 100644 --- a/features/unblock_button.feature +++ b/features/unblock_button.feature @@ -10,11 +10,11 @@ Feature: unblock button And the following blocks | owner | type | | sample-enterprise | DisabledEnterpriseMessageBlock | - And Sample Enterprise is disabled + And enterprise "Sample Enterprise" is disabled Scenario: the environment administrator unblocks a blocked enterprise Given I am logged in as admin - And Sample Enterprise is blocked + And enterprise "Sample Enterprise" is blocked And I am on Sample Enterprise's homepage When I follow "Unblock" Then I should not see "Unblock" @@ -24,7 +24,7 @@ Feature: unblock button | login | name | | joaosilva | Joao Silva | And I am logged in as "joaosilva" - And Sample Enterprise is blocked + And enterprise "Sample Enterprise" is blocked When I am on Sample Enterprise's homepage Then I should not see "Unblock" diff --git a/lib/noosfero/plugin.rb b/lib/noosfero/plugin.rb index e268178..16d6bb3 100644 --- a/lib/noosfero/plugin.rb +++ b/lib/noosfero/plugin.rb @@ -40,7 +40,11 @@ class Noosfero::Plugin end def root_path - Rails.root+'/plugins/'+public_name + File.join(RAILS_ROOT, 'plugins', public_name) + end + + def view_path + File.join(root_path,'views') end # Here the developer should specify the meta-informations that the plugin can @@ -61,11 +65,6 @@ class Noosfero::Plugin end end - def expanded_template(file_path, locals = {}) - views_path = "#{RAILS_ROOT}/plugins/#{self.class.public_name}/views" - ERB.new(File.read("#{views_path}/#{file_path}")).result(binding) - end - # Here the developer may specify the events to which the plugins can # register and must return true or false. The default value must be false. @@ -92,7 +91,7 @@ class Noosfero::Plugin # returns = { :title => title, :id => id, :content => content, :start => start } # title = name that will be displayed. # id = div id. - # content = content of the tab (use expanded_template method to import content from another file). + # content = lambda block that creates a html code. # start = boolean that specifies if the tab must come before noosfero tabs (optional). def profile_tabs nil @@ -104,6 +103,12 @@ class Noosfero::Plugin nil end + # -> Adds content to calalog list item + # returns = lambda block that creates a html code + def catalog_list_item_extras(item) + nil + end + # -> Adds content to products info # returns = lambda block that creates a html code def product_info_extras(product) @@ -116,13 +121,21 @@ class Noosfero::Plugin nil end + # -> Adds a property to the product on asset products + # returns = {:name => name, :content => content} + # name = Name of the property + # content = lambda block that creates an html + def asset_product_properties(product) + nil + end + # -> Adds content to the beginning of the page # returns = lambda block that creates html code or raw rhtml/html.erb def body_beginning nil end - # -> Add plugins' javascript files to application + # -> Adds plugins' javascript files to application # returns = ['example1.js', 'javascripts/example2.js', 'example3.js'] def js_files [] @@ -140,4 +153,43 @@ class Noosfero::Plugin raw_content end + # -> Adds links to the admin panel + # returns = {:title => title, :url => url} + # title = name that will be displayed in the link + # url = url or route to which the link will redirect to. + def admin_panel_links + nil + end + + # -> Adds buttons to manage members page + # returns = { :title => title, :icon => icon, :url => url } + # title = name that will be displayed. + # icon = css class name (for customized icons include them in a css file). + # url = url or route to which the button will redirect. + def manage_members_extra_buttons + nil + end + + # This is a generic hotspot for all controllers on Noosfero. + # If any plugin wants to define filters to run on any controller, the name of + # the hotspot must be in the following form: _filters. + # Example: for ProfileController the hotspot is profile_controller_filters + # + # -> Adds a filter to a controller + # returns = { :type => type, + # :method_name => method_name, + # :options => {:opt1 => opt1, :opt2 => opt2}, + # :block => Proc or lambda block} + # type = 'before_filter' or 'after_filter' + # method_name = The name of the filter + # option = Filter options, like :only or :except + # block = Block that the filter will call + def method_missing(method, *args, &block) + if method.to_s =~ /^(.+)_controller_filters$/ + [] + else + super + end + end + end diff --git a/lib/noosfero/plugin/context.rb b/lib/noosfero/plugin/context.rb index d79a01a..58d464d 100644 --- a/lib/noosfero/plugin/context.rb +++ b/lib/noosfero/plugin/context.rb @@ -30,4 +30,8 @@ class Noosfero::Plugin::Context @session ||= @controller.send(:session) end + def user + @user ||= @controller.send(:user) + end + end diff --git a/plugins/bsc/controllers/bsc_plugin_environment_controller.rb b/plugins/bsc/controllers/bsc_plugin_environment_controller.rb new file mode 100644 index 0000000..9b870ef --- /dev/null +++ b/plugins/bsc/controllers/bsc_plugin_environment_controller.rb @@ -0,0 +1,35 @@ +class BscPluginEnvironmentController < AdminController + + def new + @bsc = BscPlugin::Bsc.new(params[:profile_data]) + if request.post? && @bsc.valid? + @bsc.user = current_user + @bsc.save! + @bsc.add_admin(user) + session[:notice] = _('Your Bsc was created.') + redirect_to :controller => 'profile_editor', :profile => @bsc.identifier + end + end + + def save_validations + enterprises = [Enterprise.find(params[:q].split(','))].flatten + + begin + enterprises.each { |enterprise| enterprise.validated = true ; enterprise.save! } + session[:notice] = _('Enterprises validated.') + redirect_to :controller => 'admin_panel' + rescue Exception => ex + session[:notice] = _('Enterprise validations couldn\'t be saved.') + logger.info ex + redirect_to :action => 'validate_enterprises' + end + end + + def search_enterprise + render :text => Enterprise.not_validated.find(:all, :conditions => ["type <> 'BscPlugin::Bsc' AND (name LIKE ? OR identifier LIKE ?)", "%#{params[:q]}%", "%#{params[:q]}%"]). + map {|enterprise| {:id => enterprise.id, :name => enterprise.name} }. + to_json + end + +end + diff --git a/plugins/bsc/controllers/bsc_plugin_myprofile_controller.rb b/plugins/bsc/controllers/bsc_plugin_myprofile_controller.rb new file mode 100644 index 0000000..3954e8d --- /dev/null +++ b/plugins/bsc/controllers/bsc_plugin_myprofile_controller.rb @@ -0,0 +1,88 @@ +class BscPluginMyprofileController < MyProfileController + + def manage_associated_enterprises + @associated_enterprises = profile.enterprises + @pending_enterprises = profile.enterprise_requests.pending.map(&:enterprise) + end + + def search_enterprise + render :text => environment.enterprises.find(:all, :conditions => ["type <> 'BscPlugin::Bsc' AND (LOWER(name) LIKE ? OR LOWER(identifier) LIKE ?)", "%#{params[:q]}%", "%#{params[:q]}%"]). + select { |enterprise| enterprise.bsc.nil? && !profile.already_requested?(enterprise)}. + map {|enterprise| {:id => enterprise.id, :name => enterprise.name} }. + to_json + end + + def save_associations + enterprises = [Enterprise.find(params[:q].split(','))].flatten + to_remove = profile.enterprises - enterprises + to_add = enterprises - profile.enterprises + + to_remove.each do |enterprise| + enterprise.bsc = nil + enterprise.save! + profile.enterprises.delete(enterprise) + end + + to_add.each do |enterprise| + if enterprise.enabled + BscPlugin::AssociateEnterprise.create!(:requestor => user, :target => enterprise, :bsc => profile) + else + enterprise.bsc = profile + enterprise.save! + profile.enterprises << enterprise + end + end + + session[:notice] = _('This Bsc associations were saved successfully.') + begin + redirect_to :controller => 'profile_editor' + rescue Exception => ex + session[:notice] = _('This Bsc associations couldn\'t be saved.') + logger.info ex + redirect_to :action => 'manage_associated_enterprises' + end + end + + def similar_enterprises + name = params[:name] + city = params[:city] + + result = [] + if !name.blank? + enterprises = (profile.environment.enterprises - profile.enterprises).select { |enterprise| enterprise.bsc_id.nil? && enterprise.city == city && enterprise.name.downcase.include?(name.downcase)} + result = enterprises.inject(result) {|result, enterprise| result << [enterprise.id, enterprise.name]} + end + render :text => result.to_json + end + + def transfer_ownership + role = Profile::Roles.admin(profile.environment.id) + @roles = [role] + if request.post? + person = Person.find(params['q_'+role.key]) + + profile.admins.map { |admin| profile.remove_admin(admin) } + profile.add_admin(person) + + BscPlugin::Mailer.deliver_admin_notification(person, profile) + + session[:notice] = _('Enterprise ownership transferred.') + redirect_to :controller => 'profile_editor' + end + end + + def create_enterprise + @create_enterprise = CreateEnterprise.new(params[:create_enterprise]) + @create_enterprise.requestor = user + @create_enterprise.target = environment + @create_enterprise.bsc_id = profile.id + @create_enterprise.enabled = true + @create_enterprise.validated = false + if request.post? && @create_enterprise.valid? + @create_enterprise.perform + session[:notice] = _('Enterprise was created in association with %s.') % profile.name + redirect_to :controller => 'profile_editor', :profile => @create_enterprise.identifier + end + end + +end diff --git a/plugins/bsc/db/migrate/20110609143043_add_bsc_to_enterprise.rb b/plugins/bsc/db/migrate/20110609143043_add_bsc_to_enterprise.rb new file mode 100644 index 0000000..bad72c5 --- /dev/null +++ b/plugins/bsc/db/migrate/20110609143043_add_bsc_to_enterprise.rb @@ -0,0 +1,9 @@ +class AddBscToEnterprise < ActiveRecord::Migration + def self.up + add_column :profiles, :bsc_id, :integer + end + + def self.down + remove_column :profiles, :bsc_id + end +end diff --git a/plugins/bsc/db/migrate/20110610145112_add_bsc_fields.rb b/plugins/bsc/db/migrate/20110610145112_add_bsc_fields.rb new file mode 100644 index 0000000..3d5cafe --- /dev/null +++ b/plugins/bsc/db/migrate/20110610145112_add_bsc_fields.rb @@ -0,0 +1,9 @@ +class AddBscFields < ActiveRecord::Migration + def self.up + add_column :profiles, :company_name, :string + end + + def self.down + remove_column :profiles, :company_name + end +end diff --git a/plugins/bsc/db/migrate/20110614183624_add_bsc_to_tasks.rb b/plugins/bsc/db/migrate/20110614183624_add_bsc_to_tasks.rb new file mode 100644 index 0000000..be61e14 --- /dev/null +++ b/plugins/bsc/db/migrate/20110614183624_add_bsc_to_tasks.rb @@ -0,0 +1,9 @@ +class AddBscToTasks < ActiveRecord::Migration + def self.up + add_column :tasks, :bsc_id, :integer + end + + def self.down + remove_column :tasks, :bsc_id + end +end diff --git a/plugins/bsc/lib/bsc_plugin.rb b/plugins/bsc/lib/bsc_plugin.rb new file mode 100644 index 0000000..6204bbc --- /dev/null +++ b/plugins/bsc/lib/bsc_plugin.rb @@ -0,0 +1,121 @@ +require_dependency 'ext/enterprise' +require_dependency 'ext/product' + +class BscPlugin < Noosfero::Plugin + + Bsc + + def self.plugin_name + "Bsc" + end + + def self.plugin_description + _("Adds the Bsc feature") + end + + def admin_panel_links + [{:title => _('Create Bsc'), :url => {:controller => 'bsc_plugin_environment', :action => 'new'}}, + {:title => _('Validate Enterprises'), :url => {:controller => 'bsc_plugin_environment', :action => 'validate_enterprises'}} ] + end + + def control_panel_buttons + buttons = [] + buttons << {:title => _("Manage associated enterprises"), :icon => 'bsc-enterprises', :url => {:controller => 'bsc_plugin_myprofile', :action => 'manage_associated_enterprises'}} if bsc?(context.profile) + buttons << {:title => _('Transfer ownership'), :icon => 'transfer-enterprise-ownership', :url => {:controller => 'bsc_plugin_myprofile', :action => 'transfer_ownership'}} if context.profile.enterprise? + buttons + end + + def manage_members_extra_buttons + {:title => _('Transfer ownership'), :icon => '', :url => {:controller => 'bsc_plugin_myprofile', :action => 'transfer_enterprises_management'}} if context.profile.enterprise? + end + + def stylesheet? + true + end + + def catalog_list_item_extras(product) + if bsc?(context.profile) + enterprise = product.enterprise + if is_member_of_any_bsc?(context.user) + lambda {link_to(enterprise.short_name, enterprise.url, :class => 'bsc-catalog-enterprise-link')} + else + lambda {enterprise.short_name} + end + end + end + + def profile_controller_filters + if profile + special_enterprise = profile.enterprise? && !profile.validated && profile.bsc + is_member_of_any_bsc = is_member_of_any_bsc?(context.user) + block = lambda { + render_access_denied if special_enterprise && !is_member_of_any_bsc + } + + [{ :type => 'before_filter', :method_name => 'bsc_access', :block => block }] + else + [] + end + end + + def content_viewer_controller_filters + if profile + special_enterprise = profile.enterprise? && !profile.validated && profile.bsc + is_member_of_any_bsc = is_member_of_any_bsc?(context.user) + block = lambda { + render_access_denied if special_enterprise && !is_member_of_any_bsc + } + + [{ :type => 'before_filter', :method_name => 'bsc_access', :block => block }] + else + [] + end + end + + def profile_editor_controller_filters + if context.user + is_not_admin = !context.environment.admins.include?(context.user) + [{ :type => 'before_filter', + :method_name => 'bsc_destroy_access', + :options => {:only => :destroy_profile}, + :block => lambda { render_access_denied if is_not_admin } }] + else + [] + end + end + + def asset_product_properties(product) + properties = [] + properties << { :name => _('Bsc'), :content => lambda { link_to(product.bsc.name, product.bsc.url) } } if product.bsc + if product.enterprise.validated || is_member_of_any_bsc?(context.user) + content = lambda { link_to_homepage(product.enterprise.name, product.enterprise.identifier) } + else + content = lambda { product.enterprise.name } + end + properties << { :name => _('Supplier'), :content => content } + end + + def profile_tabs + if bsc?(context.profile) + { :title => _("Contact"), + :id => 'bsc-contact', + :content => lambda { render :partial => 'profile_tab' }, + :start => true } + end + end + + private + + def bsc?(profile) + profile.kind_of?(BscPlugin::Bsc) + end + + def is_member_of_any_bsc?(user) + BscPlugin::Bsc.all.any? { |bsc| bsc.members.include?(user) } + end + + def profile + context.environment.profiles.find_by_identifier(context.params[:profile]) + end + +end diff --git a/plugins/bsc/lib/bsc_plugin/associate_enterprise.rb b/plugins/bsc/lib/bsc_plugin/associate_enterprise.rb new file mode 100644 index 0000000..c0a05de --- /dev/null +++ b/plugins/bsc/lib/bsc_plugin/associate_enterprise.rb @@ -0,0 +1,49 @@ +class BscPlugin::AssociateEnterprise < Task + + alias :enterprise :target + + belongs_to :bsc, :class_name => 'BscPlugin::Bsc' + + validates_presence_of :bsc + + def title + _("BSC association") + end + + def linked_subject + {:text => bsc.name, :url => bsc.url} + end + + def information + {:message => _('%{requestor} wants to associate this enterprise with %{linked_subject}.')} + end + + def icon + src = bsc.image ? bsc.image.public_filename(:minor) : '/images/icons-app/enterprise-minor.png' + {:type => :defined_image, :src => src, :name => bsc.name} + end + + def reject_details + true + end + + def perform + bsc.enterprises << enterprise + end + + def task_finished_message + _('%{enterprise} accepted your request to associate it with %{bsc}.') % {:enterprise => enterprise.name, :bsc => bsc.name} + end + + def task_cancelled_message + message = _("%{enterprise} rejected your request to associate it with %{bsc}.") % {:enterprise => enterprise.name, :bsc => bsc.name} + if !reject_explanation.blank? + message += " " + _("Here is the reject explanation left by the administrator:\n\n%{reject_explanation}") % {:reject_explanation => reject_explanation} + end + end + + def target_notification_message + _('%{requestor} wants assoaciate %{bsc} as your BSC.') % {:requestor => requestor.name, :enterprise => enterprise.name, :bsc => bsc.name} + end + +end diff --git a/plugins/bsc/lib/bsc_plugin/bsc.rb b/plugins/bsc/lib/bsc_plugin/bsc.rb new file mode 100644 index 0000000..4991795 --- /dev/null +++ b/plugins/bsc/lib/bsc_plugin/bsc.rb @@ -0,0 +1,42 @@ +class BscPlugin::Bsc < Enterprise + + has_many :enterprises + has_many :enterprise_requests, :class_name => 'BscPlugin::AssociateEnterprise' + + validates_presence_of :nickname + validates_presence_of :company_name + validates_presence_of :cnpj + validates_uniqueness_of :nickname + validates_uniqueness_of :company_name + validates_uniqueness_of :cnpj + + before_validation do |bsc| + bsc.name = bsc.business_name || 'Sample name' + end + + def already_requested?(enterprise) + enterprise_requests.pending.map(&:enterprise).include?(enterprise) + end + + def enterprises_to_json + enterprises.map { |enterprise| {:id => enterprise.id, :name => enterprise.name} }.to_json + end + + def control_panel_settings_button + {:title => _('Bsc info and settings'), :icon => 'edit-profile-enterprise'} + end + + def products(reload_flag=false) + reload if reload_flag + enterprises.map { |enterprise| enterprise.products }.flatten + end + + def create_product? + false + end + + def self.identification + 'Bsc' + end + +end diff --git a/plugins/bsc/lib/bsc_plugin/mailer.rb b/plugins/bsc/lib/bsc_plugin/mailer.rb new file mode 100644 index 0000000..009fab9 --- /dev/null +++ b/plugins/bsc/lib/bsc_plugin/mailer.rb @@ -0,0 +1,13 @@ +class BscPlugin::Mailer < Noosfero::Plugin::MailerBase + + prepend_view_path(BscPlugin.root_path+'/views') + + def admin_notification(admin, bsc) + domain = bsc.hostname || bsc.environment.default_hostname + recipients admin.contact_email + from 'no-reply@' + domain + subject _("[%s] Bsc management transferred to you.") % bsc.name + content_type 'text/html' + body :bsc => bsc + end +end diff --git a/plugins/bsc/lib/ext/enterprise.rb b/plugins/bsc/lib/ext/enterprise.rb new file mode 100644 index 0000000..ff3f68b --- /dev/null +++ b/plugins/bsc/lib/ext/enterprise.rb @@ -0,0 +1,11 @@ +require_dependency 'enterprise' + +class Enterprise + belongs_to :bsc, :class_name => 'BscPlugin::Bsc' + FIELDS << 'bsc_id' + FIELDS << 'enabled' + FIELDS << 'validated' + + named_scope :validated, :conditions => {:validated => true} + named_scope :not_validated, :conditions => {:validated => false} +end diff --git a/plugins/bsc/lib/ext/product.rb b/plugins/bsc/lib/ext/product.rb new file mode 100644 index 0000000..bdd10fd --- /dev/null +++ b/plugins/bsc/lib/ext/product.rb @@ -0,0 +1,11 @@ +require_dependency 'product' + +class Product + def bsc + enterprise.bsc if enterprise + end + + def display_supplier_on_search? + false + end +end diff --git a/plugins/bsc/public/images/manage-bsc-enterprises-icon.png b/plugins/bsc/public/images/manage-bsc-enterprises-icon.png new file mode 100644 index 0000000..a9d92f9 Binary files /dev/null and b/plugins/bsc/public/images/manage-bsc-enterprises-icon.png differ diff --git a/plugins/bsc/public/images/manage-bsc-enterprises.gif b/plugins/bsc/public/images/manage-bsc-enterprises.gif new file mode 100644 index 0000000..53b5b1a Binary files /dev/null and b/plugins/bsc/public/images/manage-bsc-enterprises.gif differ diff --git a/plugins/bsc/public/images/manage-bsc-enterprises.png b/plugins/bsc/public/images/manage-bsc-enterprises.png new file mode 100644 index 0000000..f4bd12e Binary files /dev/null and b/plugins/bsc/public/images/manage-bsc-enterprises.png differ diff --git a/plugins/bsc/public/images/manage-bsc-enterprises.svg b/plugins/bsc/public/images/manage-bsc-enterprises.svg new file mode 100644 index 0000000..d64a42f --- /dev/null +++ b/plugins/bsc/public/images/manage-bsc-enterprises.svg @@ -0,0 +1,1309 @@ + +image/svg+xmlJakub Steinerhttp://jimmac.musichall.czhomereturngodefaultuserdirectoryTuomas Kuosmanen + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/bsc/public/images/transfer-ownership.png b/plugins/bsc/public/images/transfer-ownership.png new file mode 100644 index 0000000..a04baa5 Binary files /dev/null and b/plugins/bsc/public/images/transfer-ownership.png differ diff --git a/plugins/bsc/public/images/transfer-ownership.svg b/plugins/bsc/public/images/transfer-ownership.svg new file mode 100644 index 0000000..6d5a653 --- /dev/null +++ b/plugins/bsc/public/images/transfer-ownership.svg @@ -0,0 +1,1965 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/bsc/public/style.css b/plugins/bsc/public/style.css new file mode 100644 index 0000000..a7bdc16 --- /dev/null +++ b/plugins/bsc/public/style.css @@ -0,0 +1,12 @@ +.controller-profile_editor a.control-panel-bsc-enterprises {background-image: url(images/manage-bsc-enterprises.png)} +.controller-profile_editor .msie6 a.control-panel-bsc-enterprises {background-image: url(images/manage-bsc-enterprises.gif)} + +.controller-profile_editor a.control-panel-transfer-enterprise-ownership {background-image: url(images/transfer-ownership.png)} + +.icon-menu-bsc { + background-image: url(images/manage-bsc-enterprises-icon.png); +} + +#content .token-input-list { + margin-bottom: 30px; +} diff --git a/plugins/bsc/test/functional/bsc_plugin_environment_controller_test.rb b/plugins/bsc/test/functional/bsc_plugin_environment_controller_test.rb new file mode 100644 index 0000000..4a926e7 --- /dev/null +++ b/plugins/bsc/test/functional/bsc_plugin_environment_controller_test.rb @@ -0,0 +1,82 @@ +require File.dirname(__FILE__) + '/../../../../test/test_helper' +require File.dirname(__FILE__) + '/../../controllers/bsc_plugin_environment_controller' +require File.dirname(__FILE__) + '/../../../../app/models/uploaded_file' + +# Re-raise errors caught by the controller. +class BscPluginEnvironmentController; def rescue_action(e) raise e end; end + +class BscPluginEnvironmentControllerTest < Test::Unit::TestCase + + VALID_CNPJ = '94.132.024/0001-48' + + def setup + @controller = BscPluginEnvironmentController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + user_login = create_admin_user(Environment.default) + login_as(user_login) + @admin = User[user_login].person + e = Environment.default + e.enabled_plugins = ['BscPlugin'] + e.save! + end + + attr_accessor :admin + + should 'create a new bsc' do + assert_difference BscPlugin::Bsc, :count, 1 do + post :new, :profile_data => {:business_name => 'Sample Bsc', :identifier => 'sample-bsc', :company_name => 'Sample Bsc Ltda.', :cnpj => VALID_CNPJ} + end + + assert_redirected_to :controller => 'profile_editor', :profile => 'sample-bsc' + end + + should 'not create an invalid bsc' do + assert_difference BscPlugin::Bsc, :count, 0 do + post :new, :profile_data => {:business_name => 'Sample Bsc', :identifier => 'sample-bsc', :company_name => 'Sample Bsc Ltda.', :cnpj => '29837492304'} + end + + assert_response 200 + end + + should 'set the current user as the bsc admin' do + name = 'Sample Bsc' + post :new, :profile_data => {:business_name => name, :identifier => 'sample-bsc', :company_name => 'Sample Bsc Ltda.', :cnpj => VALID_CNPJ} + bsc = BscPlugin::Bsc.find_by_name(name) + assert_includes bsc.admins, admin + end + + should 'list correct enterprises on search' do + # Should list if: not validated AND (name matches OR identifier matches) AND not bsc + e1 = Enterprise.create!(:name => 'Sample Enterprise 1', :identifier => 'bli', :validated => false) + e2 = Enterprise.create!(:name => 'Bla', :identifier => 'sample-enterprise-6', :validated => false) + e3 = Enterprise.create!(:name => 'Blo', :identifier => 'blo', :validated => false) + e4 = BscPlugin::Bsc.create!(:business_name => "Sample Bsc", :identifier => 'sample-bsc', :company_name => 'Sample Bsc Ltda.', :cnpj => VALID_CNPJ, :validated => false) + e5 = Enterprise.create!(:name => 'Sample Enterprise 5', :identifier => 'sample-enterprise-5') + e5.validated = true + e5.save! + + get :search_enterprise, :q => 'sampl' + + assert_match /#{e1.name}/, @response.body + assert_match /#{e2.name}/, @response.body + assert_no_match /#{e3.name}/, @response.body + assert_no_match /#{e4.name}/, @response.body + assert_no_match /#{e5.name}/, @response.body + end + + should 'save validations' do + e1 = fast_create(Enterprise, :validated => false) + e2 = fast_create(Enterprise, :validated => false) + e3 = fast_create(Enterprise, :validated => false) + + post :save_validations, :q => "#{e1.id},#{e2.id}" + e1.reload + e2.reload + e3.reload + + assert e1.validated + assert e2.validated + assert !e3.validated + end +end diff --git a/plugins/bsc/test/functional/bsc_plugin_myprofile_controller_test.rb b/plugins/bsc/test/functional/bsc_plugin_myprofile_controller_test.rb new file mode 100644 index 0000000..5264a1a --- /dev/null +++ b/plugins/bsc/test/functional/bsc_plugin_myprofile_controller_test.rb @@ -0,0 +1,112 @@ +require File.dirname(__FILE__) + '/../../../../test/test_helper' +require File.dirname(__FILE__) + '/../../controllers/bsc_plugin_myprofile_controller' +require File.dirname(__FILE__) + '/../../../../app/models/uploaded_file' + +# Re-raise errors caught by the controller. +class BscPluginMyprofileController; def rescue_action(e) raise e end; end + +class BscPluginMyprofileControllerTest < Test::Unit::TestCase + + VALID_CNPJ = '94.132.024/0001-48' + + def setup + @controller = BscPluginMyprofileController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @bsc = BscPlugin::Bsc.create!({:business_name => 'Sample Bsc', :identifier => 'sample-bsc', :company_name => 'Sample Bsc Ltda.', :cnpj => VALID_CNPJ}) + @admin = create_user('admin').person + @bsc.add_admin(@admin) + login_as(@admin.user.login) + e = Environment.default + e.enabled_plugins = ['BscPlugin'] + e.save! + end + + attr_accessor :admin, :bsc + + should 'list enterprises on search' do + # Should list if match name + e1 = Enterprise.create!(:name => 'sample enterprise 1', :identifier => 'sample-enterprise-1') + # Should be case insensitive + e2 = Enterprise.create!(:name => 'SaMpLe eNtErPrIsE 2', :identifier => 'sample-enterprise-2') + # Should not list if don't match name + e3 = Enterprise.create!(:name => 'blo', :identifier => 'blo') + # Should not list if is has a bsc + e4 = Enterprise.create!(:name => 'sample enterprise 4', :identifier => 'sample-enterprise-4', :bsc => bsc) + # Should not list if is enabled + e5 = Enterprise.create!(:name => 'sample enterprise 5', :identifier => 'sample-enterprise-5', :enabled => true) + BscPlugin::AssociateEnterprise.create!(:requestor => admin, :target => e5, :bsc => bsc) + # Should search by identifier + e6 = Enterprise.create!(:name => 'Bla', :identifier => 'sample-enterprise-6') + + get :search_enterprise, :profile => bsc.identifier, :q => 'sampl' + + assert_match /#{e1.name}/, @response.body + assert_match /#{e2.name}/, @response.body + assert_no_match /#{e3.name}/, @response.body + assert_no_match /#{e4.name}/, @response.body + assert_no_match /#{e5.name}/, @response.body + assert_no_match /#{bsc.name}/, @response.body + assert_match /#{e6.name}/, @response.body + end + + should 'save associations' do + e1 = fast_create(Enterprise, :enabled => false) + e2 = fast_create(Enterprise, :enabled => false) + + post :save_associations, :profile => bsc.identifier, :q => "#{e1.id},#{e2.id}" + e1.reload + e2.reload + assert_equal e1.bsc, bsc + assert_equal e2.bsc, bsc + + post :save_associations, :profile => bsc.identifier, :q => "#{e1.id}" + e1.reload + e2.reload + assert_equal e1.bsc, bsc + assert_not_equal e2.bsc, bsc + end + + should 'create a task to the enabled enterprise instead of associating it' do + e = fast_create(Enterprise, :enabled => true) + + assert_difference BscPlugin::AssociateEnterprise, :count, 1 do + post :save_associations, :profile => bsc.identifier, :q => "#{e.id}" + bsc.reload + assert_not_includes bsc.enterprises, e + end + end + + should 'transfer ownership' do + p1 = create_user('p1').person + p2 = create_user('p2').person + p3 = create_user('p3').person + + role = Profile::Roles.admin(bsc.environment.id) + + bsc.add_admin(p1) + bsc.add_admin(p2) + + post :transfer_ownership, :profile => bsc.identifier, 'q_'+role.key => "#{p3.id}" + + assert_response :redirect + + assert_not_includes bsc.admins, p1 + assert_not_includes bsc.admins, p2 + assert_includes bsc.admins, p3 + end + + should 'create enterprise' do + assert_difference Enterprise, :count, 1 do + post :create_enterprise, :profile => bsc.identifier, :create_enterprise => {:name => 'Test Bsc', :identifier => 'test-bsc'} + end + + enterprise = Enterprise.find_by_identifier('test-bsc') + + assert_equal true, enterprise.enabled + assert_equal false, enterprise.validated + assert_equal enterprise.bsc, bsc + end + +end + diff --git a/plugins/bsc/test/unit/bsc_plugin/associate_enterprise_test.rb b/plugins/bsc/test/unit/bsc_plugin/associate_enterprise_test.rb new file mode 100644 index 0000000..908b664 --- /dev/null +++ b/plugins/bsc/test/unit/bsc_plugin/associate_enterprise_test.rb @@ -0,0 +1,53 @@ +require File.dirname(__FILE__) + '/../../../../../test/test_helper' +require File.dirname(__FILE__) + '/../../../../../app/models/uploaded_file' + +class BscPlugin::AssociateEnterpriseTest < Test::Unit::TestCase + VALID_CNPJ = '94.132.024/0001-48' + + def setup + @enterprise = fast_create(Enterprise) + @person = create_user('user').person + @bsc = BscPlugin::Bsc.create!(:business_name => 'Sample Bsc', :company_name => 'Sample Bsc Ltda.', :identifier => 'sample-bsc', :cnpj => VALID_CNPJ) + end + + attr_accessor :enterprise, :person, :bsc + + should 'associate enteprise with bsc after perform' do + task = BscPlugin::AssociateEnterprise.create!(:requestor => person, :target => enterprise, :bsc => bsc) + task.perform + bsc.reload + + assert_includes bsc.enterprises, enterprise + end + + should 'notify enterprise when some bsc create the request' do + enterprise.contact_email = 'enterprise@bsc.org' + enterprise.save! + assert_difference ActionMailer::Base.deliveries, :count, 1 do + BscPlugin::AssociateEnterprise.create!(:requestor => person, :target => enterprise, :bsc => bsc) + end + assert_includes ActionMailer::Base.deliveries.last.to, enterprise.contact_email + end + + should 'notify requestor when some enterprise reject the request' do + person.email = 'person@bsc.org' + person.save! + task = BscPlugin::AssociateEnterprise.create!(:requestor => person, :target => enterprise, :bsc => bsc) + assert_difference ActionMailer::Base.deliveries, :count, 1 do + task.cancel + end + assert_includes ActionMailer::Base.deliveries.last.to, person.contact_email + end + + should 'notify requestor when some enterprise accept the request' do + person.email = 'person@bsc.org' + person.save! + task = BscPlugin::AssociateEnterprise.create!(:requestor => person, :target => enterprise, :bsc => bsc) + assert_difference ActionMailer::Base.deliveries, :count, 1 do + task.finish + end + assert_includes ActionMailer::Base.deliveries.last.to, person.contact_email + end + +end + diff --git a/plugins/bsc/test/unit/bsc_plugin/bsc_test.rb b/plugins/bsc/test/unit/bsc_plugin/bsc_test.rb new file mode 100644 index 0000000..d81b376 --- /dev/null +++ b/plugins/bsc/test/unit/bsc_plugin/bsc_test.rb @@ -0,0 +1,86 @@ +require File.dirname(__FILE__) + '/../../../../../test/test_helper' +require File.dirname(__FILE__) + '/../../../../../app/models/uploaded_file' +require File.dirname(__FILE__) + '/../../../lib/ext/enterprise' + +class BscPlugin::BscTest < Test::Unit::TestCase + VALID_CNPJ = '94.132.024/0001-48' + + should 'validate presence of cnpj' do + bsc = BscPlugin::Bsc.new() + bsc.valid? + + assert bsc.errors.invalid?(:cnpj) + end + + should 'validate uniqueness of cnpj' do + bsc1 = BscPlugin::Bsc.create!({:business_name => 'Sample Bsc', :identifier => 'sample-bsc', :company_name => 'Sample Bsc Ltda.', :cnpj => VALID_CNPJ}) + bsc2 = BscPlugin::Bsc.new(:cnpj => VALID_CNPJ) + bsc2.valid? + assert bsc2.errors.invalid?(:cnpj) + end + + should 'have many enterprises' do + e1 = Enterprise.new(:name => 'Enterprise1', :identifier => 'enterprise1') + e2 = Enterprise.new(:name => 'Enterprise2', :identifier => 'enterprise2') + bsc = BscPlugin::Bsc.new(:business_name => 'Sample Bsc', :company_name => 'Sample Bsc Ltda.', :identifier => 'sample-bsc', :cnpj => VALID_CNPJ) + bsc.enterprises << e1 + bsc.enterprises << e2 + bsc.save! + + assert_equal e1.bsc, bsc + assert_equal e2.bsc, bsc + end + + should 'verify already requested enterprises' do + e1 = fast_create(Enterprise) + e2 = fast_create(Enterprise) + bsc = BscPlugin::Bsc.new() + task = BscPlugin::AssociateEnterprise.new(:target => e1, :bsc => bsc) + bsc.enterprise_requests.stubs(:pending).returns([task]) + + assert bsc.already_requested?(e1) + assert !bsc.already_requested?(e2) + end + + should 'return associated enterprises products' do + e1 = fast_create(Enterprise) + e2 = fast_create(Enterprise) + category = fast_create(ProductCategory) + bsc = BscPlugin::Bsc.new() + + p1 = fast_create(Product, :product_category_id => category.id) + p2 = fast_create(Product, :product_category_id => category.id) + p3 = fast_create(Product, :product_category_id => category.id) + + e1.products << p1 + e1.products << p2 + e2.products << p3 + + bsc.enterprises << e1 + bsc.enterprises << e2 + + assert_includes bsc.products, p1 + assert_includes bsc.products, p2 + assert_includes bsc.products, p3 + end + + should 'reload products' do + e = fast_create(Enterprise) + category = fast_create(ProductCategory) + bsc = BscPlugin::Bsc.create!(:business_name => 'Sample Bsc', :company_name => 'Sample Bsc', :identifier => 'sample-bsc', :cnpj => VALID_CNPJ) + p = fast_create(Product, :product_category_id => category.id) + + e.bsc = bsc + e.save! + e.products << p + + assert_equal [], bsc.products + assert_equal [p], bsc.products(true) + end + + should 'not be able to create product' do + bsc = BscPlugin::Bsc.new + assert !bsc.create_product? + end + +end diff --git a/plugins/bsc/test/unit/bsc_plugin/enterprise_test.rb b/plugins/bsc/test/unit/bsc_plugin/enterprise_test.rb new file mode 100644 index 0000000..e4cbe2f --- /dev/null +++ b/plugins/bsc/test/unit/bsc_plugin/enterprise_test.rb @@ -0,0 +1,26 @@ +require File.dirname(__FILE__) + '/../../../../../test/test_helper' +require File.dirname(__FILE__) + '/../../../../../app/models/uploaded_file' +require File.dirname(__FILE__) + '/../../../../../app/models/enterprise' +require File.dirname(__FILE__) + '/../../../lib/ext/enterprise' + +class ProductTest < Test::Unit::TestCase + VALID_CNPJ = '94.132.024/0001-48' + + should 'belongs to a bsc' do + bsc = BscPlugin::Bsc.create!({:business_name => 'Sample Bsc', :identifier => 'sample-bsc', :company_name => 'Sample Bsc Ltda.', :cnpj => VALID_CNPJ}) + enterprise = fast_create(Enterprise, :bsc_id => bsc.id) + + assert_equal bsc, enterprise.bsc + end + + should 'return correct enterprises on validated and not validated namedscopes' do + validated_enterprise = fast_create(Enterprise, :validated => true) + not_validated_enterprise = fast_create(Enterprise, :validated => false) + + assert_includes Enterprise.validated, validated_enterprise + assert_not_includes Enterprise.validated, not_validated_enterprise + assert_not_includes Enterprise.not_validated, validated_enterprise + assert_includes Enterprise.not_validated, not_validated_enterprise + end +end + diff --git a/plugins/bsc/test/unit/bsc_plugin/product_test.rb b/plugins/bsc/test/unit/bsc_plugin/product_test.rb new file mode 100644 index 0000000..81a1930 --- /dev/null +++ b/plugins/bsc/test/unit/bsc_plugin/product_test.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../../../../../test/test_helper' +require File.dirname(__FILE__) + '/../../../../../app/models/uploaded_file' + +class ProductTest < Test::Unit::TestCase + VALID_CNPJ = '94.132.024/0001-48' + + should 'return have bsc' do + bsc = BscPlugin::Bsc.create!({:business_name => 'Sample Bsc', :identifier => 'sample-bsc', :company_name => 'Sample Bsc Ltda.', :cnpj => VALID_CNPJ}) + enterprise = fast_create(Enterprise, :bsc_id => bsc.id) + product = fast_create(Product, :enterprise_id => enterprise.id) + + assert_equal bsc, product.bsc + end +end diff --git a/plugins/bsc/test/unit/bsc_plugin_test.rb b/plugins/bsc/test/unit/bsc_plugin_test.rb new file mode 100644 index 0000000..299dc57 --- /dev/null +++ b/plugins/bsc/test/unit/bsc_plugin_test.rb @@ -0,0 +1,36 @@ +require File.dirname(__FILE__) + '/../../../../test/test_helper' +require File.dirname(__FILE__) + '/../../../../app/models/uploaded_file' +require File.dirname(__FILE__) + '/../../lib/ext/enterprise' + +class BscPluginTest < Test::Unit::TestCase + + VALID_CNPJ = '94.132.024/0001-48' + + should 'add profile controller filter correctly' do + bsc_plugin = BscPlugin.new + person = fast_create(Person) + context = mock() + context.stubs(:profile).returns(person) + context.stubs(:params).returns({:profile => person.identifier}) + context.stubs(:user).returns(person) + context.stubs(:environment).returns(person.environment) + bsc_plugin.stubs(:context).returns(context) + + assert_nil bsc_plugin.profile_controller_filters.first[:block].call + assert_nil bsc_plugin.content_viewer_controller_filters.first[:block].call + + enterprise = fast_create(Enterprise, :validated => false) + enterprise.bsc = BscPlugin::Bsc.create!({:business_name => 'Sample Bsc', :identifier => 'sample-bsc', :company_name => 'Sample Bsc Ltda.', :cnpj => VALID_CNPJ}) + enterprise.save! + context.stubs(:profile).returns(enterprise) + context.stubs(:params).returns({:profile => enterprise.identifier}) + context.stubs(:environment).returns(enterprise.environment) + + assert_raise NameError do + bsc_plugin.profile_controller_filters.first[:block].call + end + assert_raise NameError do + bsc_plugin.content_viewer_controller_filters.first[:block].call + end + end +end diff --git a/plugins/bsc/views/bsc_plugin/mailer/admin_notification.html.erb b/plugins/bsc/views/bsc_plugin/mailer/admin_notification.html.erb new file mode 100644 index 0000000..cd3bfac --- /dev/null +++ b/plugins/bsc/views/bsc_plugin/mailer/admin_notification.html.erb @@ -0,0 +1 @@ +<%= _('The management of %{bsc} was transferred to you.') % {:bsc => @bsc.name}%> diff --git a/plugins/bsc/views/bsc_plugin_environment/new.html.erb b/plugins/bsc/views/bsc_plugin_environment/new.html.erb new file mode 100644 index 0000000..05154b5 --- /dev/null +++ b/plugins/bsc/views/bsc_plugin_environment/new.html.erb @@ -0,0 +1,11 @@ +<%= error_messages_for :bsc %> +

    <%= _('BSC registration') %>

    + +<% labelled_form_for :profile_data, @bsc do |f| %> + <%= render :partial => 'shared/fields', :locals => {:f => f, :profile => @bsc} %> + + <% button_bar do %> + <%= submit_button('save', _('Save')) %> + <%= button('cancel', _('Cancel'), {:controller => 'admin_panel'}) %> + <% end %> +<% end %> diff --git a/plugins/bsc/views/bsc_plugin_environment/validate_enterprises.html.erb b/plugins/bsc/views/bsc_plugin_environment/validate_enterprises.html.erb new file mode 100644 index 0000000..fc7953c --- /dev/null +++ b/plugins/bsc/views/bsc_plugin_environment/validate_enterprises.html.erb @@ -0,0 +1,31 @@ +

    <%= _('Validate enterprises') %>

    + +<% form_tag :action => 'save_validations' do %> + <%= text_field_tag(:q, nil, :id => 'search-enterprises') %> + + <% button_bar do %> + <%= submit_button('save', _('Save'))%> + <%= button('cancel', _('Cancel'), {:controller => 'admin_panel'})%> + <% end %> +<% end %> + +<%= javascript_include_tag '/plugins/bsc/jquery_tokeninput/src/jquery.tokeninput.js' %> +<% stylesheet('/plugins/bsc/jquery_tokeninput/styles/token-input.css') %> +<% search_url = url_for(:action => 'search_enterprise') %> + + diff --git a/plugins/bsc/views/bsc_plugin_myprofile/_similar_enterprises.html.erb b/plugins/bsc/views/bsc_plugin_myprofile/_similar_enterprises.html.erb new file mode 100644 index 0000000..aaa4f1e --- /dev/null +++ b/plugins/bsc/views/bsc_plugin_myprofile/_similar_enterprises.html.erb @@ -0,0 +1,48 @@ + diff --git a/plugins/bsc/views/bsc_plugin_myprofile/create_enterprise.html.erb b/plugins/bsc/views/bsc_plugin_myprofile/create_enterprise.html.erb new file mode 100644 index 0000000..f542c8e --- /dev/null +++ b/plugins/bsc/views/bsc_plugin_myprofile/create_enterprise.html.erb @@ -0,0 +1,21 @@ +<%= error_messages_for 'create_enterprise' %> + +

    <%= __('Enterprise registration') %>

    + +<%= required_fields_message %> + +<% labelled_form_for(:create_enterprise, @create_enterprise) do |f| %> + + <%= required f.text_field 'name', :onchange => "updateUrlField(this, 'create_enterprise_identifier')", :size => 40 %> + <%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => :create_enterprise, :profile => @create_enterprise } %> +

    + <%= required labelled_form_field(_('Address'), content_tag('code', environment.top_url + "/" + text_field(:create_enterprise, 'identifier', :size => 26))) %> +

    + <%= render :partial => 'similar_enterprises', :locals => {:bsc => profile}%> + + <% button_bar do %> + <%= submit_button('save', _('Save'), :cancel => {:controller => 'profile_editor', :profile => profile.identifier}) %> + <% end %> +<% end %> + + diff --git a/plugins/bsc/views/bsc_plugin_myprofile/manage_associated_enterprises.html.erb b/plugins/bsc/views/bsc_plugin_myprofile/manage_associated_enterprises.html.erb new file mode 100644 index 0000000..6b8bdf2 --- /dev/null +++ b/plugins/bsc/views/bsc_plugin_myprofile/manage_associated_enterprises.html.erb @@ -0,0 +1,43 @@ +

    <%= _('Manage associated enterprises') %>

    + +<% if !@pending_enterprises.blank? %> + <%= _('Associations awaiting approval:') %> +
      + <% @pending_enterprises.each do |enterprise| %> +
    • <%= enterprise.name %>
    • + <% end %> +
    +<% end %> + +<% form_tag :action => 'save_associations' do %> + <%= text_field_tag(:q, nil, :id => 'search-enterprises') %> + + <%= button('add', _('Add new enterprise'), {:action => 'create_enterprise'}) %> + + <% button_bar do %> + <%= submit_button('save', _('Save'))%> + <%= button('cancel', _('Cancel'), {:controller => 'profile_editor'})%> + <% end %> + +<% end %> +<%= javascript_include_tag '/plugins/bsc/jquery_tokeninput/src/jquery.tokeninput.js' %> +<% stylesheet('/plugins/bsc/jquery_tokeninput/styles/token-input.css') %> +<% search_url = url_for(:action => 'search_enterprise', :profile => profile.identifier) %> + diff --git a/plugins/bsc/views/bsc_plugin_myprofile/transfer_ownership.html.erb b/plugins/bsc/views/bsc_plugin_myprofile/transfer_ownership.html.erb new file mode 100644 index 0000000..9312f99 --- /dev/null +++ b/plugins/bsc/views/bsc_plugin_myprofile/transfer_ownership.html.erb @@ -0,0 +1,54 @@ +

    <%= _('Transfer Ownership') %>

    + +
    + <%= _('This option allows you to transfer this enterprise\'s management to another user. This action will remove all the current administrators. Be careful when confirming this procedure.') %> +
    + +<% if !profile.admins.blank? %> + <%= _('Current administrators:') %> +
      + <% profile.admins.each do |admin| %> +
    • <%= link_to(profile_image(admin, :icon, :style => 'margin-right: 3px;'), admin.url) + link_to(admin.name, admin.url, :style => 'margin-top: -3px;') %>
    • + <% end %> +
    +<% end %> + +<% form_tag do %> + <% @roles.each do |role|%> + <%= content_tag('b', _('Administrator:')) %> + <%= text_field_tag('q_'+role.key, nil, :id => 'search_'+role.key) %> + <% end %> + + <% button_bar do %> + <%= submit_button('save', _('Save'))%> + <%= button('cancel', _('Cancel'), {:controller => 'profile_editor'})%> + <% end %> +<% end %> + +<%= javascript_include_tag '/plugins/bsc/jquery_tokeninput/src/jquery.tokeninput.js' %> +<% stylesheet('/plugins/bsc/jquery_tokeninput/styles/token-input.css') %> + +<% @roles.each do |role| %> + <% search_url = url_for(:controller => 'profile_members', :action => 'search_user', :role => role.id, :profile => profile.identifier) %> + +<% end %> + + diff --git a/plugins/bsc/views/profile/_profile_tab.html.erb b/plugins/bsc/views/profile/_profile_tab.html.erb new file mode 100644 index 0000000..3fba233 --- /dev/null +++ b/plugins/bsc/views/profile/_profile_tab.html.erb @@ -0,0 +1,6 @@ +
      + <%= content_tag('li', content_tag('b', _('Contact phone: ')) + profile.contact_phone) if !profile.contact_phone.blank? %> + <%= content_tag('li', content_tag('b', _('Email: ')) + profile.contact_email) if !profile.contact_email.blank? %> + <%= content_tag('li', content_tag('b', _('Location: ')) + profile.state) if !profile.state.blank? %> + <%= content_tag('li', content_tag('b', _('Address: ')) + profile.address) if !profile.address.blank? %> +
    diff --git a/plugins/bsc/views/profile_editor/bsc_plugin/_bsc.html.erb b/plugins/bsc/views/profile_editor/bsc_plugin/_bsc.html.erb new file mode 100644 index 0000000..534f72e --- /dev/null +++ b/plugins/bsc/views/profile_editor/bsc_plugin/_bsc.html.erb @@ -0,0 +1,2 @@ +<%= render :partial => 'shared/fields', :locals => {:f => f, :profile => profile} %> +<%= render :partial => 'moderation', :locals => { :profile => profile } %> diff --git a/plugins/bsc/views/shared/_fields.html.erb b/plugins/bsc/views/shared/_fields.html.erb new file mode 100644 index 0000000..1175915 --- /dev/null +++ b/plugins/bsc/views/shared/_fields.html.erb @@ -0,0 +1,66 @@ + +<%# extend FormerPlugin::FieldHelper %> + +
    + <%= _('Basic information')%> + <%= required f.text_field(:business_name, :onchange => "updateUrlField(this, 'profile_data_identifier')") %> + <%= required f.text_field(:company_name) %> + <%= required f.text_field(:cnpj) %> + + <%#= widgets_for_form(f, :bsc_fields) %> + + + + <%= hidden_field_tag 'old_bsc_identifier', profile.identifier %> +
    + <%= content_tag('code', + environment.top_url + '/ ' + + text_field(:profile_data, :identifier, :onchange => "warn_value_change()", :size => 25) + ) + + content_tag('div', + content_tag('strong', _('WARNING!')) + ' ' + + _("You are about to change the address, and this will break external links to this bsc or to posts inside it. Do you really want to change?") + + content_tag('div', + button_to_function(:ok, _("Yes"), "confirm_change()") + ' ' + + button_to_function(:cancel, _('No'), 'no_change()') + ), + :id => 'identifier-change-confirmation', + :class => 'change-confirmation', + :style => 'display: none;' + ) + %> +
    +
    + +
    + <%= _('Contact')%> + <%= f.text_field(:contact_email) %> + <%= f.text_field(:organization_website) %> + <%= f.text_field(:contact_phone) %> +
    + +
    + <%= _('Location')%> + <%= f.text_field(:address) %> + <%= f.text_field(:zip_code) %> + <%= f.text_field(:city) %> + <%= f.text_field(:state) %> + <%= select_country(_('Country'), :profile_data, 'country', {:class => 'type-select'}) %> +
    diff --git a/plugins/mezuro/lib/mezuro_plugin.rb b/plugins/mezuro/lib/mezuro_plugin.rb index 8c317a4..225adf9 100644 --- a/plugins/mezuro/lib/mezuro_plugin.rb +++ b/plugins/mezuro/lib/mezuro_plugin.rb @@ -19,7 +19,7 @@ class MezuroPlugin < Noosfero::Plugin MezuroPlugin::Project.by_profile(context.profile).with_tab.map do |project| { :title => 'Mezuro ' + project.name, :id => 'mezuro-project-'+project.identifier, - :content => expanded_template("show.html.erb",{:current_project => project}) } + :content => lambda { render :partial => 'project_tab', :locals => {:current_project => project} } } end end end diff --git a/plugins/mezuro/views/profile/_project_tab.html.erb b/plugins/mezuro/views/profile/_project_tab.html.erb new file mode 100644 index 0000000..1f8884d --- /dev/null +++ b/plugins/mezuro/views/profile/_project_tab.html.erb @@ -0,0 +1,81 @@ +<% @project = current_project %> +<% @total_metrics = @project.total_metrics if @project != nil %> +<% @statistical_metrics = @project.statistical_metrics if @project != nil %> +<% @svn_error = @project.svn_error if (@project != nil && @project.svn_error) %> + +

    <%= @project.name %>'s Info

    + + + + + + <% if (@project.description != nil && @project.description != "" ) %> + + + + + <% end %> + + + + +
    <%= _("Name") %><%= @project.name %>
    <%= _("Description") %><%= @project.description %>
    <%= _("Repository address") %><%= @project.repository_url %>
    + +
    + + +<% if @svn_error %> +

    ERROR

    +
    + <%= @svn_error %> +
    +
    +
    +

    + <%= _("Possible causes:") %> +

      +
    • + <%= _("Server is down") %> +
    • +
    • + <% _("URL invalid, in this case create another project with the correct URL + (Sorry for the incovenience, we're working for a better solution)") %> +
    • +
    +

    +<%else%> +

    <%= _("Metric Results") %>

    + <% if @project.metrics_calculated? %> +

    <% _("Total Metrics") %>

    + + <% @total_metrics.each_with_index do |metric, index| %> + + + + + <% end %> +
    <%= metric.name %> <%= metric.value %>
    + +

    <%= _("Statistical Metrics") %>

    + <% @statistical_metrics.each_key do |metric_name| %> +
    + <%= "#{metric_name}_average: #{@statistical_metrics[metric_name]["average"]}" %> +
      + <% @statistical_metrics[metric_name].each do |stat_name, stat_value| %> + <% if stat_name != "average" %> +
    • + <%= "#{metric_name}_#{stat_name}: #{stat_value}" %> +
    • + <% end %> + <% end %> +
    +
    + <% end %> + + <% else %> +
    + <%= _("Wait a moment while the metrics are calculated.
    + Reload the page manually in a few moment. ") %> +
    + <% end %> +<% end %> diff --git a/plugins/mezuro/views/show.html.erb b/plugins/mezuro/views/show.html.erb deleted file mode 100644 index fec090e..0000000 --- a/plugins/mezuro/views/show.html.erb +++ /dev/null @@ -1,81 +0,0 @@ -<% @project = locals[:current_project] %> -<% @total_metrics = @project.total_metrics if @project != nil %> -<% @statistical_metrics = @project.statistical_metrics if @project != nil %> -<% @svn_error = @project.svn_error if (@project != nil && @project.svn_error) %> - -

    <%= @project.name %>'s Info

    - - - - - - <% if (@project.description != nil && @project.description != "" ) %> - - - - - <% end %> - - - - -
    <%= _("Name") %><%= @project.name %>
    <%= _("Description") %><%= @project.description %>
    <%= _("Repository address") %><%= @project.repository_url %>
    - -
    - - -<% if @svn_error %> -

    ERROR

    -
    - <%= @svn_error %> -
    -
    -
    -

    - <%= _("Possible causes:") %> -

      -
    • - <%= _("Server is down") %> -
    • -
    • - <% _("URL invalid, in this case create another project with the correct URL - (Sorry for the incovenience, we're working for a better solution)") %> -
    • -
    -

    -<%else%> -

    <%= _("Metric Results") %>

    - <% if @project.metrics_calculated? %> -

    <% _("Total Metrics") %>

    - - <% @total_metrics.each_with_index do |metric, index| %> - - - - - <% end %> -
    <%= metric.name %> <%= metric.value %>
    - -

    <%= _("Statistical Metrics") %>

    - <% @statistical_metrics.each_key do |metric_name| %> -
    - <%= "#{metric_name}_average: #{@statistical_metrics[metric_name]["average"]}" %> -
      - <% @statistical_metrics[metric_name].each do |stat_name, stat_value| %> - <% if stat_name != "average" %> -
    • - <%= "#{metric_name}_#{stat_name}: #{stat_value}" %> -
    • - <% end %> - <% end %> -
    -
    - <% end %> - - <% else %> -
    - <%= _("Wait a moment while the metrics are calculated.
    - Reload the page manually in a few moment. ") %> -
    - <% end %> -<% end %> diff --git a/public/javascripts/jquery.tokeninput.js b/public/javascripts/jquery.tokeninput.js new file mode 100644 index 0000000..79615f8 --- /dev/null +++ b/public/javascripts/jquery.tokeninput.js @@ -0,0 +1,793 @@ +/* + * jQuery Plugin: Tokenizing Autocomplete Text Entry + * Version 1.5.0 + * + * Copyright (c) 2009 James Smith (http://loopj.com) + * Licensed jointly under the GPL and MIT licenses, + * choose which one suits your project best! + * + */ + +(function ($) { +// Default settings +var DEFAULT_SETTINGS = { + hintText: "Type in a search term", + noResultsText: "No results", + searchingText: "Searching...", + deleteText: "×", + searchDelay: 300, + minChars: 1, + tokenLimit: null, + jsonContainer: null, + method: "GET", + contentType: "json", + queryParam: "q", + tokenDelimiter: ",", + preventDuplicates: false, + prePopulate: null, + processPrePopulate: false, + animateDropdown: true, + onResult: null, + onAdd: null, + onDelete: null, + idPrefix: "token-input-", + backspaceDeleteItem: true +}; + +// Default classes to use when theming +var DEFAULT_CLASSES = { + tokenList: "token-input-list", + token: "token-input-token", + tokenDelete: "token-input-delete-token", + selectedToken: "token-input-selected-token", + highlightedToken: "token-input-highlighted-token", + dropdown: "token-input-dropdown", + dropdownItem: "token-input-dropdown-item", + dropdownItem2: "token-input-dropdown-item2", + selectedDropdownItem: "token-input-selected-dropdown-item", + inputToken: "token-input-input-token" +}; + +// Input box position "enum" +var POSITION = { + BEFORE: 0, + AFTER: 1, + END: 2 +}; + +// Keys "enum" +var KEY = { + BACKSPACE: 8, + TAB: 9, + ENTER: 13, + ESCAPE: 27, + SPACE: 32, + PAGE_UP: 33, + PAGE_DOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + NUMPAD_ENTER: 108, + COMMA: 188 +}; + +// Additional public (exposed) methods +var methods = { + init: function(url_or_data_or_function, options) { + var settings = $.extend({}, DEFAULT_SETTINGS, options || {}); + + return this.each(function () { + $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings)); + }); + }, + clear: function() { + this.data("tokenInputObject").clear(); + return this; + }, + add: function(item) { + this.data("tokenInputObject").add(item); + return this; + }, + remove: function(item) { + this.data("tokenInputObject").remove(item); + return this; + } +} + +// Expose the .tokenInput function to jQuery as a plugin +$.fn.tokenInput = function (method) { + // Method calling and initialization logic + if(methods[method]) { + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else { + return methods.init.apply(this, arguments); + } +}; + +// TokenList class for each input +$.TokenList = function (input, url_or_data, settings) { + // + // Initialization + // + + // Configure the data source + if(typeof(url_or_data) === "string") { + // Set the url to query against + settings.url = url_or_data; + + // Make a smart guess about cross-domain if it wasn't explicitly specified + if(settings.crossDomain === undefined) { + if(settings.url.indexOf("://") === -1) { + settings.crossDomain = false; + } else { + settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]); + } + } + } else if(typeof(url_or_data) === "object") { + // Set the local data to search through + settings.local_data = url_or_data; + } + + // Build class names + if(settings.classes) { + // Use custom class names + settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes); + } else if(settings.theme) { + // Use theme-suffixed default class names + settings.classes = {}; + $.each(DEFAULT_CLASSES, function(key, value) { + settings.classes[key] = value + "-" + settings.theme; + }); + } else { + settings.classes = DEFAULT_CLASSES; + } + + + // Save the tokens + var saved_tokens = []; + + // Keep track of the number of tokens in the list + var token_count = 0; + + // Basic cache to save on db hits + var cache = new $.TokenList.Cache(); + + // Keep track of the timeout, old vals + var timeout; + var input_val; + + // Create a new text input an attach keyup events + var input_box = $("") + .css({ + outline: "none" + }) + .attr("id", settings.idPrefix + input.id) + .focus(function () { + if (settings.tokenLimit === null || settings.tokenLimit !== token_count) { + show_dropdown_hint(); + } + }) + .blur(function () { + hide_dropdown(); + $(this).val(""); + }) + .bind("keyup keydown blur update", resize_input) + .keydown(function (event) { + var previous_token; + var next_token; + + switch(event.keyCode) { + case KEY.LEFT: + case KEY.RIGHT: + case KEY.UP: + case KEY.DOWN: + if(!$(this).val()) { + previous_token = input_token.prev(); + next_token = input_token.next(); + + if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) { + // Check if there is a previous/next token and it is selected + if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) { + deselect_token($(selected_token), POSITION.BEFORE); + } else { + deselect_token($(selected_token), POSITION.AFTER); + } + } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) { + // We are moving left, select the previous token if it exists + select_token($(previous_token.get(0))); + } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) { + // We are moving right, select the next token if it exists + select_token($(next_token.get(0))); + } + } else { + var dropdown_item = null; + + if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) { + dropdown_item = $(selected_dropdown_item).next(); + } else { + dropdown_item = $(selected_dropdown_item).prev(); + } + + if(dropdown_item.length) { + select_dropdown_item(dropdown_item); + } + return false; + } + break; + + case KEY.BACKSPACE: + previous_token = input_token.prev(); + if(!$(this).val().length && settings.backspaceDeleteItem) { + if(selected_token) { + delete_token($(selected_token)); + } else if(previous_token.length) { + select_token($(previous_token.get(0))); + } + + return false; + } else if($(this).val().length === 1) { + hide_dropdown(); + } else { + // set a timeout just long enough to let this function finish. + setTimeout(function(){do_search();}, 5); + } + break; + + case KEY.TAB: + case KEY.ENTER: + case KEY.NUMPAD_ENTER: + case KEY.COMMA: + if(selected_dropdown_item) { + add_token($(selected_dropdown_item).data("tokeninput")); + return false; + } + break; + + case KEY.ESCAPE: + hide_dropdown(); + return true; + + default: + if(String.fromCharCode(event.which)) { + // set a timeout just long enough to let this function finish. + setTimeout(function(){do_search();}, 5); + } + break; + } + }); + + // Keep a reference to the original input box + var hidden_input = $(input) + .hide() + .val("") + .focus(function () { + input_box.focus(); + }) + .blur(function () { + input_box.blur(); + }); + + // Keep a reference to the selected token and dropdown item + var selected_token = null; + var selected_token_index = 0; + var selected_dropdown_item = null; + + // The list to store the token items in + var token_list = $("
      ") + .addClass(settings.classes.tokenList) + .click(function (event) { + var li = $(event.target).closest("li"); + if(li && li.get(0) && $.data(li.get(0), "tokeninput")) { + toggle_select_token(li); + } else { + // Deselect selected token + if(selected_token) { + deselect_token($(selected_token), POSITION.END); + } + + // Focus input box + input_box.focus(); + } + }) + .mouseover(function (event) { + var li = $(event.target).closest("li"); + if(li && selected_token !== this) { + li.addClass(settings.classes.highlightedToken); + } + }) + .mouseout(function (event) { + var li = $(event.target).closest("li"); + if(li && selected_token !== this) { + li.removeClass(settings.classes.highlightedToken); + } + }) + .insertBefore(hidden_input); + + // The token holding the input box + var input_token = $("
    • ") + .addClass(settings.classes.inputToken) + .appendTo(token_list) + .append(input_box); + + // The list to store the dropdown items in + var dropdown = $("
      ") + .addClass(settings.classes.dropdown) + .appendTo("body") + .hide(); + + // Magic element to help us resize the text input + var input_resizer = $("") + .insertAfter(input_box) + .css({ + position: "absolute", + top: -9999, + left: -9999, + width: "auto", + fontSize: input_box.css("fontSize"), + fontFamily: input_box.css("fontFamily"), + fontWeight: input_box.css("fontWeight"), + letterSpacing: input_box.css("letterSpacing"), + whiteSpace: "nowrap" + }); + + // Pre-populate list if items exist + hidden_input.val(""); + var li_data = settings.prePopulate || hidden_input.data("pre"); + if(settings.processPrePopulate && $.isFunction(settings.onResult)) { + li_data = settings.onResult.call(hidden_input, li_data); + } + if(li_data && li_data.length) { + $.each(li_data, function (index, value) { + insert_token(value); + checkTokenLimit(); + }); + } + + + // + // Public functions + // + + this.clear = function() { + token_list.children("li").each(function() { + if ($(this).children("input").length === 0) { + delete_token($(this)); + } + }); + } + + this.add = function(item) { + add_token(item); + } + + this.remove = function(item) { + token_list.children("li").each(function() { + if ($(this).children("input").length === 0) { + var currToken = $(this).data("tokeninput"); + var match = true; + for (var prop in item) { + if (item[prop] !== currToken[prop]) { + match = false; + break; + } + } + if (match) { + delete_token($(this)); + } + } + }); + } + + // + // Private functions + // + + function checkTokenLimit() { + if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) { + input_box.hide(); + hide_dropdown(); + return; + } else { + input_box.focus(); + } + } + + function resize_input() { + if(input_val === (input_val = input_box.val())) {return;} + + // Enter new content into resizer and resize input accordingly + var escaped = input_val.replace(/&/g, '&').replace(/\s/g,' ').replace(//g, '>'); + input_resizer.html(escaped); + input_box.width(input_resizer.width() + 30); + } + + function is_printable_character(keycode) { + return ((keycode >= 48 && keycode <= 90) || // 0-1a-z + (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * . + (keycode >= 186 && keycode <= 192) || // ; = , - . / ^ + (keycode >= 219 && keycode <= 222)); // ( \ ) ' + } + + // Inner function to a token to the list + function insert_token(item) { + var this_token = $("
    • "+ item.name +"

    • ") + .addClass(settings.classes.token) + .insertBefore(input_token); + + // The 'delete token' button + $("" + settings.deleteText + "") + .addClass(settings.classes.tokenDelete) + .appendTo(this_token) + .click(function () { + delete_token($(this).parent()); + return false; + }); + + // Store data on the token + var token_data = {"id": item.id, "name": item.name}; + $.data(this_token.get(0), "tokeninput", item); + + // Save this token for duplicate checking + saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index)); + selected_token_index++; + + // Update the hidden input + var token_ids = $.map(saved_tokens, function (el) { + return el.id; + }); + hidden_input.val(token_ids.join(settings.tokenDelimiter)); + + token_count += 1; + + return this_token; + } + + // Add a token to the token list based on user input + function add_token (item) { + var callback = settings.onAdd; + + // See if the token already exists and select it if we don't want duplicates + if(token_count > 0 && settings.preventDuplicates) { + var found_existing_token = null; + token_list.children().each(function () { + var existing_token = $(this); + var existing_data = $.data(existing_token.get(0), "tokeninput"); + if(existing_data && existing_data.id === item.id) { + found_existing_token = existing_token; + return false; + } + }); + + if(found_existing_token) { + select_token(found_existing_token); + input_token.insertAfter(found_existing_token); + input_box.focus(); + return; + } + } + + // Insert the new tokens + insert_token(item); + checkTokenLimit(); + + // Clear input box + input_box.val(""); + + // Don't show the help dropdown, they've got the idea + hide_dropdown(); + + // Execute the onAdd callback if defined + if($.isFunction(callback)) { + callback.call(hidden_input,item); + } + } + + // Select a token in the token list + function select_token (token) { + token.addClass(settings.classes.selectedToken); + selected_token = token.get(0); + + // Hide input box + input_box.val(""); + + // Hide dropdown if it is visible (eg if we clicked to select token) + hide_dropdown(); + } + + // Deselect a token in the token list + function deselect_token (token, position) { + token.removeClass(settings.classes.selectedToken); + selected_token = null; + + if(position === POSITION.BEFORE) { + input_token.insertBefore(token); + selected_token_index--; + } else if(position === POSITION.AFTER) { + input_token.insertAfter(token); + selected_token_index++; + } else { + input_token.appendTo(token_list); + selected_token_index = token_count; + } + + // Show the input box and give it focus again + input_box.focus(); + } + + // Toggle selection of a token in the token list + function toggle_select_token(token) { + var previous_selected_token = selected_token; + + if(selected_token) { + deselect_token($(selected_token), POSITION.END); + } + + if(previous_selected_token === token.get(0)) { + deselect_token(token, POSITION.END); + } else { + select_token(token); + } + } + + // Delete a token from the token list + function delete_token (token) { + // Remove the id from the saved list + var token_data = $.data(token.get(0), "tokeninput"); + var callback = settings.onDelete; + + var index = token.prevAll().length; + if(index > selected_token_index) index--; + + // Delete the token + token.remove(); + selected_token = null; + + // Show the input box and give it focus again + input_box.focus(); + + // Remove this token from the saved list + saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1)); + if(index < selected_token_index) selected_token_index--; + + // Update the hidden input + var token_ids = $.map(saved_tokens, function (el) { + return el.id; + }); + hidden_input.val(token_ids.join(settings.tokenDelimiter)); + + token_count -= 1; + + if(settings.tokenLimit !== null) { + input_box + .show() + .val("") + .focus(); + } + + // Execute the onDelete callback if defined + if($.isFunction(callback)) { + callback.call(hidden_input,token_data); + } + } + + // Hide and clear the results dropdown + function hide_dropdown () { + dropdown.hide().empty(); + selected_dropdown_item = null; + } + + function show_dropdown() { + dropdown + .css({ + position: "absolute", + top: $(token_list).offset().top + $(token_list).outerHeight(), + left: $(token_list).offset().left, + zindex: 999 + }) + .show(); + } + + function show_dropdown_searching () { + if(settings.searchingText) { + dropdown.html("

      "+settings.searchingText+"

      "); + show_dropdown(); + } + } + + function show_dropdown_hint () { + if(settings.hintText) { + dropdown.html("

      "+settings.hintText+"

      "); + show_dropdown(); + } + } + + // Highlight the query part of the search term + function highlight_term(value, term) { + return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); + } + + // Populate the results dropdown with some results + function populate_dropdown (query, results) { + if(results && results.length) { + dropdown.empty(); + var dropdown_ul = $("
        ") + .appendTo(dropdown) + .mouseover(function (event) { + select_dropdown_item($(event.target).closest("li")); + }) + .mousedown(function (event) { + add_token($(event.target).closest("li").data("tokeninput")); + return false; + }) + .hide(); + + $.each(results, function(index, value) { + var this_li = $("
      • " + highlight_term(value.name, query) + "
      • ") + .appendTo(dropdown_ul); + + if(index % 2) { + this_li.addClass(settings.classes.dropdownItem); + } else { + this_li.addClass(settings.classes.dropdownItem2); + } + + if(index === 0) { + select_dropdown_item(this_li); + } + + $.data(this_li.get(0), "tokeninput", value); + }); + + show_dropdown(); + + if(settings.animateDropdown) { + dropdown_ul.slideDown("fast"); + } else { + dropdown_ul.show(); + } + } else { + if(settings.noResultsText) { + dropdown.html("

        "+settings.noResultsText+"

        "); + show_dropdown(); + } + } + } + + // Highlight an item in the results dropdown + function select_dropdown_item (item) { + if(item) { + if(selected_dropdown_item) { + deselect_dropdown_item($(selected_dropdown_item)); + } + + item.addClass(settings.classes.selectedDropdownItem); + selected_dropdown_item = item.get(0); + } + } + + // Remove highlighting from an item in the results dropdown + function deselect_dropdown_item (item) { + item.removeClass(settings.classes.selectedDropdownItem); + selected_dropdown_item = null; + } + + // Do a search and show the "searching" dropdown if the input is longer + // than settings.minChars + function do_search() { + var query = input_box.val().toLowerCase(); + + if(query && query.length) { + if(selected_token) { + deselect_token($(selected_token), POSITION.AFTER); + } + + if(query.length >= settings.minChars) { + show_dropdown_searching(); + clearTimeout(timeout); + + timeout = setTimeout(function(){ + run_search(query); + }, settings.searchDelay); + } else { + hide_dropdown(); + } + } + } + + // Do the actual search + function run_search(query) { + var cached_results = cache.get(query); + if(cached_results) { + populate_dropdown(query, cached_results); + } else { + // Are we doing an ajax search or local data search? + if(settings.url) { + // Extract exisiting get params + var ajax_params = {}; + ajax_params.data = {}; + if(settings.url.indexOf("?") > -1) { + var parts = settings.url.split("?"); + ajax_params.url = parts[0]; + + var param_array = parts[1].split("&"); + $.each(param_array, function (index, value) { + var kv = value.split("="); + ajax_params.data[kv[0]] = kv[1]; + }); + } else { + ajax_params.url = settings.url; + } + + // Prepare the request + ajax_params.data[settings.queryParam] = query; + ajax_params.type = settings.method; + ajax_params.dataType = settings.contentType; + if(settings.crossDomain) { + ajax_params.dataType = "jsonp"; + } + + // Attach the success callback + ajax_params.success = function(results) { + if($.isFunction(settings.onResult)) { + results = settings.onResult.call(hidden_input, results); + } + cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results); + + // only populate the dropdown if the results are associated with the active search query + if(input_box.val().toLowerCase() === query) { + populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results); + } + }; + + // Make the request + $.ajax(ajax_params); + } else if(settings.local_data) { + // Do the search through local data + var results = $.grep(settings.local_data, function (row) { + return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1; + }); + + if($.isFunction(settings.onResult)) { + results = settings.onResult.call(hidden_input, results); + } + cache.add(query, results); + populate_dropdown(query, results); + } + } + } +}; + +// Really basic cache for the results +$.TokenList.Cache = function (options) { + var settings = $.extend({ + max_size: 500 + }, options); + + var data = {}; + var size = 0; + + var flush = function () { + data = {}; + size = 0; + }; + + this.add = function (query, results) { + if(size > settings.max_size) { + flush(); + } + + if(!data[query]) { + size += 1; + } + + data[query] = results; + }; + + this.get = function (query) { + return data[query]; + }; +}; +}(jQuery)); diff --git a/public/stylesheets/token-input-facebook.css b/public/stylesheets/token-input-facebook.css new file mode 100644 index 0000000..b4d8e92 --- /dev/null +++ b/public/stylesheets/token-input-facebook.css @@ -0,0 +1,122 @@ +/* Example tokeninput style #2: Facebook style */ +ul.token-input-list-facebook { + overflow: hidden; + height: auto !important; + height: 1%; + width: 400px; + border: 1px solid #8496ba; + cursor: text; + font-size: 12px; + font-family: Verdana; + min-height: 1px; + z-index: 999; + margin: 0; + padding: 0; + background-color: #fff; + list-style-type: none; + clear: left; +} + +ul.token-input-list-facebook li input { + border: 0; + width: 100px; + padding: 3px 8px; + background-color: white; + margin: 2px 0; + -webkit-appearance: caret; +} + +li.token-input-token-facebook { + overflow: hidden; + height: auto !important; + height: 15px; + margin: 3px; + padding: 1px 3px; + background-color: #eff2f7; + color: #000; + cursor: default; + border: 1px solid #ccd5e4; + font-size: 11px; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + float: left; + white-space: nowrap; +} + +li.token-input-token-facebook p { + display: inline; + padding: 0; + margin: 0; +} + +li.token-input-token-facebook span { + color: #a6b3cf; + margin-left: 5px; + font-weight: bold; + cursor: pointer; +} + +li.token-input-selected-token-facebook { + background-color: #5670a6; + border: 1px solid #3b5998; + color: #fff; +} + +li.token-input-input-token-facebook { + float: left; + margin: 0; + padding: 0; + list-style-type: none; +} + +div.token-input-dropdown-facebook { + position: absolute; + width: 400px; + background-color: #fff; + overflow: hidden; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + cursor: default; + font-size: 11px; + font-family: Verdana; + z-index: 1; +} + +div.token-input-dropdown-facebook p { + margin: 0; + padding: 5px; + font-weight: bold; + color: #777; +} + +div.token-input-dropdown-facebook ul { + margin: 0; + padding: 0; +} + +div.token-input-dropdown-facebook ul li { + background-color: #fff; + padding: 3px; + margin: 0; + list-style-type: none; +} + +div.token-input-dropdown-facebook ul li.token-input-dropdown-item-facebook { + background-color: #fff; +} + +div.token-input-dropdown-facebook ul li.token-input-dropdown-item2-facebook { + background-color: #fff; +} + +div.token-input-dropdown-facebook ul li em { + font-weight: bold; + font-style: normal; +} + +div.token-input-dropdown-facebook ul li.token-input-selected-dropdown-item-facebook { + background-color: #3b5998; + color: #fff; +} \ No newline at end of file diff --git a/public/stylesheets/token-input-mac.css b/public/stylesheets/token-input-mac.css new file mode 100644 index 0000000..18522f0 --- /dev/null +++ b/public/stylesheets/token-input-mac.css @@ -0,0 +1,204 @@ +/* Example tokeninput style #2: Mac Style */ +fieldset.token-input-mac { + position: relative; + padding: 0; + margin: 5px 0; + background: #fff; + width: 400px; + border: 1px solid #A4BDEC; + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; +} + +fieldset.token-input-mac.token-input-dropdown-mac { + border-radius: 10px 10px 0 0; + -moz-border-radius: 10px 10px 0 0; + -webkit-border-radius: 10px 10px 0 0; + box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25); + -moz-box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25); + -webkit-box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25); +} + +ul.token-input-list-mac { + overflow: hidden; + height: auto !important; + height: 1%; + cursor: text; + font-size: 12px; + font-family: Verdana; + min-height: 1px; + z-index: 999; + margin: 0; + padding: 3px; + background: transparent; +} + +ul.token-input-list-mac.error { + border: 1px solid #C52020; +} + +ul.token-input-list-mac li { + list-style-type: none; +} + +li.token-input-token-mac p { + display: inline; + padding: 0; + margin: 0; +} + +li.token-input-token-mac span { + color: #a6b3cf; + margin-left: 5px; + font-weight: bold; + cursor: pointer; +} + +/* TOKENS */ + +li.token-input-token-mac { + font-family: "Lucida Grande", Arial, serif; + font-size: 9pt; + line-height: 12pt; + overflow: hidden; + height: 16px; + margin: 3px; + padding: 0 10px; + background: none; + background-color: #dee7f8; + color: #000; + cursor: default; + border: 1px solid #a4bdec; + border-radius: 15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; + float: left; +} + +li.token-input-highlighted-token-mac { + background-color: #bbcef1; + border: 1px solid #598bec; + color: #000; +} + +li.token-input-selected-token-mac { + background-color: #598bec; + border: 1px solid transparent; + color: #fff; +} + +li.token-input-highlighted-token-mac span.token-input-delete-token-mac { + color: #000; +} + +li.token-input-selected-token-mac span.token-input-delete-token-mac { + color: #fff; +} + +li.token-input-input-token-mac { + border: none; + background: transparent; + float: left; + padding: 0; + margin: 0; +} + +li.token-input-input-token-mac input { + border: 0; + width: 100px; + padding: 3px; + background-color: transparent; + margin: 0; +} + +div.token-input-dropdown-mac { + position: absolute; + border: 1px solid #A4BDEC; + border-top: none; + left: -1px; + right: -1px; + background-color: #fff; + overflow: hidden; + cursor: default; + font-size: 10pt; + font-family: "Lucida Grande", Arial, serif; + padding: 5px; + border-radius: 0 0 10px 10px; + -moz-border-radius: 0 0 10px 10px; + -webkit-border-radius: 0 0 10px 10px; + box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25); + -moz-box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25); + -webkit-box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25); + clip:rect(0px, 1000px, 1000px, -10px); +} + +div.token-input-dropdown-mac p { + font-size: 8pt; + margin: 0; + padding: 0 5px; + font-style: italic; + color: #aaa; +} + +div.token-input-dropdown-mac h3.token-input-dropdown-category-mac { + font-family: "Lucida Grande", Arial, serif; + font-size: 10pt; + font-weight: bold; + border: none; + padding: 0 5px; + margin: 0; +} + +div.token-input-dropdown-mac ul { + margin: 0; + padding: 0; +} + +div.token-input-dropdown-mac ul li { + list-style-type: none; + cursor: pointer; + background: none; + background-color: #fff; + margin: 0; + padding: 0 0 0 25px; +} + +div.token-input-dropdown-mac ul li.token-input-dropdown-item-mac { + background-color: #fff; +} + +div.token-input-dropdown-mac ul li.token-input-dropdown-item-mac.odd { + background-color: #ECF4F9; + border-radius: 15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; +} + +div.token-input-dropdown-mac ul li.token-input-dropdown-item-mac span.token-input-dropdown-item-description-mac { + float: right; + font-size: 8pt; + font-style: italic; + padding: 0 10px 0 0; + color: #999; +} + +div.token-input-dropdown-mac ul li strong { + font-weight: bold; + text-decoration: underline; + font-style: none; +} + +div.token-input-dropdown-mac ul li.token-input-selected-dropdown-item-mac, +div.token-input-dropdown-mac ul li.token-input-selected-dropdown-item-mac.odd { + background-color: #598bec; + color: #fff; + border-radius: 15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; +} + +div.token-input-dropdown-mac ul li.token-input-selected-dropdown-item-mac span.token-input-dropdown-item-description-mac, +div.token-input-dropdown-mac ul li.token-input-selected-dropdown-item-mac.odd span.token-input-dropdown-item-description-mac { + color: #fff; +} diff --git a/public/stylesheets/token-input.css b/public/stylesheets/token-input.css new file mode 100644 index 0000000..7fd1373 --- /dev/null +++ b/public/stylesheets/token-input.css @@ -0,0 +1,113 @@ +/* Example tokeninput style #1: Token vertical list*/ +ul.token-input-list { + overflow: hidden; + height: auto !important; + height: 1%; + width: 497px; + border: 1px solid #999; + cursor: text; + font-size: 12px; + font-family: Verdana; + z-index: 999; + margin: 0; + padding: 0; + background-color: #fff; + list-style-type: none; + clear: left; +} + +ul.token-input-list li { + list-style-type: none; +} + +ul.token-input-list li input { + border: 0; + width: 350px; + padding: 3px 8px; + background-color: white; + -webkit-appearance: caret; +} + +li.token-input-token { + overflow: hidden; + height: auto !important; + height: 1%; + margin: 3px; + padding: 3px 5px; + background-color: #d0efa0; + color: #000; + font-weight: bold; + cursor: default; + display: block; +} + +li.token-input-token p { + float: left; + padding: 0; + margin: 0; +} + +li.token-input-token span { + float: right; + color: #777; + cursor: pointer; +} + +li.token-input-selected-token { + background-color: #08844e; + color: #fff; +} + +li.token-input-selected-token span { + color: #bbb; +} + +div.token-input-dropdown { + position: absolute; + width: 497px; + background-color: #fff; + overflow: hidden; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + cursor: default; + font-size: 12px; + font-family: Verdana; + z-index: 1; +} + +div.token-input-dropdown p { + margin: 0; + padding: 5px; + font-weight: bold; + color: #777; +} + +div.token-input-dropdown ul { + margin: 0; + padding: 0; +} + +div.token-input-dropdown ul li { + background-color: #fff; + padding: 3px; + list-style-type: none; +} + +div.token-input-dropdown ul li.token-input-dropdown-item { + background-color: #fafafa; +} + +div.token-input-dropdown ul li.token-input-dropdown-item2 { + background-color: #fff; +} + +div.token-input-dropdown ul li em { + font-weight: bold; + font-style: normal; +} + +div.token-input-dropdown ul li.token-input-selected-dropdown-item { + background-color: #d0efa0; +} + diff --git a/script/sample-profiles b/script/sample-profiles index 1f491a8..7880256 100755 --- a/script/sample-profiles +++ b/script/sample-profiles @@ -95,6 +95,7 @@ save guest do communities.rand.add_member(guest.person) end end +User.all(:conditions => ['login NOT LIKE "%%_template"']).each(&:activate) done people = $environment.people diff --git a/test/functional/account_controller_test.rb b/test/functional/account_controller_test.rb index f490608..a60861a 100644 --- a/test/functional/account_controller_test.rb +++ b/test/functional/account_controller_test.rb @@ -667,17 +667,21 @@ class AccountControllerTest < Test::Unit::TestCase end should 'merge user data with extra stuff from plugins' do - plugin1 = mock() - plugin1.stubs(:user_data_extras).returns({ :foo => 'bar' }) - - plugin2 = mock() - plugin2.stubs(:user_data_extras).returns({ :test => 5 }) + class Plugin1 < Noosfero::Plugin + def user_data_extras + {:foo => 'bar'} + end + end - enabled_plugins = [plugin1, plugin2] + class Plugin2 < Noosfero::Plugin + def user_data_extras + {:test => 5} + end + end - plugins = mock() - plugins.stubs(:enabled_plugins).returns(enabled_plugins) - Noosfero::Plugin::Manager.stubs(:new).returns(plugins) + e = User.find_by_login('ze').environment + e.enable_plugin(Plugin1.name) + e.enable_plugin(Plugin2.name) login_as 'ze' diff --git a/test/functional/admin_panel_controller_test.rb b/test/functional/admin_panel_controller_test.rb index c3348b8..cf7b6b6 100644 --- a/test/functional/admin_panel_controller_test.rb +++ b/test/functional/admin_panel_controller_test.rb @@ -342,4 +342,20 @@ class AdminPanelControllerTest < Test::Unit::TestCase assert_equal 3, Environment.default.news_amount_by_folder end + should 'display plugins links' do + plugin1_link = {:title => 'Plugin1 link', :url => 'plugin1.com'} + plugin2_link = {:title => 'Plugin2 link', :url => 'plugin2.com'} + links = [plugin1_link, plugin2_link] + plugins = mock() + plugins.stubs(:map).with(:admin_panel_links).returns(links) + plugins.stubs(:enabled_plugins).returns([]) + plugins.stubs(:map).with(:body_beginning).returns([]) + Noosfero::Plugin::Manager.stubs(:new).returns(plugins) + + get :index + + assert_tag :tag => 'a', :content => /#{plugin1_link[:title]}/, :attributes => {:href => /#{plugin1_link[:url]}/} + assert_tag :tag => 'a', :content => /#{plugin2_link[:title]}/, :attributes => {:href => /#{plugin2_link[:url]}/} + end + end diff --git a/test/functional/application_controller_test.rb b/test/functional/application_controller_test.rb index a551782..b623717 100644 --- a/test/functional/application_controller_test.rb +++ b/test/functional/application_controller_test.rb @@ -442,28 +442,23 @@ class ApplicationControllerTest < Test::Unit::TestCase end should 'include stylesheets supplied by plugins' do - plugin1 = mock() - plugin1.stubs(:stylesheet?).returns(true) - plugin1.stubs(:js_files).returns([]) - plugin1_class = mock() + class Plugin1 < Noosfero::Plugin + def stylesheet? + true + end + end plugin1_path = '/plugin1/style.css' - plugin1_class.stubs(:public_path).with('style.css').returns(plugin1_path) - plugin1.stubs(:class).returns(plugin1_class) - plugin2 = mock() - plugin2.stubs(:stylesheet?).returns(true) - plugin2.stubs(:js_files).returns([]) - plugin2_class = mock() + class Plugin2 < Noosfero::Plugin + def stylesheet? + true + end + end plugin2_path = '/plugin2/style.css' - plugin2_class.stubs(:public_path).with('style.css').returns(plugin2_path) - plugin2.stubs(:class).returns(plugin2_class) - - enabled_plugins = [plugin1, plugin2] - plugins = mock() - plugins.stubs(:map).with(:body_beginning).returns([]) - plugins.stubs(:enabled_plugins).returns(enabled_plugins) - Noosfero::Plugin::Manager.stubs(:new).returns(plugins) + environment = Environment.default + environment.enable_plugin(Plugin1.name) + environment.enable_plugin(Plugin2.name) get :index @@ -472,33 +467,29 @@ class ApplicationControllerTest < Test::Unit::TestCase end should 'include javascripts supplied by plugins' do + class Plugin1 < Noosfero::Plugin + def js_files + ['js1.js'] + end + end + js1 = 'js1.js' - plugin1 = mock() - plugin1.stubs(:stylesheet?).returns(false) - plugin1.stubs(:js_files).returns([js1]) - plugin1_class = mock() plugin1_path = '/plugin1/'+js1 - plugin1_class.stubs(:public_path).with(js1).returns(plugin1_path) - plugin1.stubs(:class).returns(plugin1_class) + + class Plugin2 < Noosfero::Plugin + def js_files + ['js2.js', 'js3.js'] + end + end js2 = 'js2.js' js3 = 'js3.js' - plugin2 = mock() - plugin2.stubs(:stylesheet?).returns(false) - plugin2.stubs(:js_files).returns([js2, js3]) - plugin2_class = mock() plugin2_path2 = '/plugin2/'+js2 plugin2_path3 = '/plugin2/'+js3 - plugin2_class.stubs(:public_path).with(js2).returns(plugin2_path2) - plugin2_class.stubs(:public_path).with(js3).returns(plugin2_path3) - plugin2.stubs(:class).returns(plugin2_class) - enabled_plugins = [plugin1, plugin2] - - plugins = mock() - plugins.stubs(:map).with(:body_beginning).returns([]) - plugins.stubs(:enabled_plugins).returns(enabled_plugins) - Noosfero::Plugin::Manager.stubs(:new).returns(plugins) + environment = Environment.default + environment.enable_plugin(Plugin1.name) + environment.enable_plugin(Plugin2.name) get :index diff --git a/test/functional/catalog_controller_test.rb b/test/functional/catalog_controller_test.rb index 2f6c279..0e3b9a4 100644 --- a/test/functional/catalog_controller_test.rb +++ b/test/functional/catalog_controller_test.rb @@ -94,23 +94,27 @@ class CatalogControllerTest < Test::Unit::TestCase end should 'include extra content supplied by plugins on catalog item extras' do + class Plugin1 < Noosfero::Plugin + def catalog_item_extras(product) + lambda {"This is Plugin1 speaking!"} + end + end + + class Plugin2 < Noosfero::Plugin + def catalog_item_extras(product) + lambda {"This is Plugin2 speaking!"} + end + end + product = fast_create(Product, :enterprise_id => @enterprise.id) - plugin1_local_variable = "Plugin1" - plugin1_content = lambda {"This is #{plugin1_local_variable} speaking!"} - plugin2_local_variable = "Plugin2" - plugin2_content = lambda {"This is #{plugin2_local_variable} speaking!"} - contents = [plugin1_content, plugin2_content] - - plugins = mock() - plugins.stubs(:enabled_plugins).returns([]) - plugins.stubs(:map).with(:body_beginning).returns([]) - plugins.stubs(:map).with(:catalog_item_extras, product).returns(contents) - Noosfero::Plugin::Manager.stubs(:new).returns(plugins) + environment = Environment.default + environment.enable_plugin(Plugin1.name) + environment.enable_plugin(Plugin2.name) get :index, :profile => @enterprise.identifier - assert_tag :tag => 'span', :content => 'This is ' + plugin1_local_variable + ' speaking!', :attributes => {:id => 'plugin1'} - assert_tag :tag => 'span', :content => 'This is ' + plugin2_local_variable + ' speaking!', :attributes => {:id => 'plugin2'} + assert_tag :tag => 'span', :content => 'This is Plugin1 speaking!', :attributes => {:id => 'plugin1'} + assert_tag :tag => 'span', :content => 'This is Plugin2 speaking!', :attributes => {:id => 'plugin2'} end end diff --git a/test/functional/enterprise_registration_controller_test.rb b/test/functional/enterprise_registration_controller_test.rb index e78eab9..e6c9062 100644 --- a/test/functional/enterprise_registration_controller_test.rb +++ b/test/functional/enterprise_registration_controller_test.rb @@ -179,5 +179,4 @@ all_fixtures get :index assert_equal assigns(:create_enterprise).target, environment end - end diff --git a/test/functional/manage_products_controller_test.rb b/test/functional/manage_products_controller_test.rb index a5b2847..24bb76f 100644 --- a/test/functional/manage_products_controller_test.rb +++ b/test/functional/manage_products_controller_test.rb @@ -455,4 +455,16 @@ class ManageProductsControllerTest < Test::Unit::TestCase assert_tag :tag => 'span', :content => 'This is ' + plugin1_local_variable + ' speaking!', :attributes => {:id => 'plugin1'} assert_tag :tag => 'span', :content => 'This is ' + plugin2_local_variable + ' speaking!', :attributes => {:id => 'plugin2'} end + + should 'not allow product creation for profiles that can\'t do it' do + class SpecialEnterprise < Enterprise + def create_product? + false + end + end + enterprise = SpecialEnterprise.create!(:identifier => 'special-enterprise', :name => 'Special Enterprise') + get 'new', :profile => enterprise.identifier + assert_response 403 + end + end diff --git a/test/functional/profile_controller_test.rb b/test/functional/profile_controller_test.rb index b8f9ef1..6b329e1 100644 --- a/test/functional/profile_controller_test.rb +++ b/test/functional/profile_controller_test.rb @@ -1163,19 +1163,31 @@ class ProfileControllerTest < Test::Unit::TestCase end should 'display plugins tabs' do - plugin1_tab = {:title => 'Plugin1 tab', :id => 'plugin1_tab', :content => 'Content from plugin1.'} - plugin2_tab = {:title => 'Plugin2 tab', :id => 'plugin2_tab', :content => 'Content from plugin2.'} - tabs = [plugin1_tab, plugin2_tab] - plugins = mock() - plugins.stubs(:map).with(:profile_tabs).returns(tabs) - plugins.stubs(:enabled_plugins).returns([]) - plugins.stubs(:map).with(:body_beginning).returns([]) - Noosfero::Plugin::Manager.stubs(:new).returns(plugins) + class Plugin1 < Noosfero::Plugin + def profile_tabs + {:title => 'Plugin1 tab', :id => 'plugin1_tab', :content => lambda { 'Content from plugin1.' }} + end + end + + class Plugin2 < Noosfero::Plugin + def profile_tabs + {:title => 'Plugin2 tab', :id => 'plugin2_tab', :content => lambda { 'Content from plugin2.' }} + end + end + + e = profile.environment + e.enable_plugin(Plugin1.name) + e.enable_plugin(Plugin2.name) get :index, :profile => profile.identifier - assert_tag :tag => 'a', :content => /#{plugin1_tab[:title]}/, :attributes => {:href => /#{plugin1_tab[:id]}/} - assert_tag :tag => 'div', :content => /#{plugin1_tab[:content]}/, :attributes => {:id => /#{plugin1_tab[:id]}/} + plugin1 = Plugin1.new + plugin2 = Plugin2.new + + assert_tag :tag => 'a', :content => /#{plugin1.profile_tabs[:title]}/, :attributes => {:href => /#{plugin1.profile_tabs[:id]}/} + assert_tag :tag => 'div', :content => /#{instance_eval(&plugin1.profile_tabs[:content])}/, :attributes => {:id => /#{plugin1.profile_tabs[:id]}/} + assert_tag :tag => 'a', :content => /#{plugin2.profile_tabs[:title]}/, :attributes => {:href => /#{plugin2.profile_tabs[:id]}/} + assert_tag :tag => 'div', :content => /#{instance_eval(&plugin2.profile_tabs[:content])}/, :attributes => {:id => /#{plugin2.profile_tabs[:id]}/} end should 'redirect to profile page when try to request join_not_logged via GET method' do diff --git a/test/functional/profile_members_controller_test.rb b/test/functional/profile_members_controller_test.rb index dc85aae..f9a370d 100644 --- a/test/functional/profile_members_controller_test.rb +++ b/test/functional/profile_members_controller_test.rb @@ -233,59 +233,87 @@ class ProfileMembersControllerTest < Test::Unit::TestCase assert_not_includes com.members, u end - should 'find users' do - ent = fast_create(Enterprise, :name => 'Test Ent', :identifier => 'test_ent') - user = create_user_full('test_user').person - person = create_user_with_permission('ent_user', 'manage_memberships', ent) - login_as :ent_user - - get :find_users, :profile => ent.identifier, :query => 'test*', :scope => 'all_users' - - assert_includes assigns(:users_found), user - end - - should 'not display members when finding users in all_users scope' do - ent = fast_create(Enterprise, :name => 'Test Ent', :identifier => 'test_ent') - user = create_user_full('test_user').person - - person = create_user_with_permission('ent_user', 'manage_memberships', ent) - login_as :ent_user - - get :find_users, :profile => ent.identifier, :query => '*user', :scope => 'all_users' - - assert_tag :tag => 'a', :content => /#{user.name}/ - assert_no_tag :tag => 'a', :content => /#{person.name}/ - end - - should 'not display admins when finding users in new_admins scope' do - ent = fast_create(Enterprise, :name => 'Test Ent', :identifier => 'test_ent') - - person = create_user('admin_user').person - ent.add_admin(person) - - user = create_user_full('test_user').person - ent.add_member(user).finish - - login_as :admin_user - - get :find_users, :profile => ent.identifier, :query => '*user', :scope => 'new_admins' + should 'list users on search by role' do + e = Enterprise.create!(:name => 'Sample Enterprise', :identifier => 'sample-enterprise') + user = create_user_with_permission('test_user', 'manage_memberships', e) + login_as :test_user - assert_tag :tag => 'a', :content => /#{user.name}/ - assert_no_tag :tag => 'a', :content => /#{person.name}/ + # Should list if match name + p1 = create_user('person_1').person + p2 = create_user('person_2').person + # Should not list if don't match name + p3 = create_user('blo').person + r1 = Profile::Roles.organization_member_roles(e.environment.id).first + r2 = Profile::Roles.organization_member_roles(e.environment.id).last + + p4 = create_user('person_4').person + e.affiliate(p4, r1) + p5 = create_user('person_5').person + e.affiliate(p5, r2) + + # Should be case insensitive + p6 = create_user('PeRsOn_2').person + # Should list if match identifier + p7 = create_user('person_7').person + p7.name = 'Bli' + p7.save! + + get :search_user, :profile => e.identifier, 'q_'+r1.key => 'per', :role => r1.id + assert_match /#{p1.name}/, @response.body + assert_match /#{p2.name}/, @response.body + assert_no_match /#{p3.name}/, @response.body + assert_no_match /#{p4.name}/, @response.body + assert_match /#{p5.name}/, @response.body + assert_match /#{p6.name}/, @response.body + assert_match /#{p7.name}/, @response.body + + get :search_user, :profile => e.identifier, 'q_'+r2.key => 'per', :role => r2.id + assert_match /#{p1.name}/, @response.body + assert_match /#{p2.name}/, @response.body + assert_no_match /#{p3.name}/, @response.body + assert_match /#{p4.name}/, @response.body + assert_no_match /#{p5.name}/, @response.body + assert_match /#{p6.name}/, @response.body + assert_match /#{p7.name}/, @response.body end - should 'return users with as a prefix' do - daniel = create_user_full('daniel').person - daniela = create_user_full('daniela').person - - ent = fast_create(Enterprise, :name => 'Test Ent', :identifier => 'test_ent') - person = create_user_with_permission('test_user', 'manage_memberships', ent) + should 'save associations' do + e = Enterprise.create!(:name => 'Sample Enterprise', :identifier => 'sample-enterprise') + user = create_user_with_permission('test_user', 'manage_memberships', e) login_as :test_user - get :find_users, :profile => ent.identifier, :query => 'daniel', :scope => 'all_users' - - assert_includes assigns(:users_found), daniel - assert_includes assigns(:users_found), daniela + p1 = create_user('person-1').person + p2 = create_user('person-2').person + p3 = create_user('person-3').person + roles = Profile::Roles.organization_member_roles(e.environment.id) + r1 = roles.first + r2 = roles.last + roles.delete(r1) + roles.delete(r2) + + roles_params = roles.inject({}) { |result, role| result.merge({'q_'+role.key => ''})} + + post :save_associations, + {:profile => e.identifier, + 'q_'+r1.key => "#{p1.id},#{p2.id},#{user.id}", + 'q_'+r2.key => "#{p2.id},#{p3.id}"}.merge(roles_params) + assert_includes e.members_by_role(r1), p1 + assert_includes e.members_by_role(r1), p2 + assert_not_includes e.members_by_role(r1), p3 + assert_not_includes e.members_by_role(r2), p1 + assert_includes e.members_by_role(r2), p2 + assert_includes e.members_by_role(r2), p3 + + post :save_associations, + {:profile => e.identifier, + 'q_'+r1.key => "#{p2.id},#{p3.id},#{user.id}", + 'q_'+r2.key => "#{p1.id},#{p2.id}"}.merge(roles_params) + assert_not_includes e.members_by_role(r1), p1 + assert_includes e.members_by_role(r1), p2 + assert_includes e.members_by_role(r1), p3 + assert_includes e.members_by_role(r2), p1 + assert_includes e.members_by_role(r2), p2 + assert_not_includes e.members_by_role(r2), p3 end should 'ignore roles with id zero' do diff --git a/test/functional/search_controller_test.rb b/test/functional/search_controller_test.rb index a9cc95c..1f9691f 100644 --- a/test/functional/search_controller_test.rb +++ b/test/functional/search_controller_test.rb @@ -290,24 +290,53 @@ class SearchControllerTest < Test::Unit::TestCase end should 'include extra content supplied by plugins on product asset' do + class Plugin1 < Noosfero::Plugin + def asset_product_extras(product, enterprise) + lambda {"This is Plugin1 speaking!"} + end + end + + class Plugin2 < Noosfero::Plugin + def asset_product_extras(product, enterprise) + lambda {"This is Plugin2 speaking!"} + end + end + enterprise = fast_create(Enterprise) product = fast_create(Product, :enterprise_id => enterprise.id) - plugin1_local_variable = "Plugin1" - plugin1_content = lambda {"This is #{plugin1_local_variable} speaking!"} - plugin2_local_variable = "Plugin2" - plugin2_content = lambda {"This is #{plugin2_local_variable} speaking!"} - contents = [plugin1_content, plugin2_content] - - plugins = mock() - plugins.stubs(:enabled_plugins).returns([]) - plugins.stubs(:map).with(:body_beginning).returns([]) - plugins.stubs(:map).with(:asset_product_extras, product, enterprise).returns(contents) - Noosfero::Plugin::Manager.stubs(:new).returns(plugins) + + e = Environment.default + e.enable_plugin(Plugin1.name) + e.enable_plugin(Plugin2.name) + + get :assets, :asset => 'products' + + assert_tag :tag => 'span', :content => 'This is Plugin1 speaking!', :attributes => {:id => 'plugin1'} + assert_tag :tag => 'span', :content => 'This is Plugin2 speaking!', :attributes => {:id => 'plugin2'} + end + + should 'include extra properties of the product supplied by plugins' do + class Plugin1 < Noosfero::Plugin + def asset_product_properties(product) + return { :name => _('Property1'), :content => lambda { link_to(product.name, '/plugin1') } } + end + end + class Plugin2 < Noosfero::Plugin + def asset_product_properties(product) + return { :name => _('Property2'), :content => lambda { link_to(product.name, '/plugin2') } } + end + end + enterprise = fast_create(Enterprise) + product = fast_create(Product, :enterprise_id => enterprise.id) + + environment = Environment.default + environment.enable_plugin(Plugin1.name) + environment.enable_plugin(Plugin2.name) get :assets, :asset => 'products' - assert_tag :tag => 'span', :content => 'This is ' + plugin1_local_variable + ' speaking!', :attributes => {:id => 'plugin1'} - assert_tag :tag => 'span', :content => 'This is ' + plugin2_local_variable + ' speaking!', :attributes => {:id => 'plugin2'} + assert_tag :tag => 'li', :content => /Property1/, :child => {:tag => 'a', :attributes => {:href => '/plugin1'}, :content => product.name} + assert_tag :tag => 'li', :content => /Property2/, :child => {:tag => 'a', :attributes => {:href => '/plugin2'}, :content => product.name} end should 'paginate enterprise listing' do diff --git a/test/unit/application_helper_test.rb b/test/unit/application_helper_test.rb index 245acc7..b463a69 100644 --- a/test/unit/application_helper_test.rb +++ b/test/unit/application_helper_test.rb @@ -18,18 +18,6 @@ class ApplicationHelperTest < Test::Unit::TestCase File.expects(:exists?).with(p1+"test/_integer.rhtml").returns(true) - assert_equal 'integer', partial_for_class(Integer) - end - - - should 'calculate correctly partial for models recursively' do - p1 = 'path1/' - p2 = 'path2/' - @controller = mock() - @controller.stubs(:view_paths).returns([p1,p2]) - - self.stubs(:params).returns({:controller => 'test'}) - File.expects(:exists?).with(p1+"test/_float.rhtml").returns(false) File.expects(:exists?).with(p1+"test/_float.html.erb").returns(false) File.expects(:exists?).with(p2+"test/_float.rhtml").returns(false) @@ -40,22 +28,13 @@ class ApplicationHelperTest < Test::Unit::TestCase File.expects(:exists?).with(p1+"test/_numeric.html.erb").returns(false) File.expects(:exists?).with(p2+"test/_numeric.rhtml").returns(true) - assert_equal 'numeric', partial_for_class(Float) - end - - should 'raise error when partial is missing' do - p1 = 'path1/' - p2 = 'path2/' - @controller = mock() - @controller.stubs(:view_paths).returns([p1,p2]) - - self.stubs(:params).returns({:controller => 'test'}) - File.expects(:exists?).with(p1+"test/_object.rhtml").returns(false) File.expects(:exists?).with(p1+"test/_object.html.erb").returns(false) File.expects(:exists?).with(p2+"test/_object.rhtml").returns(false) File.expects(:exists?).with(p2+"test/_object.html.erb").returns(false) + assert_equal 'integer', partial_for_class(Integer) + assert_equal 'numeric', partial_for_class(Float) assert_raises ArgumentError do partial_for_class(Object) end @@ -74,6 +53,15 @@ class ApplicationHelperTest < Test::Unit::TestCase assert_equal 'test/application_helper_test/school/project', partial_for_class(School::Project) end + should 'look for superclasses on view_for_profile actions' do + File.expects(:exists?).with("#{RAILS_ROOT}/app/views/blocks/profile_info_actions/float.rhtml").returns(false) + File.expects(:exists?).with("#{RAILS_ROOT}/app/views/blocks/profile_info_actions/float.html.erb").returns(false) + File.expects(:exists?).with("#{RAILS_ROOT}/app/views/blocks/profile_info_actions/numeric.rhtml").returns(false) + File.expects(:exists?).with("#{RAILS_ROOT}/app/views/blocks/profile_info_actions/numeric.html.erb").returns(true) + + assert_equal 'blocks/profile_info_actions/numeric.html.erb', view_for_profile_actions(Float) + end + should 'give error when there is no partial for class' do assert_raises ArgumentError do partial_for_class(nil) diff --git a/test/unit/enterprise_test.rb b/test/unit/enterprise_test.rb index 762af71..b454779 100644 --- a/test/unit/enterprise_test.rb +++ b/test/unit/enterprise_test.rb @@ -350,6 +350,20 @@ class EnterpriseTest < Test::Unit::TestCase assert_equal false, Enterprise['test_ent'].enabled? end + should 'enterprise is validated according to feature enterprises_are_validated_when_created' do + e = Environment.default + + e.enable('enterprises_are_validated_when_created') + e.save + enterprise = Enterprise.create(:name => 'test enteprise', :identifier => 'test_ent1') + assert enterprise.validated + + e.disable('enterprises_are_validated_when_created') + e.save + enterprise = Enterprise.create(:name => 'test enteprise', :identifier => 'test_ent2') + assert !enterprise.validated + end + should 'have inactive_template when creating enterprise and feature is enabled' do inactive_template = fast_create(Enterprise, :name => 'inactive enteprise template', :identifier => 'inactive_enterprise_template') inactive_template.boxes.destroy_all diff --git a/test/unit/environment_test.rb b/test/unit/environment_test.rb index 23eedd9..cd39f96 100644 --- a/test/unit/environment_test.rb +++ b/test/unit/environment_test.rb @@ -1183,4 +1183,17 @@ class EnvironmentTest < Test::Unit::TestCase assert_equal env.settings[:string_key], 'new value' end + should 'be able to enable or disable a plugin' do + environment = Environment.default + plugin = 'Plugin' + + environment.enable_plugin(plugin) + environment.reload + assert_includes environment.enabled_plugins, plugin + + environment.disable_plugin(plugin) + environment.reload + assert_not_includes environment.enabled_plugins, plugin + end + end diff --git a/test/unit/organization_test.rb b/test/unit/organization_test.rb index eb7f1a5..e701d82 100644 --- a/test/unit/organization_test.rb +++ b/test/unit/organization_test.rb @@ -386,4 +386,26 @@ class OrganizationTest < Test::Unit::TestCase assert_includes Organization.more_active, profile end + should 'validates format of cnpj' do + organization = Organization.new(:cnpj => '239-234.234') + organization.valid? + assert organization.errors.invalid?(:cnpj) + + organization.cnpj = '94.132.024/0001-48' + organization.valid? + assert !organization.errors.invalid?(:cnpj) + end + + should 'return members by role in a json format' do + organization = fast_create(Organization) + p1 = create_user('person-1').person + p2 = create_user('person-2').person + role = Profile::Roles.organization_member_roles(organization.environment.id).last + + organization.affiliate(p1, role) + organization.affiliate(p2, role) + + assert_match [{:id => p1.id, :name => p1.name}, {:id => p2.id, :name => p2.name}].to_json, organization.members_by_role_to_json(role) + end + end diff --git a/test/unit/profile_test.rb b/test/unit/profile_test.rb index b1af76e..5c6bfcb 100644 --- a/test/unit/profile_test.rb +++ b/test/unit/profile_test.rb @@ -1726,6 +1726,20 @@ class ProfileTest < Test::Unit::TestCase assert_not_includes env.profiles.without_image, with_image end + should 'return enterprises subclasses too on namedscope enterprises' do + class EnterpriseSubclass < Enterprise; end + child = EnterpriseSubclass.create!(:identifier => 'child', :name => 'Child') + + assert_includes Profile.enterprises, child + end + + should 'return communities subclasses too on namedscope communities' do + class CommunitySubclass < Community; end + child = CommunitySubclass.create!(:identifier => 'child', :name => 'Child') + + assert_includes Profile.communities, child + end + private def assert_invalid_identifier(id) diff --git a/vendor/plugins/validates_as_cnpj/LEIAME b/vendor/plugins/validates_as_cnpj/LEIAME new file mode 100755 index 0000000..7ff1096 --- /dev/null +++ b/vendor/plugins/validates_as_cnpj/LEIAME @@ -0,0 +1,13 @@ +Validacao de CNPJ +================= + +Plugin para validacao do formato e dos digitos verificadores de um CNPJ. + +Uso: + Adicione ao modelo: + validates_as_cnpj :nome_do_campo + +Exemplo: + class Empresa < ActiveRecord::Base + validates_as_cnpj :cnpj + end diff --git a/vendor/plugins/validates_as_cnpj/init.rb b/vendor/plugins/validates_as_cnpj/init.rb new file mode 100755 index 0000000..3fc9568 --- /dev/null +++ b/vendor/plugins/validates_as_cnpj/init.rb @@ -0,0 +1,17 @@ +# init.rb - initialize the plugin +# +# Copyright (c) 2006 O.S. Systems +# +# Authors: André Ribeiro Camargo , +# Luis Gustavo S. Barreto +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +require 'validates_as_cnpj' diff --git a/vendor/plugins/validates_as_cnpj/lib/validates_as_cnpj.rb b/vendor/plugins/validates_as_cnpj/lib/validates_as_cnpj.rb new file mode 100755 index 0000000..1662b15 --- /dev/null +++ b/vendor/plugins/validates_as_cnpj/lib/validates_as_cnpj.rb @@ -0,0 +1,78 @@ +# validates_as_cnpj.rb - implement the validation of cnpj +# +# Copyright (c) 2006 O.S. Systems +# +# Authors: Andr Ribeiro Camargo , +# Luis Gustavo S. Barreto +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +module ValidaCNPJ + def self.valida_cnpj(cnpj = nil) + return nil if cnpj.nil? + + nulos = %w{11111111111111 + 22222222222222 + 33333333333333 + 44444444444444 + 55555555555555 + 66666666666666 + 77777777777777 + 88888888888888 + 99999999999999 + 00000000000000} + + valor = cnpj.scan(/[0-9]/).collect{|x| x.to_i} + + fatores = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2] + + if valor.length == 14 and not nulos.member?(valor.to_s) + soma = 0 + 0.upto(11) do |i| + soma += valor[i] * fatores[i] + end + + resto = soma % 11 + dv1 = resto < 2 ? 0 : 11 - resto + + if dv1 == valor[12] + soma = 0 + 0.upto(12) do |i| + soma += valor[i] * (i == 0 ? 6 : fatores[i-1]) + end + + resto = soma % 11 + dv2 = resto < 2 ? 0 : 11 - resto + + return true if dv2 == valor[13] + end + end + return nil + end +end + +module ActiveRecord + module Validations + module ClassMethods + def validates_as_cnpj(*attr_names) + configuration = { :message => "%{fn} is invalid" } + configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) + + validates_each(attr_names, configuration) do |record, attr_name, value| + next if value.blank? + + unless ValidaCNPJ::valida_cnpj(value) + record.errors.add(attr_name, configuration[:message]) + end + end + end + end + end +end diff --git a/vendor/plugins/validates_as_cnpj/test/abstract_unit.rb b/vendor/plugins/validates_as_cnpj/test/abstract_unit.rb new file mode 100755 index 0000000..021f5ff --- /dev/null +++ b/vendor/plugins/validates_as_cnpj/test/abstract_unit.rb @@ -0,0 +1,7 @@ +require '../../../../config/environment' + +$:.unshift(File.dirname(__FILE__) + '/../lib') + +require 'test/unit' +require 'active_record' +require 'connection' diff --git a/vendor/plugins/validates_as_cnpj/test/cnpj_test.rb b/vendor/plugins/validates_as_cnpj/test/cnpj_test.rb new file mode 100755 index 0000000..8717bc8 --- /dev/null +++ b/vendor/plugins/validates_as_cnpj/test/cnpj_test.rb @@ -0,0 +1,61 @@ + +require File.dirname(__FILE__) + '/abstract_unit' +require '../lib/validates_as_cnpj' + +# Modelo +class CNPJData < ActiveRecord::Base + set_table_name "cnpjs" + + validates_as_cnpj :cnpj +end + +# Testes +class CNPJsTest < Test::Unit::TestCase + def test_aceita_cnpj_nulo_por_que_deve_ser_barrado_por_validates_presence_of + cnpj_valido = CNPJData.new(:id => 1, :cnpj => nil) + + assert cnpj_valido.save, "Nao salvou CNPJ nulo." + end + + def test_aceita_cnpj_vazio_por_que_deve_ser_barrado_por_validates_presence_of + cnpj_valido = CNPJData.new(:id => 1, :cnpj => "") + + assert cnpj_valido.save, "Nao salvou CNPJ vazio." + end + + def test_cnpj_incompleto + cnpj_invalido = CNPJData.new(:id => 1, :cnpj => "123") + + assert ( not cnpj_invalido.save ), "Salvou CNPJ incompleto." + end + + def test_cnpj_invalido_sem_pontuacao + cnpj_invalido = CNPJData.new(:id => 1, :cnpj => "00000000000000") + + assert ( not cnpj_invalido.save ), "Salvou CNPJ invalido." + end + + def test_cnpj_valido_sem_pontuacao + cnpj_valido = CNPJData.new(:id => 1, :cnpj => "04613251000100") + + assert cnpj_valido.save, "Nao salvou CNPJ valido." + end + + def test_cnpj_invalido_sem_pontuacao_com_digitos_verificadores_invertidos + cnpj_invalido = CNPJData.new(:id => 1, :cnpj => "10002574000125") + + assert ( not cnpj_invalido.save ), "Salvou CNPJ invalido." + end + + def test_cnpj_invalido_com_pontuacao + cnpj_invalido = CNPJData.new(:id => 1, :cnpj => "51.357.999/1110-98") + + assert ( not cnpj_invalido.save ), "CNPJ invalido foi salvo." + end + + def test_cnpj_valido_com_pontuacao + cnpj_valido = CNPJData.new(:id => 1, :cnpj => "94.132.024/0001-48") + + assert ( cnpj_valido.save ), "CNPJ valido nao foi salvo." + end +end diff --git a/vendor/plugins/validates_as_cnpj/test/connection.rb b/vendor/plugins/validates_as_cnpj/test/connection.rb new file mode 100755 index 0000000..2c0f2b8 --- /dev/null +++ b/vendor/plugins/validates_as_cnpj/test/connection.rb @@ -0,0 +1,6 @@ +ActiveRecord::Base.establish_connection( + :adapter => "sqlite3", + :database => "db_test.db" +) + +load File.dirname(__FILE__) + "/fixtures/schema.rb" diff --git a/vendor/plugins/validates_as_cnpj/test/fixtures/schema.rb b/vendor/plugins/validates_as_cnpj/test/fixtures/schema.rb new file mode 100755 index 0000000..3cbf765 --- /dev/null +++ b/vendor/plugins/validates_as_cnpj/test/fixtures/schema.rb @@ -0,0 +1,6 @@ +ActiveRecord::Schema.define do + create_table :cnpjs, :force => true do |t| + t.column :id, :string, :null => true + t.column :cnpj, :string, :null => true + end +end -- libgit2 0.21.2