Commit 84ca7256e30e2ac68264f248c8c15457cb134771

Authored by Joenio Costa
2 parents 75f13387 7be7e7c6

Merge branch 'customfields-rails4' into 'master'

Add custom field feature to core

Custom fields can be added to any profile through the admin panel
in the 'Fields' section. They have the same behavior as the current
Noosfero's fields (active, signup, required and privacy).

See merge request !730
Showing 52 changed files with 806 additions and 36 deletions   Show diff stats
app/controllers/admin/features_controller.rb
1 class FeaturesController < AdminController 1 class FeaturesController < AdminController
2 protect 'edit_environment_features', :environment 2 protect 'edit_environment_features', :environment
  3 + helper CustomFieldsHelper
3 4
4 def index 5 def index
5 @features = Environment.available_features.sort_by{|k,v|v} 6 @features = Environment.available_features.sort_by{|k,v|v}
@@ -51,6 +52,34 @@ class FeaturesController &lt; AdminController @@ -51,6 +52,34 @@ class FeaturesController &lt; AdminController
51 redirect_to :action => 'manage_fields' 52 redirect_to :action => 'manage_fields'
52 end 53 end
53 54
  55 + def manage_custom_fields
  56 + custom_field_list = params[:custom_fields] || {}
  57 +
  58 + custom_fields_to_destroy =
  59 + params[:customized_type].constantize.custom_fields(environment).map(&:id) - custom_field_list.keys.map(&:to_i)
  60 + CustomField.destroy(custom_fields_to_destroy)
  61 +
  62 + custom_field_list.each_pair do |id, custom_field|
  63 + field = CustomField.find_by_id(id)
  64 + if not field.blank?
  65 + params_to_update = custom_field.except(:format, :extras, :customized_type,:environment)
  66 + field.update_attributes(params_to_update)
  67 + else
  68 + if !custom_field[:extras].nil?
  69 + tmp = []
  70 + custom_field[:extras].each_pair do |k, v|
  71 + tmp << v
  72 + end
  73 + custom_field[:extras] = tmp
  74 + end
  75 + field = CustomField.new custom_field.except(:environment)
  76 + field.environment=environment
  77 + field.save if field.valid?
  78 + end
  79 + end
  80 + redirect_to :action => 'manage_fields'
  81 + end
  82 +
54 def search_members 83 def search_members
55 arg = params[:q].downcase 84 arg = params[:q].downcase
56 result = environment.people.where('LOWER(name) LIKE ? OR identifier LIKE ?', "%#{arg}%", "%#{arg}%") 85 result = environment.people.where('LOWER(name) LIKE ? OR identifier LIKE ?', "%#{arg}%", "%#{arg}%")
app/controllers/my_profile/memberships_controller.rb
1 class MembershipsController < MyProfileController 1 class MembershipsController < MyProfileController
2 2
3 protect 'manage_memberships', :profile 3 protect 'manage_memberships', :profile
  4 + helper CustomFieldsHelper
4 5
5 def index 6 def index
6 @roles = environment.roles.select do |role| 7 @roles = environment.roles.select do |role|
app/controllers/my_profile/profile_editor_controller.rb
@@ -8,6 +8,7 @@ class ProfileEditorController &lt; MyProfileController @@ -8,6 +8,7 @@ class ProfileEditorController &lt; MyProfileController
8 before_filter :forbid_destroy_profile, :only => [:destroy_profile] 8 before_filter :forbid_destroy_profile, :only => [:destroy_profile]
9 before_filter :check_user_can_edit_header_footer, :only => [:header_footer] 9 before_filter :check_user_can_edit_header_footer, :only => [:header_footer]
10 helper_method :has_welcome_page 10 helper_method :has_welcome_page
  11 + helper CustomFieldsHelper
11 12
12 def index 13 def index
13 @pending_tasks = Task.to(profile).pending.without_spam.select{|i| user.has_permission?(i.permission, profile)} 14 @pending_tasks = Task.to(profile).pending.without_spam.select{|i| user.has_permission?(i.permission, profile)}
app/controllers/public/account_controller.rb
@@ -6,6 +6,7 @@ class AccountController &lt; ApplicationController @@ -6,6 +6,7 @@ class AccountController &lt; ApplicationController
6 before_filter :redirect_if_logged_in, :only => [:login, :signup] 6 before_filter :redirect_if_logged_in, :only => [:login, :signup]
7 before_filter :protect_from_bots, :only => :signup 7 before_filter :protect_from_bots, :only => :signup
8 8
  9 + helper CustomFieldsHelper
9 # say something nice, you goof! something sweet. 10 # say something nice, you goof! something sweet.
10 def index 11 def index
11 unless logged_in? 12 unless logged_in?
app/controllers/public/profile_controller.rb
@@ -7,6 +7,7 @@ class ProfileController &lt; PublicController @@ -7,6 +7,7 @@ class ProfileController &lt; PublicController
7 7
8 helper TagsHelper 8 helper TagsHelper
9 helper ActionTrackerHelper 9 helper ActionTrackerHelper
  10 + helper CustomFieldsHelper
10 11
11 protect 'send_mail_to_members', :profile, :only => [:send_mail] 12 protect 'send_mail_to_members', :profile, :only => [:send_mail]
12 13
app/helpers/custom_fields_helper.rb 0 → 100644
@@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
  1 +module CustomFieldsHelper
  2 +
  3 + def format_name(format)
  4 + names = {}
  5 + names['string'] = _('String')
  6 + names['text'] = _('Text')
  7 + names['date'] = _('Date')
  8 + names['numeric'] = _('Numeric')
  9 + names['link'] = _('Link')
  10 + names['list'] = _('List')
  11 + names['checkbox'] = _('Checkbox')
  12 + names[format]
  13 + end
  14 +
  15 + def custom_field_forms(customized_type)
  16 + forms = []
  17 + forms << [_('String'), form_for_format(customized_type,'string')]
  18 + forms << [_('Text'), form_for_format(customized_type,'text')]
  19 + forms << [_('Date'), form_for_format(customized_type,'date')]
  20 + forms << [_('Numeric'), form_for_format(customized_type,'numeric')]
  21 + forms << [_('Link'), form_for_format(customized_type,'link')]
  22 + forms << [_('List'), form_for_format(customized_type,'list')]
  23 + forms << [_('Checkbox'), form_for_format(customized_type,'checkbox')]
  24 + forms
  25 + end
  26 +
  27 + def render_extras_field(id, extra=nil, field=nil)
  28 + if extra.nil?
  29 + CGI::escapeHTML((render(:partial => 'features/custom_fields/extras_field', :locals => {:id => id, :extra => nil, :field => field})))
  30 + else
  31 + render :partial => 'features/custom_fields/extras_field', :locals => {:id => id, :extra => extra, :field => field}
  32 + end
  33 + end
  34 +
  35 + def form_for_field(field, customized_type)
  36 + render :partial => 'features/custom_fields/form', :locals => {:field => field}
  37 + end
  38 +
  39 + def display_custom_field_value(custom_field_value)
  40 + value = profile.custom_value(custom_field_value.custom_field.name)
  41 + case custom_field_value.custom_field.format
  42 + when 'text', 'list', 'numeric', 'date', 'string'
  43 + value
  44 + when 'checkbox'
  45 + value == "1" ? _('Yes') : _('No')
  46 + when 'link'
  47 + url = value[/\Ahttps?:\/\//i] ? value : "http://#{value}"
  48 + link_to(value, url, :target => '_blank')
  49 + end
  50 + end
  51 +
  52 + private
  53 +
  54 + def form_for_format(customized_type, format)
  55 + field = CustomField.new(:format => format, :customized_type => customized_type, :environment => environment)
  56 + CGI::escapeHTML((render(:partial => 'features/custom_fields/form', :locals => {:field => field})))
  57 + end
  58 +end
app/helpers/forms_helper.rb
@@ -186,6 +186,7 @@ module FormsHelper @@ -186,6 +186,7 @@ module FormsHelper
186 element_id = html_options[:id] || 'datepicker-date' 186 element_id = html_options[:id] || 'datepicker-date'
187 value = value.strftime(format) if value.present? 187 value = value.strftime(format) if value.present?
188 method = datepicker_options[:time] ? 'datetimepicker' : 'datepicker' 188 method = datepicker_options[:time] ? 'datetimepicker' : 'datepicker'
  189 + current_date_or_nil = value.present? ? "new Date('#{value}')" : "null"
189 result = text_field_tag(name, value, html_options) 190 result = text_field_tag(name, value, html_options)
190 result += 191 result +=
191 " 192 "
@@ -236,7 +237,7 @@ module FormsHelper @@ -236,7 +237,7 @@ module FormsHelper
236 weekHeader: #{datepicker_options[:week_header].to_json}, 237 weekHeader: #{datepicker_options[:week_header].to_json},
237 yearRange: #{datepicker_options[:year_range].to_json}, 238 yearRange: #{datepicker_options[:year_range].to_json},
238 yearSuffix: #{datepicker_options[:year_suffix].to_json} 239 yearSuffix: #{datepicker_options[:year_suffix].to_json}
239 - }).datepicker('setDate', new Date('#{value}')) 240 + }).datepicker('setDate', current_date_or_nil)
240 </script> 241 </script>
241 ".html_safe 242 ".html_safe
242 result 243 result
app/models/community.rb
@@ -29,7 +29,7 @@ class Community &lt; Organization @@ -29,7 +29,7 @@ class Community &lt; Organization
29 # places that call this method are safe from mass-assignment by setting the 29 # places that call this method are safe from mass-assignment by setting the
30 # environment key themselves. 30 # environment key themselves.
31 def self.create_after_moderation(requestor, attributes = {}) 31 def self.create_after_moderation(requestor, attributes = {})
32 - environment = attributes.delete(:environment) 32 + environment = attributes[:environment]
33 community = Community.new(attributes) 33 community = Community.new(attributes)
34 community.environment = environment 34 community.environment = environment
35 if community.environment.enabled?('admin_must_approve_new_communities') 35 if community.environment.enabled?('admin_must_approve_new_communities')
app/models/custom_field.rb 0 → 100644
@@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
  1 +class CustomField < ActiveRecord::Base
  2 + attr_accessible :name, :default_value, :format, :extras, :customized_type, :active, :required, :signup, :environment
  3 + serialize :customized_type
  4 + serialize :extras
  5 + has_many :custom_field_values, :dependent => :delete_all
  6 + belongs_to :environment
  7 +
  8 + validates_presence_of :name, :format, :customized_type, :environment
  9 + validate :related_to_other?
  10 + validate :unique?
  11 +
  12 + def unique?
  13 + if environment.custom_fields.any?{|cf| cf.name==name && cf.environment == environment && cf.customized_type==customized_type && new_record?}
  14 + errors.add(:body, N_("There is a field with the same name for this type in this environment"))
  15 + return false
  16 + end
  17 + true
  18 + end
  19 +
  20 + def related_to_other?
  21 + environment.custom_fields.any? do |cf|
  22 + if cf.name == name && cf.customized_type != customized_type
  23 + ancestor = cf.customized_type.constantize < customized_type.constantize
  24 + descendant = cf.customized_type.constantize > customized_type.constantize
  25 + if ancestor || descendant
  26 + errors.add(:body, N_("New field related to existent one with same name"))
  27 + return false
  28 + end
  29 + end
  30 + end
  31 + true
  32 + end
  33 +end
  34 +
app/models/custom_field_value.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +class CustomFieldValue < ActiveRecord::Base
  2 + belongs_to :custom_field
  3 + belongs_to :customized, :polymorphic => true
  4 + attr_accessible :value, :public, :customized, :custom_field, :customized_type
  5 + validate :can_save?
  6 +
  7 + def can_save?
  8 + if value.blank? && custom_field.required
  9 + errors.add(custom_field.name, _("can't be blank"))
  10 + return false
  11 + end
  12 + return true
  13 + end
  14 +end
app/models/environment.rb
@@ -24,6 +24,7 @@ class Environment &lt; ActiveRecord::Base @@ -24,6 +24,7 @@ class Environment &lt; ActiveRecord::Base
24 24
25 has_many :tasks, :dependent => :destroy, :as => 'target' 25 has_many :tasks, :dependent => :destroy, :as => 'target'
26 has_many :search_terms, :as => :context 26 has_many :search_terms, :as => :context
  27 + has_many :custom_fields, :dependent => :destroy
27 28
28 IDENTIFY_SCRIPTS = /(php[0-9s]?|[sp]htm[l]?|pl|py|cgi|rb)/ 29 IDENTIFY_SCRIPTS = /(php[0-9s]?|[sp]htm[l]?|pl|py|cgi|rb)/
29 30
app/models/profile.rb
@@ -84,6 +84,7 @@ class Profile &lt; ActiveRecord::Base @@ -84,6 +84,7 @@ class Profile &lt; ActiveRecord::Base
84 } 84 }
85 85
86 acts_as_accessible 86 acts_as_accessible
  87 + acts_as_customizable
87 88
88 include Noosfero::Plugin::HotSpot 89 include Noosfero::Plugin::HotSpot
89 90
app/views/account/_signup_form.html.erb
@@ -126,6 +126,7 @@ @@ -126,6 +126,7 @@
126 </div> 126 </div>
127 127
128 <%= recaptcha_tags :ajax => true, :display => {:theme => 'clean'} if @block_bot %> 128 <%= recaptcha_tags :ajax => true, :display => {:theme => 'clean'} if @block_bot %>
  129 +<%= render :partial => 'shared/custom_fields', :locals => {:f => f, :profile => @person, :signup => true} %>
129 130
130 <p style="text-align: center"> 131 <p style="text-align: center">
131 <%= submit_button('save', _('Create my account')) %> 132 <%= submit_button('save', _('Create my account')) %>
app/views/custom_fields/_checkbox.html.erb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<%= labelled_check_box(field.name, name, 1, profile.custom_value(field.name) == '1' )%>
app/views/custom_fields/_date.html.erb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<%= labelled_form_field(field.name, date_field(name, profile.custom_value(field.name).to_date, '%Y-%m-%d', {:change_month => true, :change_year => true, :year_range => '-100:-5', :date_format => 'yy-mm-dd'}, {:id => field.name.parameterize.underscore}))%>
app/views/custom_fields/_link.html.erb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<%= render :partial => "custom_fields/string", :locals => {:field => field, :profile => profile, :name => name} %>
app/views/custom_fields/_list.html.erb 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<%= label_tag field.name, nil, class: 'formlabel'%>
  2 +<%= select_tag name, options_for_select(field.extras.map{|v| [v,v]}, profile.custom_value(field.name)), {:prompt => _('[Select ...]') } %>
app/views/custom_fields/_numeric.html.erb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<%= labelled_form_field field.name, number_field_tag(name,profile.custom_value(field.name))%>
app/views/custom_fields/_string.html.erb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<%= labelled_form_field field.name, text_field_tag(name, profile.custom_value(field.name), :size => 30) %>
app/views/custom_fields/_text.html.erb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<%= labelled_form_field field.name, text_area_tag(name,profile.custom_value(field.name), :rows => 5, :cols => 40)%>
app/views/features/_manage_community_fields.html.erb
@@ -63,6 +63,4 @@ @@ -63,6 +63,4 @@
63 63
64 <% end %> 64 <% end %>
65 65
66 -  
67 -  
68 - 66 +<%= render :partial => "manage_custom_fields", :locals => {:customized_type => Community} %>
app/views/features/_manage_custom_fields.html.erb 0 → 100644
@@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
  1 +<h1><%= _("Custom Fields") %></h1><hr>
  2 +
  3 +<% form_id = "#{customized_type.to_s.downcase}-custom-fields-form" %>
  4 +<% fields_id = "#{customized_type.to_s.downcase}-custom-fields" %>
  5 +<% format_values_id = "#{customized_type.to_s.downcase}-formats" %>
  6 +
  7 +<%= form_tag({:action => 'manage_custom_fields'}, :id => "#{form_id}") do %>
  8 +
  9 +<%= hidden_field_tag 'customized_type', customized_type.to_s %>
  10 +
  11 +<div id="<%= fields_id %>">
  12 + <% customized_type.custom_fields(environment).each do |field|%>
  13 + <%= form_for_field(field, customized_type.to_s) %>
  14 + <% end %>
  15 +</div>
  16 +
  17 +<div class="addition-buttons">
  18 + <fieldset>
  19 + <legend><%= _('New field') %></legend>
  20 + <%= select_tag _('Type: '), options_for_select(custom_field_forms(customized_type.to_s)), {:id => "#{format_values_id}"} %>
  21 + <%= button(:add, _('Add'), 'javascript: void()', :onClick => "add_content('##{fields_id}',$('##{format_values_id} option:selected').val(), 'NEW_FIELD_ID');") %>
  22 + </fieldset>
  23 +</div>
  24 +
  25 +<% button_bar do %>
  26 + <%= button(:save, _('Save'), 'javascript: void()', :onClick => "submit_custom_field_form('##{format_values_id}', '##{form_id}');") %>
  27 +<% end %>
  28 +
  29 +<% end %>
app/views/features/_manage_enterprise_fields.html.erb
@@ -63,6 +63,4 @@ @@ -63,6 +63,4 @@
63 63
64 <% end %> 64 <% end %>
65 65
66 -  
67 -  
68 - 66 +<%= render :partial => "manage_custom_fields", :locals => {:customized_type => Enterprise} %>
app/views/features/_manage_person_fields.html.erb
@@ -60,9 +60,7 @@ @@ -60,9 +60,7 @@
60 <%= button :back, _('Back to admin panel'), :controller => 'admin_panel', :action => 'index' %> 60 <%= button :back, _('Back to admin panel'), :controller => 'admin_panel', :action => 'index' %>
61 <% end %> 61 <% end %>
62 </div> 62 </div>
63 - 63 +<br>
64 <% end %> 64 <% end %>
65 65
66 -  
67 -  
68 - 66 +<%= render :partial => "manage_custom_fields", :locals => {:customized_type => Person} %>
app/views/features/custom_fields/_extras_field.html.erb 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +<% field_id = extra.nil? ? 'EXTRAS_ID' : "#{Time.now.usec}" %>
  2 +<% def_value = field.nil? ? '' : field.default_value%>
  3 +<tr id='<%= "extras_#{field_id}" %>' >
  4 + <td>
  5 + <%= text_field_tag "custom_fields[#{id}][extras][#{field_id}]", (extra.nil? ? '' : extra), :onkeyup => "update_default_value($(this).val(), '#custom_fields_#{id}_extras_#{field_id}_default')" %>
  6 + </td>
  7 + <td>
  8 + <%= check_box_tag "custom_fields[#{id}][default_value]", extra.blank? ? '' : extra, extra.blank? ? false : def_value == extra, :id => "custom_fields_#{id}_extras_#{field_id}_default", :onclick => "manage_default_option(this);" %>
  9 + </td>
  10 + <td>
  11 + <% if extra.nil? %>
  12 + <%= button_to_function_without_text :remove, _('Remove'), "remove_content('#extras_#{field_id}'); ", :class => 'remove-field', :title => 'Remove alternative' %>
  13 + <% end %>
  14 + </td>
  15 +</tr>
app/views/features/custom_fields/_form.html.erb 0 → 100644
@@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
  1 +<% id = field.new_record? ? "NEW_FIELD_ID" : field.id %>
  2 +
  3 +<div id="<%= id %>" class="custom-field-item">
  4 +<fieldset class="fieldbox">
  5 + <legend><%= format_name(field.format) %></legend>
  6 + <%= required labelled_form_field _('Name'), text_field_tag("custom_fields[#{id}][name]", field.name, :size => 30) %>
  7 + <%= button_to_function :delete, _('Remove field'), "remove_content('##{id}');" %>
  8 +
  9 + <% if field.format != "list" %>
  10 + <%= labelled_form_field _('Default value'), text_field_tag("custom_fields[#{id}][default_value]", field.default_value, :size => 30) %>
  11 + <% end %>
  12 + <%= hidden_field_tag "custom_fields[#{id}][customized_type]", field.customized_type.to_s %>
  13 + <%= hidden_field_tag "custom_fields[#{id}][format]", field.format %>
  14 +
  15 + <div>
  16 + <%= labelled_check_box _('Active'), "custom_fields[#{id}][active]", 1, field.active, :id => "active_checkbox", :onclick => "active_action('custom_fields[#{id}][active]','custom_fields[#{id}][required]', 'custom_fields[#{id}][signup]')" %>
  17 + <%= labelled_check_box _('Required'), "custom_fields[#{id}][required]", 1, field.required, :id => "required_checkbox", :onclick => "required_action('custom_fields[#{id}][active]','custom_fields[#{id}][required]', 'custom_fields[#{id}][signup]')" %>
  18 + <%= labelled_check_box _('Display on creation?'), "custom_fields[#{id}][signup]", 1, field.signup, :id => "signup_checkbox",:onclick => "signup_action('custom_fields[#{id}][active]','custom_fields[#{id}][required]', 'custom_fields[#{id}][signup]')" %>
  19 + </div>
  20 +
  21 + <% if field.format == "list" %>
  22 + <table>
  23 + <thead>
  24 + <tr>
  25 + <th><%= _("Alternative") %></th>
  26 + <th><%= _("Default") %></th>
  27 + <th><%= _("Delete") %></th>
  28 + </tr>
  29 + </thead>
  30 + <tfoot>
  31 + <tr><td colspan=3><%= button(:add, _('Add option'), 'javascript: void()', :id => "btn_opt_#{id}", :onclick => "add_content('##{id} .custom-field-extras', $('#btn_opt_#{id}').attr('value'), 'EXTRAS_ID');", :value => "#{render_extras_field(id)}") %></td></tr>
  32 + </tfoot>
  33 + <tbody class="custom-field-extras">
  34 + <% if !field.extras.blank?%>
  35 + <% field.extras.each do |extra|%>
  36 + <%= render_extras_field id, extra, field %>
  37 + <% end %>
  38 + <% end %>
  39 + </tbody>
  40 + </table>
  41 + <% end %>
  42 +</fieldset>
  43 +</div>
app/views/features/custom_fields/_view.html.erb 0 → 100644
app/views/features/custom_fields/edit.html.erb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<%= render :partial => 'features/custom_fields/form' %>
app/views/features/custom_fields/new.html.erb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<%= render :partial => 'features/custom_fields/form' %>
app/views/memberships/new_community.html.erb
@@ -13,7 +13,6 @@ @@ -13,7 +13,6 @@
13 <%= labelled_form_for :community, :html => { :multipart => true } do |f| %> 13 <%= labelled_form_for :community, :html => { :multipart => true } do |f| %>
14 14
15 <%= required_fields_message %> 15 <%= required_fields_message %>
16 -  
17 <%= required f.text_field(:name) %> 16 <%= required f.text_field(:name) %>
18 17
19 <% @plugins.dispatch(:new_community_hidden_fields).each do |field| %> 18 <% @plugins.dispatch(:new_community_hidden_fields).each do |field| %>
@@ -23,6 +22,7 @@ @@ -23,6 +22,7 @@
23 <% end %> 22 <% end %>
24 23
25 <%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => 'community', :profile => @community } %> 24 <%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => 'community', :profile => @community } %>
  25 + <%= render :partial => 'shared/custom_fields', :locals => { :f => f, :profile => @community, :signup => true } %>
26 26
27 <%= f.fields_for :image_builder, @community.image do |i| %> 27 <%= f.fields_for :image_builder, @community.image do |i| %>
28 <%= file_field_or_thumbnail(_('Image:'), @community.image, i) %> 28 <%= file_field_or_thumbnail(_('Image:'), @community.image, i) %>
@@ -56,7 +56,3 @@ @@ -56,7 +56,3 @@
56 <% end %> 56 <% end %>
57 57
58 </div> 58 </div>
59 -  
60 -  
61 -  
62 -  
app/views/profile/_custom_fields.html.erb 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +<% public_values = profile.public_values %>
  2 +<% if !public_values.blank?%>
  3 + <tr> <th colspan="2"><%= _('Others') %> </th></tr>
  4 + <% profile.public_values.each do |cv|%>
  5 + <tr>
  6 + <td class="field-name"><%= cv.custom_field.name %></td>
  7 + <td><%= display_custom_field_value(cv) %></td>
  8 + </tr>
  9 + <% end%>
  10 +<% end %>
  11 +
app/views/profile/_organization_profile.html.erb
@@ -3,4 +3,5 @@ @@ -3,4 +3,5 @@
3 <%= display_contact %> 3 <%= display_contact %>
4 <%= display_economic %> 4 <%= display_economic %>
5 <%= render :partial => 'common' %> 5 <%= render :partial => 'common' %>
  6 + <%= render :partial => 'custom_fields' %>
6 </table> 7 </table>
app/views/profile/_person_profile.html.erb
@@ -10,5 +10,6 @@ @@ -10,5 +10,6 @@
10 10
11 <%= render :partial => 'common' %> 11 <%= render :partial => 'common' %>
12 <% end %> 12 <% end %>
13 -</table>  
14 13
  14 + <%= render :partial => 'custom_fields'%>
  15 +</table>
app/views/profile_editor/_organization.html.erb
@@ -61,6 +61,7 @@ @@ -61,6 +61,7 @@
61 <% end %> 61 <% end %>
62 62
63 <%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => 'profile_data', :profile => @profile } %> 63 <%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => 'profile_data', :profile => @profile } %>
  64 +<%= render :partial => 'shared/custom_fields', :locals => {:f => f, :profile => @profile, :editing_profile => true} %>
64 65
65 <%= labelled_check_box(_('Enable "contact us"'), 'profile_data[enable_contact_us]', "1", @profile.enable_contact_us) if @profile.enterprise? %> 66 <%= labelled_check_box(_('Enable "contact us"'), 'profile_data[enable_contact_us]', "1", @profile.enable_contact_us) if @profile.enterprise? %>
66 67
app/views/profile_editor/_person.html.erb
@@ -27,6 +27,7 @@ @@ -27,6 +27,7 @@
27 <%= link_to("Reset token", {:controller => :profile_editor, :action => :reset_private_token, :id => @profile.id}, :class => "button with-text") %> 27 <%= link_to("Reset token", {:controller => :profile_editor, :action => :reset_private_token, :id => @profile.id}, :class => "button with-text") %>
28 28
29 <%= render :partial => 'person_form', :locals => {:f => f} %> 29 <%= render :partial => 'person_form', :locals => {:f => f} %>
  30 + <%= render :partial => 'shared/custom_fields', :locals => {:f => f, :profile => @profile, :editing_profile => true} %>
30 31
31 <h2><%= _('Notification options') %></h2> 32 <h2><%= _('Notification options') %></h2>
32 <div> 33 <div>
app/views/shared/_custom_fields.html.erb 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +<% if signup ||= false %>
  2 + <% fields = profile.class.signup_custom_fields environment%>
  3 +<% else %>
  4 + <% fields = profile.class.active_custom_fields environment%>
  5 +<% end %>
  6 +<% editing_profile ||= false %>
  7 +<% fields.each do |field| %>
  8 + <% rendered = render(:partial => "custom_fields/#{field.format}", :locals => {:field => field, :profile => profile, :name => "profile_data[custom_values[#{field.name}[value]]]"})%>
  9 + <div class="<%= 'field-with-privacy-selector' if editing_profile %>">
  10 +
  11 + <% if field.required%>
  12 + <%= required rendered%>
  13 + <% else %>
  14 + <%= rendered %>
  15 + <% end %>
  16 +
  17 + <% if editing_profile %>
  18 + <div class="field-privacy-selector">
  19 + <%= labelled_check_box(_('Public'),"profile_data[custom_values[#{field.name}[public]]]", "true", profile.is_public(field.name))%>
  20 + </div>
  21 + <% end %>
  22 +
  23 + </div>
  24 +<% end %>
  25 +
app/views/shared/_organization_custom_fields.html.erb
@@ -29,4 +29,7 @@ @@ -29,4 +29,7 @@
29 <%= optional_field(profile, 'acronym', f.text_field(:acronym)) %> 29 <%= optional_field(profile, 'acronym', f.text_field(:acronym)) %>
30 <%= optional_field(profile, 'foundation_year', f.text_field(:foundation_year)) %> 30 <%= optional_field(profile, 'foundation_year', f.text_field(:foundation_year)) %>
31 <% end %> 31 <% end %>
  32 +
  33 +<%= render :partial => 'shared/custom_fields', :locals => {:f => f, :profile => profile, :signup => true} %>
  34 +
32 <%= javascript_include_tag('city_state_validation') %> 35 <%= javascript_include_tag('city_state_validation') %>
config/initializers/dependencies.rb
@@ -18,6 +18,7 @@ require &#39;acts_as_having_settings&#39; @@ -18,6 +18,7 @@ require &#39;acts_as_having_settings&#39;
18 require 'acts_as_having_boxes' 18 require 'acts_as_having_boxes'
19 require 'acts_as_having_image' 19 require 'acts_as_having_image'
20 require 'acts_as_having_posts' 20 require 'acts_as_having_posts'
  21 +require 'acts_as_customizable'
21 require 'route_if' 22 require 'route_if'
22 require 'maybe_add_http' 23 require 'maybe_add_http'
23 require 'set_profile_region_from_city_state' 24 require 'set_profile_region_from_city_state'
db/migrate/20150921140802_create_custom_fields.rb 0 → 100644
@@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
  1 +class CreateCustomFields < ActiveRecord::Migration
  2 + def change
  3 + create_table :custom_fields do |t|
  4 + t.string :name
  5 + t.string :format, :default => ""
  6 + t.text :default_value, :default => ""
  7 + t.string :customized_type
  8 + t.text :extras, :default => ""
  9 + t.boolean :active, :default => false
  10 + t.boolean :required, :default => false
  11 + t.boolean :signup, :default => false
  12 + t.integer :environment_id
  13 + t.timestamps
  14 + end
  15 +
  16 + create_table :custom_field_values do |t|
  17 + t.column "customized_type", :string, :default => "", :null => false
  18 + t.column "customized_id", :integer, :default => 0, :null => false
  19 + t.column "public", :boolean, :default => false, :null => false
  20 + t.column "custom_field_id", :integer, :default => 0, :null => false
  21 + t.column "value", :text, :default => ""
  22 + t.timestamps
  23 + end
  24 +
  25 +
  26 + add_index :custom_field_values, ["customized_type", "customized_id","custom_field_id"], :unique => true, :name => 'index_custom_field_values'
  27 + add_index :custom_fields, ["customized_type","name","environment_id"], :unique => true, :name => 'index_custom_field'
  28 +
  29 + end
  30 +end
  31 +
lib/acts_as_customizable.rb 0 → 100644
@@ -0,0 +1,125 @@ @@ -0,0 +1,125 @@
  1 +module Customizable
  2 +
  3 + def self.included(base)
  4 + base.attr_accessible :custom_values
  5 + base.extend ClassMethods
  6 + end
  7 +
  8 + module ClassMethods
  9 + def acts_as_customizable(options = {})
  10 + attr_accessor :custom_values
  11 + has_many :custom_field_values, :dependent => :delete_all, :as => :customized
  12 + send :include, Customizable::InstanceMethods
  13 + after_save :save_custom_values
  14 + validate :valid_custom_values?
  15 + end
  16 +
  17 + def active_custom_fields environment
  18 + environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type) && cf.active}
  19 + end
  20 +
  21 + def required_custom_fields environment
  22 + environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type) && cf.required}
  23 + end
  24 +
  25 + def signup_custom_fields environment
  26 + environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type) && cf.signup}
  27 + end
  28 +
  29 + def custom_fields environment
  30 + environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type)}
  31 + end
  32 +
  33 + def customized_ancestors_list
  34 + current=self
  35 + result=[]
  36 + while current.instance_methods.include? :custom_value do
  37 + result << current.name
  38 + current=current.superclass
  39 + end
  40 + result
  41 + end
  42 +
  43 + end
  44 +
  45 + module InstanceMethods
  46 +
  47 + def valid_custom_values?
  48 + is_valid = true
  49 + parse_custom_values.each do |cv|
  50 + unless cv.valid?
  51 + name = cv.custom_field.name
  52 + errors.add(name, cv.errors.messages[name.to_sym].first)
  53 + is_valid = false
  54 + end
  55 + end
  56 + is_valid
  57 + end
  58 +
  59 + def customized_class
  60 + current=self.class
  61 + while current.instance_methods.include? :custom_fields do
  62 + result=current
  63 + current=current.superclass
  64 + end
  65 + result.name
  66 + end
  67 +
  68 + def is_public(field_name)
  69 + cv = self.custom_field_values.detect{|cv| cv.custom_field.name==field_name}
  70 + cv.nil? ? false : cv.public
  71 + end
  72 +
  73 + def public_values
  74 + self.custom_field_values.select{|cv| cv.public}
  75 + end
  76 +
  77 + def custom_value(field_name)
  78 + cv = self.custom_field_values.detect{|cv| cv.custom_field.name==field_name}
  79 + cv.nil? ? default_value_for(field_name) : cv.value
  80 + end
  81 +
  82 + def default_value_for(field_name)
  83 + field=self.class.custom_fields(environment).detect {|c| c.name == field_name}
  84 + field.nil? ? nil : field.default_value
  85 + end
  86 +
  87 + def parse_custom_values
  88 + return_list = []
  89 + return return_list if custom_values.blank?
  90 + custom_values.each_pair do |key, value|
  91 + custom_field = environment.custom_fields.detect{|cf|cf.name==key}
  92 + next if custom_field.blank?
  93 + custom_field_value = self.custom_field_values.detect{|cv| cv.custom_field.name==key}
  94 +
  95 + if custom_field_value.nil?
  96 + custom_field_value = CustomFieldValue.new
  97 + custom_field_value.custom_field = custom_field
  98 + custom_field_value.customized = self
  99 + end
  100 +
  101 + if value.is_a?(Hash)
  102 + custom_field_value.value = value['value'].to_s
  103 + if value.has_key?('public')
  104 + is_public = value['public']=="true" || value['public']==true
  105 + custom_field_value.public = is_public
  106 + else
  107 + custom_field_value.public = false
  108 + end
  109 + else
  110 + custom_field_value.value = value.to_s
  111 + custom_field_value.public = false
  112 + end
  113 + return_list << custom_field_value
  114 + end
  115 + return_list
  116 + end
  117 +
  118 + def save_custom_values
  119 + parse_custom_values.each(&:save)
  120 + end
  121 +
  122 + end
  123 +end
  124 +
  125 +ActiveRecord::Base.send(:include, Customizable)
lib/noosfero/api/entities.rb
@@ -15,7 +15,7 @@ module Noosfero @@ -15,7 +15,7 @@ module Noosfero
15 } 15 }
16 16
17 def self.can_display? profile, options, field, permission = :friend 17 def self.can_display? profile, options, field, permission = :friend
18 - return true if profile.public_fields.include?(field) 18 + return true if profile.public_fields.map{|f| f.to_sym}.include?(field.to_sym)
19 current_person = options[:current_person] 19 current_person = options[:current_person]
20 20
21 current_permission = if current_person.present? 21 current_permission = if current_person.present?
@@ -31,7 +31,6 @@ module Noosfero @@ -31,7 +31,6 @@ module Noosfero
31 else 31 else
32 :anonymous 32 :anonymous
33 end 33 end
34 -  
35 PERMISSIONS[current_permission] <= PERMISSIONS[permission] 34 PERMISSIONS[current_permission] <= PERMISSIONS[permission]
36 end 35 end
37 36
@@ -84,6 +83,20 @@ module Noosfero @@ -84,6 +83,20 @@ module Noosfero
84 expose :identifier, :name, :id 83 expose :identifier, :name, :id
85 expose :created_at, :format_with => :timestamp 84 expose :created_at, :format_with => :timestamp
86 expose :updated_at, :format_with => :timestamp 85 expose :updated_at, :format_with => :timestamp
  86 + expose :additional_data do |profile, options|
  87 + hash ={}
  88 + profile.public_values.each do |value|
  89 + hash[value.custom_field.name]=value.value
  90 + end
  91 +
  92 + private_values = profile.custom_field_values - profile.public_values
  93 + private_values.each do |value|
  94 + if Entities.can_display?(profile,options,:custom_field)
  95 + hash[value.custom_field.name]=value.value
  96 + end
  97 + end
  98 + hash
  99 + end
87 expose :image, :using => Image 100 expose :image, :using => Image
88 expose :region, :using => Region 101 expose :region, :using => Region
89 end 102 end
lib/noosfero/api/v1/comments.rb
@@ -18,12 +18,12 @@ module Noosfero @@ -18,12 +18,12 @@ module Noosfero
18 article = find_article(environment.articles, params[:id]) 18 article = find_article(environment.articles, params[:id])
19 comments = select_filtered_collection_of(article, :comments, params) 19 comments = select_filtered_collection_of(article, :comments, params)
20 20
21 - present comments, :with => Entities::Comment 21 + present comments, :with => Entities::Comment, :current_person => current_person
22 end 22 end
23 23
24 get ":id/comments/:comment_id" do 24 get ":id/comments/:comment_id" do
25 article = find_article(environment.articles, params[:id]) 25 article = find_article(environment.articles, params[:id])
26 - present article.comments.find(params[:comment_id]), :with => Entities::Comment 26 + present article.comments.find(params[:comment_id]), :with => Entities::Comment, :current_person => current_person
27 end 27 end
28 28
29 # Example Request: 29 # Example Request:
@@ -31,7 +31,7 @@ module Noosfero @@ -31,7 +31,7 @@ module Noosfero
31 post ":id/comments" do 31 post ":id/comments" do
32 article = find_article(environment.articles, params[:id]) 32 article = find_article(environment.articles, params[:id])
33 options = params.select { |key,v| !['id','private_token'].include?(key) }.merge(:author => current_person, :source => article) 33 options = params.select { |key,v| !['id','private_token'].include?(key) }.merge(:author => current_person, :source => article)
34 - present Comment.create(options), :with => Entities::Comment 34 + present Comment.create(options), :with => Entities::Comment, :current_person => current_person
35 end 35 end
36 end 36 end
37 37
lib/noosfero/api/v1/communities.rb
@@ -20,14 +20,21 @@ module Noosfero @@ -20,14 +20,21 @@ module Noosfero
20 communities = select_filtered_collection_of(environment, 'communities', params) 20 communities = select_filtered_collection_of(environment, 'communities', params)
21 communities = communities.visible_for_person(current_person) 21 communities = communities.visible_for_person(current_person)
22 communities = communities.by_location(params) # Must be the last. May return Exception obj. 22 communities = communities.by_location(params) # Must be the last. May return Exception obj.
23 - present communities, :with => Entities::Community 23 + present communities, :with => Entities::Community, :current_person => current_person
24 end 24 end
25 25
26 26
27 # Example Request: 27 # Example Request:
28 # POST api/v1/communties?private_token=234298743290432&community[name]=some_name 28 # POST api/v1/communties?private_token=234298743290432&community[name]=some_name
  29 + # for each custom field for community, add &community[field_name]=field_value to the request
29 post do 30 post do
30 params[:community] ||= {} 31 params[:community] ||= {}
  32 +
  33 + params[:community][:custom_values]={}
  34 + params[:community].keys.each do |key|
  35 + params[:community][:custom_values][key]=params[:community].delete(key) if Community.custom_fields(environment).any?{|cf| cf.name==key}
  36 + end
  37 +
31 begin 38 begin
32 community = Community.create_after_moderation(current_person, params[:community].merge({:environment => environment})) 39 community = Community.create_after_moderation(current_person, params[:community].merge({:environment => environment}))
33 rescue 40 rescue
@@ -38,12 +45,12 @@ module Noosfero @@ -38,12 +45,12 @@ module Noosfero
38 render_api_errors!(community.errors.full_messages) 45 render_api_errors!(community.errors.full_messages)
39 end 46 end
40 47
41 - present community, :with => Entities::Community 48 + present community, :with => Entities::Community, :current_person => current_person
42 end 49 end
43 50
44 get ':id' do 51 get ':id' do
45 community = environment.communities.visible_for_person(current_person).find_by_id(params[:id]) 52 community = environment.communities.visible_for_person(current_person).find_by_id(params[:id])
46 - present community, :with => Entities::Community 53 + present community, :with => Entities::Community, :current_person => current_person
47 end 54 end
48 55
49 end 56 end
@@ -58,7 +65,7 @@ module Noosfero @@ -58,7 +65,7 @@ module Noosfero
58 person = environment.people.find(params[:person_id]) 65 person = environment.people.find(params[:person_id])
59 communities = select_filtered_collection_of(person, 'communities', params) 66 communities = select_filtered_collection_of(person, 'communities', params)
60 communities = communities.visible 67 communities = communities.visible
61 - present communities, :with => Entities::Community 68 + present communities, :with => Entities::Community, :current_person => current_person
62 end 69 end
63 70
64 end 71 end
lib/noosfero/api/v1/enterprises.rb
@@ -21,13 +21,13 @@ module Noosfero @@ -21,13 +21,13 @@ module Noosfero
21 enterprises = select_filtered_collection_of(environment, 'enterprises', params) 21 enterprises = select_filtered_collection_of(environment, 'enterprises', params)
22 enterprises = enterprises.visible_for_person(current_person) 22 enterprises = enterprises.visible_for_person(current_person)
23 enterprises = enterprises.by_location(params) # Must be the last. May return Exception obj. 23 enterprises = enterprises.by_location(params) # Must be the last. May return Exception obj.
24 - present enterprises, :with => Entities::Enterprise 24 + present enterprises, :with => Entities::Enterprise, :current_person => current_person
25 end 25 end
26 26
27 desc "Return one enterprise by id" 27 desc "Return one enterprise by id"
28 get ':id' do 28 get ':id' do
29 enterprise = environment.enterprises.visible_for_person(current_person).find_by_id(params[:id]) 29 enterprise = environment.enterprises.visible_for_person(current_person).find_by_id(params[:id])
30 - present enterprise, :with => Entities::Enterprise 30 + present enterprise, :with => Entities::Enterprise, :current_person => current_person
31 end 31 end
32 32
33 end 33 end
@@ -42,7 +42,7 @@ module Noosfero @@ -42,7 +42,7 @@ module Noosfero
42 person = environment.people.find(params[:person_id]) 42 person = environment.people.find(params[:person_id])
43 enterprises = select_filtered_collection_of(person, 'enterprises', params) 43 enterprises = select_filtered_collection_of(person, 'enterprises', params)
44 enterprises = enterprises.visible.by_location(params) 44 enterprises = enterprises.visible.by_location(params)
45 - present enterprises, :with => Entities::Enterprise 45 + present enterprises, :with => Entities::Enterprise, :current_person => current_person
46 end 46 end
47 47
48 end 48 end
lib/noosfero/api/v1/people.rb
@@ -33,30 +33,31 @@ module Noosfero @@ -33,30 +33,31 @@ module Noosfero
33 get do 33 get do
34 people = select_filtered_collection_of(environment, 'people', params) 34 people = select_filtered_collection_of(environment, 'people', params)
35 people = people.visible_for_person(current_person) 35 people = people.visible_for_person(current_person)
36 - present people, :with => Entities::Person 36 + present people, :with => Entities::Person, :current_person => current_person
37 end 37 end
38 38
39 desc "Return the logged user information" 39 desc "Return the logged user information"
40 get "/me" do 40 get "/me" do
41 - present current_person, :with => Entities::Person 41 + present current_person, :with => Entities::Person, :current_person => current_person
42 end 42 end
43 43
44 desc "Return the person information" 44 desc "Return the person information"
45 get ':id' do 45 get ':id' do
46 person = environment.people.visible_for_person(current_person).find_by_id(params[:id]) 46 person = environment.people.visible_for_person(current_person).find_by_id(params[:id])
47 return not_found! if person.blank? 47 return not_found! if person.blank?
48 - present person, :with => Entities::Person 48 + present person, :with => Entities::Person, :current_person => current_person
49 end 49 end
50 50
51 desc "Update person information" 51 desc "Update person information"
52 post ':id' do 52 post ':id' do
53 return forbidden! if current_person.id.to_s != params[:id] 53 return forbidden! if current_person.id.to_s != params[:id]
54 current_person.update_attributes!(params[:person]) 54 current_person.update_attributes!(params[:person])
55 - present current_person, :with => Entities::Person 55 + present current_person, :with => Entities::Person, :current_person => current_person
56 end 56 end
57 57
58 # Example Request: 58 # Example Request:
59 # POST api/v1/people?person[login]=some_login&person[password]=some_password&person[name]=Jack 59 # POST api/v1/people?person[login]=some_login&person[password]=some_password&person[name]=Jack
  60 + # for each custom field for person, add &person[field_name]=field_value to the request
60 desc "Create person" 61 desc "Create person"
61 post do 62 post do
62 user_data = {} 63 user_data = {}
@@ -64,14 +65,21 @@ module Noosfero @@ -64,14 +65,21 @@ module Noosfero
64 user_data[:email] = params[:person].delete(:email) 65 user_data[:email] = params[:person].delete(:email)
65 user_data[:password] = params[:person].delete(:password) 66 user_data[:password] = params[:person].delete(:password)
66 user_data[:password_confirmation] = params[:person].delete(:password_confirmation) 67 user_data[:password_confirmation] = params[:person].delete(:password_confirmation)
  68 +
  69 + params[:person][:custom_values]={}
  70 + params[:person].keys.each do |key|
  71 + params[:person][:custom_values][key]=params[:person].delete(key) if Person.custom_fields(environment).any?{|cf| cf.name==key}
  72 + end
  73 +
67 user = User.build(user_data, params[:person], environment) 74 user = User.build(user_data, params[:person], environment)
  75 +
68 begin 76 begin
69 user.signup! 77 user.signup!
70 rescue ActiveRecord::RecordInvalid 78 rescue ActiveRecord::RecordInvalid
71 render_api_errors!(user.errors.full_messages) 79 render_api_errors!(user.errors.full_messages)
72 end 80 end
73 81
74 - present user.person, :with => Entities::Person 82 + present user.person, :with => Entities::Person, :current_person => user.person
75 end 83 end
76 84
77 desc "Return the person friends" 85 desc "Return the person friends"
@@ -79,7 +87,7 @@ module Noosfero @@ -79,7 +87,7 @@ module Noosfero
79 person = environment.people.visible_for_person(current_person).find_by_id(params[:id]) 87 person = environment.people.visible_for_person(current_person).find_by_id(params[:id])
80 return not_found! if person.blank? 88 return not_found! if person.blank?
81 friends = person.friends.visible 89 friends = person.friends.visible
82 - present friends, :with => Entities::Person 90 + present friends, :with => Entities::Person, :current_person => current_person
83 end 91 end
84 92
85 desc "Return the person permissions on other profiles" 93 desc "Return the person permissions on other profiles"
public/javascripts/manage-fields.js
@@ -36,6 +36,34 @@ function signup_action(name_active, name_required, name_signup) { @@ -36,6 +36,34 @@ function signup_action(name_active, name_required, name_signup) {
36 update_active(name_active, name_required, name_signup) 36 update_active(name_active, name_required, name_signup)
37 } 37 }
38 38
  39 +function add_content(target_id, content, mask) {
  40 + var id = new Date().getTime();
  41 + var regexp = new RegExp(mask, "g");
  42 + content = content.replace(regexp, id);
  43 + $(target_id).append(content);
  44 + $('#' + id).hide().slideDown();
  45 +}
  46 +
  47 +function remove_content(target) {
  48 + $(target).remove();
  49 +}
  50 +
  51 +function submit_custom_field_form(selector_id, form_id, customized_type) {
  52 + $(selector_id).attr('disabled', true);
  53 + $(form_id).submit();
  54 +}
  55 +
  56 +function manage_default_option(source) {
  57 + var th = $(source);
  58 + var name = th.prop('name');
  59 + if(th.is(':checked')){
  60 + $(':checkbox[name="' + name + '"]').not($(source)).prop('checked',false);
  61 + }
  62 +}
  63 +
  64 +function update_default_value(source, target) {
  65 + $(target).val(source);
  66 +}
39 67
40 jQuery(document).ready(function(){ 68 jQuery(document).ready(function(){
41 function check_fields(check, table_id, start) { 69 function check_fields(check, table_id, start) {
public/stylesheets/manage-fields.scss
@@ -9,3 +9,11 @@ @@ -9,3 +9,11 @@
9 font-style: italic; 9 font-style: italic;
10 } 10 }
11 11
  12 +.custom-field-item {
  13 + position: relative;
  14 + a.icon-delete {
  15 + position: absolute;
  16 + top: 20px;
  17 + right: 20px;
  18 + }
  19 +}
test/functional/features_controller_test.rb
@@ -8,6 +8,7 @@ class FeaturesControllerTest &lt; ActionController::TestCase @@ -8,6 +8,7 @@ class FeaturesControllerTest &lt; ActionController::TestCase
8 @controller = FeaturesController.new 8 @controller = FeaturesController.new
9 @request = ActionController::TestRequest.new 9 @request = ActionController::TestRequest.new
10 @response = ActionController::TestResponse.new 10 @response = ActionController::TestResponse.new
  11 +
11 login_as(create_admin_user(Environment.find(2))) 12 login_as(create_admin_user(Environment.find(2)))
12 end 13 end
13 14
@@ -159,4 +160,51 @@ class FeaturesControllerTest &lt; ActionController::TestCase @@ -159,4 +160,51 @@ class FeaturesControllerTest &lt; ActionController::TestCase
159 assert_includes json_response, {"id"=>person.id, "name"=>person.name} 160 assert_includes json_response, {"id"=>person.id, "name"=>person.name}
160 end 161 end
161 162
  163 + should 'create custom field' do
  164 + uses_host 'anhetegua.net'
  165 + assert_nil Environment.find(2).custom_fields.find_by_name('foo')
  166 + post :manage_custom_fields, :customized_type => 'Person', :custom_fields => {
  167 + Time.now.to_i => {
  168 + :name => 'foo',
  169 + :default_value => 'foobar',
  170 + :format => 'string',
  171 + :customized_type => 'Person',
  172 + :active => true,
  173 + :required => true,
  174 + :signup => true
  175 + }
  176 + }
  177 + assert_redirected_to :action => 'manage_fields'
  178 + assert_not_nil Environment.find(2).custom_fields.find_by_name('foo')
  179 + end
  180 +
  181 + should 'update custom field' do
  182 + uses_host 'anhetegua.net'
  183 +
  184 + field = CustomField.create! :name => 'foo', :default_value => 'foobar', :format => 'string', :extras => '', :customized_type => 'Enterprise', :active => true, :required => true, :signup => true, :environment => Environment.find(2)
  185 + post :manage_custom_fields, :customized_type => 'Enterprise', :custom_fields => {
  186 + field.id => {
  187 + :name => 'foo bar',
  188 + :default_value => 'foobar',
  189 + :active => true,
  190 + :required => true,
  191 + :signup => true
  192 + }
  193 + }
  194 + field.reload
  195 + assert_redirected_to :action => 'manage_fields'
  196 + assert_equal 'foo bar', field.name
  197 + end
  198 +
  199 + should 'destroy custom field' do
  200 + uses_host 'anhetegua.net'
  201 +
  202 + field = CustomField.create! :name => 'foo', :default_value => 'foobar', :format => 'string', :extras => '', :customized_type => 'Enterprise', :active => true, :required => true, :signup => true, :environment => Environment.find(2)
  203 +
  204 + post :manage_custom_fields, :customized_type => 'Enterprise'
  205 +
  206 + assert_redirected_to :action => 'manage_fields'
  207 + assert_nil Environment.find(2).custom_fields.find_by_name('foo')
  208 + end
  209 +
162 end 210 end
test/unit/acts_as_customizable_test.rb 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +require_relative "../test_helper"
  2 +
  3 +class ActsAsCustomizableTest < ActiveSupport::TestCase
  4 +
  5 + should 'save custom field values for person' do
  6 + CustomField.create!(:name => "Blog", :format => "string", :customized_type => "Person", :active => true, :environment => Environment.default)
  7 + person = create_user('testinguser').person
  8 + assert_difference 'CustomFieldValue.count' do
  9 + person.custom_values = { "Blog" => { "value" => "www.blog.org", "public" => "0"} }
  10 + person.save!
  11 + assert_equal 'www.blog.org', CustomFieldValue.find(:last, :conditions => {:customized_id => person.id}).value
  12 + end
  13 + end
  14 +
  15 + should 'not be valid when required custom field not filled' do
  16 + CustomField.create!(:name => "Blog", :format => "string", :customized_type => "Person", :active => true, :environment => Environment.default, :required => true)
  17 + person = create_user('testinguser').person
  18 +
  19 + person.custom_values = { "Blog" => { "value" => "", "public" => "0"} }
  20 + refute person.valid?
  21 + end
  22 +
  23 +end
test/unit/api/people_test.rb
@@ -165,4 +165,44 @@ class PeopleTest &lt; ActiveSupport::TestCase @@ -165,4 +165,44 @@ class PeopleTest &lt; ActiveSupport::TestCase
165 assert_equal another_name, person.name 165 assert_equal another_name, person.name
166 end 166 end
167 167
  168 + should 'display public custom fields' do
  169 + CustomField.create!(:name => "Custom Blog", :format => "string", :customized_type => "Person", :active => true, :environment => Environment.default)
  170 + some_person = create_user('some-person').person
  171 + some_person.custom_values = { "Custom Blog" => { "value" => "www.blog.org", "public" => "true"} }
  172 + some_person.save!
  173 +
  174 + get "/api/v1/people/#{some_person.id}?#{params.to_query}"
  175 + json = JSON.parse(last_response.body)
  176 + assert json['person']['additional_data'].has_key?('Custom Blog')
  177 + assert_equal "www.blog.org", json['person']['additional_data']['Custom Blog']
  178 + end
  179 +
  180 + should 'not display non-public custom fields' do
  181 + CustomField.create!(:name => "Custom Blog", :format => "string", :customized_type => "Person", :active => true, :environment => Environment.default)
  182 + some_person = create_user('some-person').person
  183 + some_person.custom_values = { "Custom Blog" => { "value" => "www.blog.org", "public" => "0"} }
  184 + some_person.save!
  185 +
  186 + get "/api/v1/people/#{some_person.id}?#{params.to_query}"
  187 + json = JSON.parse(last_response.body)
  188 + assert_equal json['person']['additional_data'], {}
  189 + end
  190 +
  191 + should 'display non-public custom fields to friend' do
  192 + CustomField.create!(:name => "Custom Blog", :format => "string", :customized_type => "Person", :active => true, :environment => Environment.default)
  193 + some_person = create_user('some-person').person
  194 + some_person.custom_values = { "Custom Blog" => { "value" => "www.blog.org", "public" => "0"} }
  195 + some_person.save!
  196 +
  197 + f = Friendship.new
  198 + f.friend = some_person
  199 + f.person = person
  200 + f.save!
  201 +
  202 + get "/api/v1/people/#{some_person.id}?#{params.to_query}"
  203 + json = JSON.parse(last_response.body)
  204 + assert json['person']['additional_data'].has_key?("Custom Blog")
  205 + assert_equal "www.blog.org", json['person']['additional_data']['Custom Blog']
  206 + end
  207 +
168 end 208 end
test/unit/custom_field_test.rb 0 → 100644
@@ -0,0 +1,153 @@ @@ -0,0 +1,153 @@
  1 +require_relative "../test_helper"
  2 +
  3 +class CustomFieldTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @person = create_user('test_user').person
  7 +
  8 + @e1 = Environment.default
  9 + @e2 = fast_create(Environment)
  10 +
  11 + @community = create(Community, :environment => @e1, :name => 'my new community')
  12 +
  13 + @community_custom_field = CustomField.create(:name => "community_field", :format=>"myFormat", :default_value => "value for community", :customized_type=>"Community", :active => true, :environment => @e1)
  14 + @person_custom_field = CustomField.create(:name => "person_field", :format=>"myFormat", :default_value => "value for person", :customized_type=>"Person", :active => true, :environment => @e1)
  15 + @profile_custom_field = CustomField.create(:name => "profile_field", :format=>"myFormat", :default_value => "value for any profile", :customized_type=>"Profile", :active => true, :environment => @e1)
  16 +
  17 + @e1.reload
  18 + end
  19 +
  20 + should 'not access another environments custom fields' do
  21 + @e2_custom_field = CustomField.create(:name => "another_field", :format=>"anoherFormat", :default_value => "default value for e2", :customized_type=>"Profile", :active => true, :environment => @e2)
  22 + @e2.reload
  23 +
  24 + assert_equal 1, Profile.custom_fields(@e1).size
  25 + assert_equal @profile_custom_field, Profile.custom_fields(@e1).first
  26 +
  27 + assert_equal 1, Profile.custom_fields(@e2).size
  28 + assert_equal @e2_custom_field, Profile.custom_fields(@e2).first
  29 +
  30 + end
  31 +
  32 + should 'no access to custom field on sibling' do
  33 + refute (Person.custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name})
  34 + refute (Community.custom_fields(@e1).any?{|cf| cf.name == @person_custom_field.name})
  35 + end
  36 +
  37 + should 'inheritance of custom_field' do
  38 + assert Community.custom_fields(@e1).any?{|cf| cf.name == @profile_custom_field.name}
  39 + assert Person.custom_fields(@e1).any?{|cf| cf.name == @profile_custom_field.name}
  40 + end
  41 +
  42 + should 'save custom_field_values' do
  43 + @community.custom_values = {"community_field" => "new_value!", "profile_field"=> "another_value!"}
  44 + @community.save
  45 +
  46 + assert CustomFieldValue.all.any?{|cv| cv.custom_field_id == @community_custom_field.id && cv.customized_id == @community.id && cv.value == "new_value!"}
  47 + assert CustomFieldValue.all.any?{|cv| cv.custom_field_id == @profile_custom_field.id && cv.customized_id == @community.id && cv.value = "another_value!"}
  48 + end
  49 +
  50 + should 'delete custom field and its values' do
  51 + @community.custom_values = {"community_field" => "new_value!", "profile_field"=> "another_value!"}
  52 + @community.save
  53 +
  54 + old_id = @community_custom_field.id
  55 + @community_custom_field.destroy
  56 +
  57 + @e1.reload
  58 + refute (@e1.custom_fields.any?{|cf| cf.id == old_id})
  59 + refute (Community.custom_fields(@e1).any?{|cf| cf.name == "community_field"})
  60 + refute (CustomFieldValue.all.any?{|cv| cv.custom_field_id == old_id})
  61 + end
  62 +
  63 + should 'not save related custom field' do
  64 + another_field = CustomField.create(:name => "profile_field", :format=>"myFormat", :default_value => "value for any profile", :customized_type=>"Community", :environment => @e1)
  65 + assert another_field.id.nil?
  66 + end
  67 +
  68 + should 'not save same custom field twice in the same environment' do
  69 + field = CustomField.create(:name => "the new field", :format=>"myFormat", :customized_type=>"Community", :environment => @e1)
  70 + refute field.id.nil?
  71 + @e1.reload
  72 + another = CustomField.new(:name => "the new field", :format=>"myFormat", :customized_type=>"Community", :environment => @e1)
  73 + refute another.valid?
  74 + end
  75 +
  76 + should 'save same custom field in another environment' do
  77 + field = CustomField.create(:name => "the new field", :format=>"myFormat", :customized_type=>"Community", :environment => @e1)
  78 + refute field.id.nil?
  79 + another_field = CustomField.create(:name => "the new field", :format=>"myFormat", :customized_type=>"Community", :environment => @e2)
  80 + refute another_field.id.nil?
  81 + end
  82 +
  83 + should 'not return inactive fields' do
  84 + @community_custom_field.update_attributes(:active=>false)
  85 + @e1.reload
  86 + refute Community.active_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
  87 + end
  88 +
  89 + should 'delete a model and its custom field values' do
  90 + @community.custom_values = {"community_field" => "new_value!", "profile_field"=> "another_value!"}
  91 + @community.save
  92 +
  93 + old_id = @community.id
  94 + @community.destroy
  95 + refute (Community.all.any?{|c| c.id == old_id})
  96 + refute (CustomFieldValue.all.any?{|cv| cv.customized_id == old_id && cv.customized_type == "Community"})
  97 + end
  98 +
  99 + should 'keep field value if the field is reactivated' do
  100 +
  101 + @community.custom_values = {"community_field" => "new_value!"}
  102 + @community.save
  103 +
  104 + @community_custom_field.update_attributes(:active=>false)
  105 + @e1.reload
  106 + refute Community.active_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
  107 +
  108 + @community_custom_field.update_attributes(:active=>true)
  109 +
  110 + @e1.reload
  111 + @community.reload
  112 + assert Community.active_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
  113 + assert_equal @community.custom_value("community_field"), "new_value!"
  114 + end
  115 +
  116 + should 'list of required fields' do
  117 + refute Community.required_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
  118 +
  119 + @community_custom_field.update_attributes(:required=>true)
  120 + @community.reload
  121 + @e1.reload
  122 + assert Community.required_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
  123 + end
  124 +
  125 + should 'list of signup fields' do
  126 + refute Community.signup_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
  127 +
  128 + @community_custom_field.update_attributes(:signup=>true)
  129 + @community.reload
  130 + @e1.reload
  131 + assert Community.signup_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
  132 + end
  133 +
  134 + should 'public values handling' do
  135 + refute @community.is_public("community_field")
  136 + @community.custom_values = {"community_field" => {"value" => "new_value!", "public"=>"true"}, "profile_field"=> "another_value!"}
  137 + @community.save
  138 + @community.reload
  139 +
  140 + assert @community.is_public("community_field")
  141 + refute @community.is_public("profile_field")
  142 + end
  143 +
  144 + should 'complete list of fields' do
  145 + assert Person.custom_fields(@e1).include? @profile_custom_field
  146 + assert Person.custom_fields(@e1).include? @person_custom_field
  147 + end
  148 +
  149 + should 'get correct customized ancestors list' do
  150 + assert (Person.customized_ancestors_list-["Person","Profile"]).blank?
  151 + end
  152 +end
  153 +
test/unit/custom_field_values_test.rb 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +require_relative "../test_helper"
  2 +
  3 +class CustomFieldValuesTest < ActiveSupport::TestCase
  4 +
  5 + should 'custom field value not be valid' do
  6 + c = CustomField.create!(:name => "Blog", :format => "string", :customized_type => "Person", :active => true, :required => true, :environment => Environment.default)
  7 + person = create_user('testinguser').person
  8 +
  9 + cv=CustomFieldValue.new(:customized => person, :custom_field => c, :value => "")
  10 + refute cv.valid?
  11 + end
  12 +end