Commit 67e8ceed271f41557f5b3e17725927058adf0fee

Authored by Rodrigo Souto
2 parents 0e26cea3 146786cb

Merge branch 'master' into pluginize-solr

INSTALL.varnish
... ... @@ -45,6 +45,10 @@ Install the RPAF apache module (or skip this step if not using apache):
45 45 On manual installations, change "/etc/noosfero/*" to
46 46 "{Rails.root}/etc/noosfero/*"
47 47  
  48 +NOTE: it is very important that the *.vcl files are included in that order,
  49 +i.e. *first* include "varnish-noosfero.vcl", and *after*
  50 +"noosfero-accept-language.cvl".
  51 +
48 52 4c) Restart Varnish
49 53  
50 54 # invoke-rc.d varnish restart
... ...
app/controllers/admin/environment_design_controller.rb
... ... @@ -3,7 +3,7 @@ class EnvironmentDesignController < BoxOrganizerController
3 3 protect 'edit_environment_design', :environment
4 4  
5 5 def available_blocks
6   - @available_blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock ]
  6 + @available_blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ]
7 7 end
8 8  
9 9 end
... ...
app/controllers/public/catalog_controller.rb
... ... @@ -7,7 +7,7 @@ class CatalogController < PublicController
7 7 def index
8 8 @category = params[:level] ? ProductCategory.find(params[:level]) : nil
9 9 @products = @profile.products.from_category(@category).paginate(:order => 'available desc, highlighted desc, name asc', :per_page => 9, :page => params[:page])
10   - @categories = ProductCategory.on_level(params[:level])
  10 + @categories = ProductCategory.on_level(params[:level]).order(:name)
11 11 end
12 12  
13 13 protected
... ...
app/helpers/catalog_helper.rb
... ... @@ -21,7 +21,7 @@ module CatalogHelper
21 21  
22 22 def category_sub_links(category)
23 23 sub_categories = []
24   - category.children.each do |sub_category|
  24 + category.children.order(:name).each do |sub_category|
25 25 sub_categories << category_link(sub_category, true)
26 26 end
27 27 content_tag('ul', sub_categories) if sub_categories.size > 1
... ...
app/models/spammer_logger.rb
... ... @@ -5,10 +5,10 @@ class SpammerLogger &lt; Logger
5 5 def self.log(spammer_ip, object=nil)
6 6 if object
7 7 if object.kind_of?(Comment)
8   - @logger << "[#{Time.now.strftime("%F %T %z")}] Comment-id: #{object.id} IP: #{spammer_ip}\n"
  8 + @logger << "[#{Time.now.strftime('%F %T %z')}] Comment-id: #{object.id} IP: #{spammer_ip}\n"
9 9 end
10 10 else
11   - @logger << "[#{Time.now.strftime("%F %T %z")}] IP: #{spammer_ip}\n"
  11 + @logger << "[#{Time.now.strftime('%F %T %z')}] IP: #{spammer_ip}\n"
12 12 end
13 13 end
14 14  
... ...
app/models/tags_block.rb
... ... @@ -20,7 +20,8 @@ class TagsBlock &lt; Block
20 20 end
21 21  
22 22 def content(args={})
23   - tags = owner.article_tags
  23 + is_env = owner.class == Environment
  24 + tags = is_env ? owner.tag_counts : owner.article_tags
24 25 return '' if tags.empty?
25 26  
26 27 if limit
... ... @@ -29,18 +30,28 @@ class TagsBlock &lt; Block
29 30 tags_tmp.map{ |k,v| tags[k] = v }
30 31 end
31 32  
  33 + url = is_env ? {:host=>owner.default_hostname, :controller=>'search', :action => 'tag'} :
  34 + owner.public_profile_url.merge(:controller => 'profile', :action => 'tags')
  35 + tagname_option = is_env ? :tag : :id
  36 +
32 37 block_title(title) +
33 38 "\n<div class='tag_cloud'>\n"+
34   - tag_cloud( tags, :id,
35   - owner.public_profile_url.merge(:controller => 'profile', :action => 'tags'),
36   - :max_size => 16, :min_size => 9 ) +
  39 + tag_cloud( tags, tagname_option, url, :max_size => 16, :min_size => 9 ) +
37 40 "\n</div><!-- end class='tag_cloud' -->\n";
38 41 end
39 42  
40 43 def footer
41   - owner_id = owner.identifier
42   - lambda do
43   - link_to s_('tags|View all'), :profile => owner_id, :controller => 'profile', :action => 'tags'
  44 + if owner.class == Environment
  45 + lambda do
  46 + link_to s_('tags|View all'),
  47 + :controller => 'search', :action => 'tags'
  48 + end
  49 + else
  50 + owner_id = owner.identifier
  51 + lambda do
  52 + link_to s_('tags|View all'),
  53 + :profile => owner_id, :controller => 'profile', :action => 'tags'
  54 + end
44 55 end
45 56 end
46 57  
... ...
app/models/user.rb
... ... @@ -15,7 +15,7 @@ class User &lt; ActiveRecord::Base
15 15 # FIXME ugly workaround
16 16 def self.human_attribute_name(attrib)
17 17 case attrib.to_sym
18   - when :login: return _('Username')
  18 + when :login: return [_('Username'), _('Email')].join(' / ')
19 19 when :email: return _('e-Mail')
20 20 else _(self.superclass.human_attribute_name(attrib))
21 21 end
... ... @@ -116,10 +116,11 @@ class User &lt; ActiveRecord::Base
116 116  
117 117 validates_inclusion_of :terms_accepted, :in => [ '1' ], :if => lambda { |u| ! u.terms_of_use.blank? }, :message => N_('%{fn} must be checked in order to signup.').fix_i18n
118 118  
119   - # Authenticates a user by their login name and unencrypted password. Returns the user or nil.
  119 + # Authenticates a user by their login name or email and unencrypted password. Returns the user or nil.
120 120 def self.authenticate(login, password, environment = nil)
121 121 environment ||= Environment.default
122   - u = first :conditions => ['login = ? AND environment_id = ? AND activated_at IS NOT NULL', login, environment.id] # need to get the salt
  122 + u = self.first :conditions => ['(login = ? OR email = ?) AND environment_id = ? AND activated_at IS NOT NULL',
  123 + login, login, environment.id] # need to get the salt
123 124 u && u.authenticated?(password) ? u : nil
124 125 end
125 126  
... ...
app/views/account/_signup_form.rhtml
... ... @@ -32,7 +32,8 @@
32 32 <span id="signup-domain"><%= environment.default_hostname %>/</span>
33 33 <div id='signup-login'>
34 34 <div id='signup-login-field' class='formfield'>
35   - <%= required text_field(:user, :login, :id => 'user_login', :onchange => 'this.value = convToValidLogin(this.value);') %>
  35 + <%= required text_field(:user, :login, :id => 'user_login',
  36 + :onchange => 'this.value = convToValidUsername(this.value);') %>
36 37 <div id='url-check'><p>&nbsp;</p></div>
37 38 </div>
38 39 <%= content_tag(:small, _('Choose your login name carefully! It will be your network access and you will not be able to change it later.'), :id => 'signup-balloon') %>
... ...
app/views/account/forgot_password.rhtml
... ... @@ -5,7 +5,7 @@
5 5 <% labelled_form_for :change_password, @change_password, :url => { :action => 'forgot_password' } do |f| %>
6 6  
7 7 <%= f.text_field :login,
8   - :onchange => 'this.value = convToValidLogin( this.value )' %>
  8 + :onchange => 'this.value = convToValidUsername( this.value )' %>
9 9  
10 10 <%= f.text_field :email %>
11 11  
... ...
app/views/catalog/index.rhtml
... ... @@ -7,13 +7,15 @@
7 7  
8 8 <div class='l-sidebar-left-bar'>
9 9 <ul>
10   - <% if @categories.size > 0 %>
  10 + <% if @categories.present? %>
11 11 <% @categories.each do |category| %>
12 12 <%= category_link(category) %>
13 13 <%= category_sub_links(category) %>
14 14 <% end %>
  15 + <% elsif @category.present? %>
  16 + <%= content_tag('li', _('There are no sub-categories for %s') % @category.name, :id => 'catalog-categories-notice') %>
15 17 <% else %>
16   - <%= content_tag('li', _('There are no sub-categories for %s') % @category.name, :style => 'color: #555753; padding-bottom: 0.5em;') %>
  18 + <%= content_tag('li', _('There are no categories available.'), :id => 'catalog-categories-notice') %>
17 19 <% end %>
18 20 </ul>
19 21 </div>
... ...
app/views/search/_image.html.erb
... ... @@ -28,8 +28,9 @@
28 28 <div class="search-gallery-items">
29 29 <% r = image.children.find(:all, :order => :updated_at, :conditions => ['type = ?', 'UploadedFile']).last(3) %>
30 30 <% if r.length > 0 %>
31   - <% r.each do |i| %>
32   - <%= link_to '', i.view_url, :class => "search-image-pic", :style => 'background-image: url(%s)'% i.public_filename(:thumb) %>
  31 + <% r.each_index do |i| img = r[i] %>
  32 + <%= link_to '', img.view_url, :class => "search-image-pic pic-num#{i+1}",
  33 + :style => 'background-image: url(%s)'% img.public_filename(:thumb) %>
33 34 <% end %>
34 35 <% else %>
35 36 <div class="search-no-image"><span><%= _('No image') %></span></div>
... ...
db/migrate/20130304200849_add_default_value_to_product_highlighted.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +class AddDefaultValueToProductHighlighted < ActiveRecord::Migration
  2 + def self.up
  3 + change_column :products, :highlighted, :boolean, :default => false
  4 + execute('UPDATE products SET highlighted=(0>1) WHERE highlighted IS NULL;')
  5 + end
  6 +
  7 + def self.down
  8 + say 'This migraiton is not reversible!'
  9 + end
  10 +end
... ...
db/schema.rb
... ... @@ -9,7 +9,7 @@
9 9 #
10 10 # It's strongly recommended to check this file into your version control system.
11 11  
12   -ActiveRecord::Schema.define(:version => 20130111232201) do
  12 +ActiveRecord::Schema.define(:version => 20130304200849) do
13 13  
14 14 create_table "abuse_reports", :force => true do |t|
15 15 t.integer "reporter_id"
... ... @@ -415,7 +415,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130111232201) do
415 415 t.datetime "updated_at"
416 416 t.decimal "discount"
417 417 t.boolean "available", :default => true
418   - t.boolean "highlighted"
  418 + t.boolean "highlighted", :default => false
419 419 t.integer "unit_id"
420 420 t.integer "image_id"
421 421 end
... ...
debian/changelog
  1 +noosfero (0.41.1) unstable; urgency=low
  2 +
  3 + * Bugfixes release
  4 +
  5 + -- Rodrigo Souto <rodrigo@colivre.coop.br> Fri, 08 Mar 2013 11:33:11 -0300
  6 +
1 7 noosfero (0.41.0) unstable; urgency=low
2 8  
3 9 * Features version with anti spam-bot measures
... ...
features/browse_catalogs.feature
... ... @@ -18,7 +18,7 @@ Feature: browse catalogs
18 18  
19 19 Scenario: display titles
20 20 Then I should see "Associação de Artesanato de Bonito"
21   - And I should see "Products/Services" within "#product-list"
  21 + And I should see "Products/Services"
22 22  
23 23 Scenario: display the simplest possible product
24 24 Given the following products
... ...
features/signup.feature
... ... @@ -3,6 +3,7 @@ Feature: signup
3 3 I want to sign up to the site
4 4 So I can have fun using its features
5 5  
  6 +@selenium
6 7 Scenario: successfull registration
7 8 Given I am on the homepage
8 9 When I follow "Login"
... ... @@ -13,6 +14,7 @@ Feature: signup
13 14 | Password | secret |
14 15 | Password confirmation | secret |
15 16 | Full name | José da Silva |
  17 + And wait for the captcha signup time
16 18 And I press "Create my account"
17 19 Then I should not be logged in
18 20 And I should receive an e-mail on josesilva@example.com
... ... @@ -36,6 +38,7 @@ Feature: signup
36 38 And I go to signup page
37 39 Then I should be on Joao Silva's control panel
38 40  
  41 + @selenium
39 42 Scenario: user cannot register without a name
40 43 Given I am on the homepage
41 44 And I follow "Login"
... ... @@ -44,6 +47,7 @@ Feature: signup
44 47 And I fill in "Username" with "josesilva"
45 48 And I fill in "Password" with "secret"
46 49 And I fill in "Password confirmation" with "secret"
  50 + And wait for the captcha signup time
47 51 And I press "Create my account"
48 52 Then I should see "Name can't be blank"
49 53  
... ...
features/step_definitions/noosfero_steps.rb
... ... @@ -538,6 +538,7 @@ end
538 538  
539 539 Then /^I should receive an e-mail on (.*)$/ do |address|
540 540 last_mail = ActionMailer::Base.deliveries.last
  541 + last_mail.nil?.should be_false
541 542 last_mail['to'].to_s.should == address
542 543 end
543 544  
... ... @@ -748,3 +749,8 @@ Given /^the profile (.*) is configured to (.*) after login$/ do |profile, option
748 749 profile.redirection_after_login = redirection
749 750 profile.save
750 751 end
  752 +
  753 +When /^wait for the captcha signup time$/ do
  754 + environment = Environment.default
  755 + sleep environment.min_signup_delay + 1
  756 +end
... ...
gitignore.example
... ... @@ -11,6 +11,7 @@ config/mongrel_cluster.yml
11 11 config/solr.yml
12 12 config/plugins
13 13 config/thin.yml
  14 +config/local.rb
14 15 index
15 16 locale
16 17 log
... ...
lib/noosfero.rb
... ... @@ -2,7 +2,7 @@ require &#39;fast_gettext&#39;
2 2  
3 3 module Noosfero
4 4 PROJECT = 'noosfero'
5   - VERSION = '0.41.0'
  5 + VERSION = '0.41.1'
6 6  
7 7 def self.pattern_for_controllers_in_directory(dir)
8 8 disjunction = controllers_in_directory(dir).join('|')
... ...
lib/noosfero/core_ext/string.rb
... ... @@ -11,12 +11,52 @@ class String
11 11 [ 'ó', 'ò', 'ô', 'ö', 'õ', 'º' ] => 'o',
12 12 [ 'Ú', 'Ù', 'Û', 'Ü' ] => 'U',
13 13 [ 'ú', 'ù', 'û', 'ü' ] => 'u',
  14 + [ 'ß' ] => 'ss',
14 15 [ 'Ç' ] => 'C',
15 16 [ 'ç' ] => 'c',
16 17 [ 'Ñ' ] => 'N',
17 18 [ 'ñ' ] => 'n',
18 19 [ 'Ÿ' ] => 'Y',
19 20 [ 'ÿ' ] => 'y',
  21 +# Cyrillic alphabet transliteration
  22 + [ 'а', 'А' ] => 'a',
  23 + [ 'б', 'Б' ] => 'b',
  24 + [ 'в', 'В' ] => 'v',
  25 + [ 'г', 'Г' ] => 'g',
  26 + [ 'д', 'Д' ] => 'd',
  27 + [ 'е', 'Е' ] => 'e',
  28 + [ 'ё', 'Ё' ] => 'yo',
  29 + [ 'ж', 'Ж' ] => 'zh',
  30 + [ 'з', 'З' ] => 'z',
  31 + [ 'и', 'И' ] => 'i',
  32 + [ 'й', 'Й' ] => 'y',
  33 + [ 'к', 'К' ] => 'k',
  34 + [ 'л', 'Л' ] => 'l',
  35 + [ 'м', 'М' ] => 'm',
  36 + [ 'н', 'Н' ] => 'n',
  37 + [ 'о', 'О' ] => 'o',
  38 + [ 'п', 'П' ] => 'p',
  39 + [ 'р', 'Р' ] => 'r',
  40 + [ 'с', 'С' ] => 's',
  41 + [ 'т', 'Т' ] => 't',
  42 + [ 'у', 'У' ] => 'u',
  43 + [ 'ф', 'Ф' ] => 'f',
  44 + [ 'х', 'Х' ] => 'h',
  45 + [ 'ц', 'Ц' ] => 'ts',
  46 + [ 'ч', 'Ч' ] => 'ch',
  47 + [ 'ш', 'Ш' ] => 'sh',
  48 + [ 'щ', 'Щ' ] => 'sch',
  49 + [ 'э', 'Э' ] => 'e',
  50 + [ 'ю', 'Ю' ] => 'yu',
  51 + [ 'я', 'Я' ] => 'ya',
  52 + [ 'ы', 'Ы' ] => 'i',
  53 + [ 'ь', 'Ь' ] => '',
  54 + [ 'ъ', 'Ъ' ] => '',
  55 +# Ukrainian lovely letters
  56 + [ 'і', 'І' ] => 'i',
  57 + [ 'ї', 'Ї' ] => 'yi',
  58 + [ 'є', 'Є' ] => 'ye',
  59 + [ 'ґ', 'Ґ' ] => 'g',
20 60 }
21 61  
22 62 # transliterate a string (assumed to contain UTF-8 data)
... ...
plugins/custom_forms/lib/custom_forms_plugin/field.rb
... ... @@ -4,7 +4,7 @@ class CustomFormsPlugin::Field &lt; ActiveRecord::Base
4 4 validates_presence_of :form, :name
5 5 validates_uniqueness_of :slug, :scope => :form_id
6 6  
7   - belongs_to :form, :class_name => 'CustomFormsPlugin::Form', :dependent => :destroy
  7 + belongs_to :form, :class_name => 'CustomFormsPlugin::Form'
8 8 has_many :answers, :class_name => 'CustomFormsPlugin::Answer'
9 9  
10 10 serialize :choices, Hash
... ...
plugins/custom_forms/lib/custom_forms_plugin/form.rb
1 1 class CustomFormsPlugin::Form < Noosfero::Plugin::ActiveRecord
2 2 belongs_to :profile
3 3  
4   - has_many :fields, :class_name => 'CustomFormsPlugin::Field'
  4 + has_many :fields, :class_name => 'CustomFormsPlugin::Field', :dependent => :destroy
5 5 has_many :submissions, :class_name => 'CustomFormsPlugin::Submission'
6 6  
7 7 serialize :access
... ...
plugins/custom_forms/test/unit/custom_forms_plugin/field_test.rb
... ... @@ -60,5 +60,16 @@ class CustomFormsPlugin::FieldTest &lt; ActiveSupport::TestCase
60 60 assert_equal 2, field.choices['Second']
61 61 assert_equal 3, field.choices['Third']
62 62 end
  63 +
  64 + should 'not destroy form after removing a field' do
  65 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  66 + license_field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form)
  67 + url_field = CustomFormsPlugin::Field.create!(:name => 'URL', :form => form)
  68 +
  69 + assert_no_difference CustomFormsPlugin::Form, :count do
  70 + url_field.destroy
  71 + end
  72 + assert_equal form.fields, [license_field]
  73 + end
63 74 end
64 75  
... ...
plugins/custom_forms/test/unit/custom_forms_plugin/form_test.rb
... ... @@ -169,4 +169,15 @@ class CustomFormsPlugin::FormTest &lt; ActiveSupport::TestCase
169 169 assert_includes scope, f2
170 170 assert_not_includes scope, f3
171 171 end
  172 +
  173 + should 'destroy fields after removing a form' do
  174 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  175 + license_field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form)
  176 + url_field = CustomFormsPlugin::Field.create!(:name => 'URL', :form => form)
  177 +
  178 + assert_difference CustomFormsPlugin::Field, :count, -2 do
  179 + form.destroy
  180 + end
  181 + end
  182 +
172 183 end
... ...
plugins/require_auth_to_comment/public/hide_comment_form.js
1 1 (function($) {
2 2 $(window).bind('userDataLoaded', function(event, data) {
3   - if (data.login || $('meta[name=profile.allow_unauthenticated_comments]').length > 0) {
  3 + if (data.login || $('meta[name="profile.allow_unauthenticated_comments"]').length > 0) {
4 4 $('.post-comment-button').show();
5 5 $('#page-comment-form').show();
6 6 $('.comment-footer').show();
... ...
plugins/shopping_cart/public/style.css
... ... @@ -209,7 +209,7 @@ label.error {
209 209 padding-left: 5px;
210 210 }
211 211  
212   -#order-filter {
  212 +#cart-order-filter {
213 213 background-color: #ccc;
214 214 border: 1px solid #aaa;
215 215 border-radius: 5px;
... ...
plugins/shopping_cart/views/shopping_cart_plugin_myprofile/reports.html.erb
... ... @@ -6,7 +6,7 @@
6 6 <% status_collection = [[nil, _('All')]] %>
7 7 <% status_collection += status.map{|s| [pos+=1, s] } %>
8 8  
9   -<% form_tag({}, {:id => 'order-filter'}) do %>
  9 +<% form_tag({}, {:id => 'cart-order-filter'}) do %>
10 10 <%= labelled_text_field(_('From')+' ', :from, @from.strftime("%Y-%m-%d"), :id => 'from', :size => 9) %>
11 11 <%= labelled_text_field(_('to')+' ', :to, @to.strftime("%Y-%m-%d"), :id => 'to', :size => 9) %>
12 12 <span style="white-space:nowrap"><%= labelled_select(_('Status')+' ', :filter_status, :first, :last, @status, status_collection)%></span>
... ...
plugins/spaminator/lib/spaminator_plugin/spaminator.rb
... ... @@ -15,12 +15,12 @@ class SpaminatorPlugin::Spaminator
15 15 def initialize_logger(environment)
16 16 logdir = File.join(RAILS_ROOT, 'log', SpaminatorPlugin.name.underscore)
17 17 File.makedirs(logdir) if !File.exist?(logdir)
18   - logpath = File.join(logdir, "#{environment.name.to_slug}_#{ENV['RAILS_ENV']}_#{Time.now.strftime("%F_%T")}.log")
  18 + logpath = File.join(logdir, "#{environment.name.to_slug}_#{ENV['RAILS_ENV']}_#{Time.now.strftime('%F_%T')}.log")
19 19 @logger = Logger.new(logpath)
20 20 end
21 21  
22 22 def log(message)
23   - @logger << "[#{Time.now.strftime("%F %T %z")}] #{message}\n"
  23 + @logger << "[#{Time.now.strftime('%F %T %z')}] #{message}\n"
24 24 end
25 25 end
26 26  
... ...
po/pt/noosfero.po
... ... @@ -5497,7 +5497,7 @@ msgstr &quot;Produto destacado&quot;
5497 5497 #: app/views/catalog/index.rhtml:42 app/views/search/_image.rhtml:35
5498 5498 #: app/views/search/_image.rhtml:47
5499 5499 msgid "No image"
5500   -msgstr "Nenhum imagem"
  5500 +msgstr "Nenhuma imagem"
5501 5501  
5502 5502 #: app/views/catalog/index.rhtml:52
5503 5503 msgid "from "
... ...
public/javascripts/application.js
... ... @@ -29,7 +29,14 @@ function focus_first_field() {
29 29  
30 30 /* * * Convert a string to a valid login name * * */
31 31 function convToValidLogin( str ) {
32   - return convToValidIdentifier(str, '')
  32 + if (str.indexOf('@') == -1)
  33 + return convToValidUsername(str);
  34 + else
  35 + return convToValidEmail(str);
  36 +}
  37 +
  38 +function convToValidUsername( str ) {
  39 + return convToValidIdentifier(str, '');
33 40 }
34 41  
35 42 /* * * Convert a string to a valid login name * * */
... ... @@ -46,6 +53,18 @@ function convToValidIdentifier( str, sep ) {
46 53 .replace( /[^-_a-z0-9.]+/g, sep )
47 54 }
48 55  
  56 +function convToValidEmail( str ) {
  57 + return str.toLowerCase()
  58 + .replace( /á|à|ã|â/g, "a" )
  59 + .replace( /é|ê/g, "e" )
  60 + .replace( /í/g, "i" )
  61 + .replace( /ó|ô|õ|ö/g, "o" )
  62 + .replace( /ú|ũ|ü/g, "u" )
  63 + .replace( /ñ/g, "n" )
  64 + .replace( /ç/g, "c" )
  65 + .replace( /[^@a-z0-9!#$%&'*+-/=?^_`{|}~.]+/g, '' )
  66 +}
  67 +
49 68 function updateUrlField(name_field, id) {
50 69 url_field = $(id);
51 70 old_url_value = url_field.value;
... ...
public/stylesheets/application.css
... ... @@ -2715,6 +2715,11 @@ div#activation_enterprise label, div#activation_enterprise input, div#activation
2715 2715 padding: 0;
2716 2716 }
2717 2717  
  2718 +#catalog-categories-notice {
  2719 + color: #555753;
  2720 + padding-bottom: 0.5em;
  2721 +}
  2722 +
2718 2723 /* * * Show Product * * * * * * * * * * * * */
2719 2724  
2720 2725 .controller-catalog #show_product .product-pic {
... ...
public/stylesheets/search.css
... ... @@ -756,10 +756,18 @@ li.search-product-item hr {
756 756 min-height: 98px;
757 757 position: absolute;
758 758 }
  759 +.search-gallery .search-content-first-column {
  760 + width: 190px;
  761 +}
  762 +
759 763 .search-content-second-column {
760 764 margin-left: 140px;
761 765 width: auto;
762 766 }
  767 +.search-gallery .search-content-second-column {
  768 + margin-left: 200px;
  769 +}
  770 +
763 771 .search-content-second-column tr:hover {
764 772 background-color: none;
765 773 }
... ... @@ -827,15 +835,45 @@ a.search-image-pic {
827 835 display: table-cell;
828 836 vertical-align: middle;
829 837 }
  838 +
830 839 .search-gallery-items a.search-image-pic {
831 840 float:left;
832 841 margin:0 2px;
833 842 }
  843 +
  844 +
834 845 .search-gallery .search-gallery-items {
835 846 float: left;
836   - margin: 0 10px 0 0;
  847 + margin: 0;
837 848 min-width: 130px;
  849 + position: relative;
838 850 }
  851 +
  852 +.search-gallery .search-gallery-items a.search-image-pic {
  853 + border: none;
  854 + border-radius: 0;
  855 + box-shadow: none;
  856 + width: 62px;
  857 + margin: 0px 0px 1px 1px;
  858 + background-size: cover;
  859 + background-position: 50% 10%;
  860 + float: none;
  861 +}
  862 +
  863 +.search-gallery .search-gallery-items a.search-image-pic.pic-num1,
  864 +.search-gallery .search-gallery-items a.search-image-pic.pic-num2 {
  865 + display: block;
  866 + width: 60px;
  867 + height: 49px;
  868 +}
  869 +.search-gallery .search-gallery-items a.search-image-pic.pic-num3 {
  870 + width: 130px;
  871 + height: 99px;
  872 + position: absolute;
  873 + left: 61px;
  874 + top: 0px;
  875 +}
  876 +
839 877 .search-content-first-column .search-image-container .search-image-pic
840 878 .search-uploaded-file-first-column .search-image-container .search-image-pic {
841 879 display: block;
... ...
script/sample-profiles
... ... @@ -5,20 +5,24 @@ include Noosfero::SampleDataHelper
5 5 categories = $environment.categories
6 6  
7 7 places = [
8   - { :country=>'BR', :city=>'Salvador',
  8 + { :country=>'BR', :state=>'Bahia', :city=>'Salvador',
9 9 :lat=>-12.94032, :lng=>-38.58398 },
10   - { :country=>'BR', :city=>'São Paulo',
  10 + { :country=>'BR', :state=>'Bahia', :city=>'Feira de Santana',
  11 + :lat=>-12.25547, :lng=>-38.95430 },
  12 + { :country=>'BR', :state=>'São Paulo', :city=>'São Paulo',
11 13 :lat=>-23.54894, :lng=>-46.63881 },
12   - { :country=>'BR', :city=>'Rio de Janeiro',
13   - :lat=>-22.90353, :lng=>-43.20958 },
14   - { :country=>'AR', :city=>'Buenos Aires',
  14 + { :country=>'BR', :state=>'Rio de Janeiro', :city=>'Petrópolis',
  15 + :lat=>-22.50462, :lng=>-43.18232 },
  16 + { :country=>'AR', :state=>'A.C.', :city=>'Buenos Aires',
15 17 :lat=>-34.61088, :lng=>-58.39782 },
16   - { :country=>'AR', :city=>'Mar del Plata',
  18 + { :country=>'AR', :state=>'Buenos Aires', :city=>'Mar del Plata',
17 19 :lat=>-37.98317, :lng=>-57.59513 },
18   - { :country=>'MX', :city=>'Acapulco',
  20 + { :country=>'MX', :state=>'Guerrero', :city=>'Acapulco',
19 21 :lat=>16.86369, :lng=>-99.88151 },
20   - { :country=>'US', :city=>'Los Angeles',
  22 + { :country=>'US', :state=>'California', :city=>'Los Angeles',
21 23 :lat=>34.02307, :lng=>-118.24310 },
  24 + { :country=>'US', :state=>'Florida', :city=>'Jacksonville',
  25 + :lat=>30.33217, :lng=>-81.65566 },
22 26 { :country=>'IT', :city=>'Roma',
23 27 :lat=>41.89512, :lng=>12.48184 },
24 28 { :country=>'IN', :city=>'Mumbai',
... ... @@ -51,12 +55,17 @@ for name in NAMES
51 55 user.person.name = full_name
52 56 place = places[rand(places.length)]
53 57 user.person.data[:country] = place[:country]
  58 + user.person.state = place[:state]
54 59 user.person.city = place[:city]
55 60 user.person.lat = place[:lat] + (rand/100)-0.005
56 61 user.person.lng = place[:lng] + (rand/100)-0.005
57 62 user.person.save!
58   - categories.rand.people << user.person
59   - categories.rand.people << user.person
  63 + if categories.present?
  64 + 2.times do
  65 + category = categories.rand
  66 + category.people << user.person unless category.people.include?(user.person)
  67 + end
  68 + end
60 69 end
61 70 end
62 71 end
... ... @@ -89,12 +98,8 @@ guest = User.new({
89 98 :password_confirmation => 'test',
90 99 :environment => $environment,
91 100 })
92   -save guest do
93   - 5.times do
94   - communities.rand.add_admin(guest.person)
95   - communities.rand.add_member(guest.person)
96   - end
97   -end
  101 +save guest
  102 +
98 103 done
99 104  
100 105 print "Activating users: "
... ... @@ -128,14 +133,32 @@ for verb in VERBS
128 133 for stuff in STUFF
129 134 name = [verb, stuff].join(' ')
130 135 community = Community.new(:name => name, :environment => $environment)
  136 + if rand(2)==1 # not all communities must have a place
  137 + place = places[rand(places.length)]
  138 + community.data[:country] = place[:country]
  139 + community.state = place[:state]
  140 + community.city = place[:city]
  141 + end
131 142 save community do
132 143 communities << community
133 144 rand(10).times do
134   - community.add_member(people.rand)
  145 + person = people.rand
  146 + community.add_member(person) unless community.members.include?(person)
  147 + end
  148 + if categories.present?
  149 + 2.times do
  150 + category = categories.rand
  151 + category.communities << community unless category.communities.include?(community)
  152 + end
135 153 end
136   - categories.rand.communities << community
137   - categories.rand.communities << community
138 154 end
139 155 end
140 156 end
  157 +
  158 +5.times do
  159 + community = communities.rand
  160 + community.add_member(guest.person) unless community.members.include?(guest.person)
  161 + community.add_admin(guest.person) unless community.admins.include?(guest.person)
  162 +end
  163 +
141 164 done
... ...
test/functional/catalog_controller_test.rb
... ... @@ -224,4 +224,21 @@ class CatalogControllerTest &lt; ActionController::TestCase
224 224 assert_tag :tag => 'li', :attributes => {:id => "product-#{p2.id}", :class => /not-available/}
225 225 end
226 226  
  227 + should 'sort categories by name' do
  228 + environment = @enterprise.environment
  229 + environment.categories.destroy_all
  230 + pc1 = ProductCategory.create!(:name => "Drinks", :environment => environment)
  231 + pc2 = ProductCategory.create!(:name => "Bananas", :environment => environment)
  232 + pc3 = ProductCategory.create!(:name => "Sodas", :environment => environment)
  233 + pc4 = ProductCategory.create!(:name => "Pies", :environment => environment)
  234 + p1 = fast_create(Product, :product_category_id => pc1.id, :enterprise_id => @enterprise.id)
  235 + p2 = fast_create(Product, :product_category_id => pc2.id, :enterprise_id => @enterprise.id)
  236 + p3 = fast_create(Product, :product_category_id => pc3.id, :enterprise_id => @enterprise.id)
  237 + p4 = fast_create(Product, :product_category_id => pc4.id, :enterprise_id => @enterprise.id)
  238 +
  239 + get :index, :profile => @enterprise.identifier
  240 +
  241 + assert_equal [pc2, pc1, pc4, pc3], assigns(:categories)
  242 + end
  243 +
227 244 end
... ...
test/functional/environment_design_controller_test.rb
... ... @@ -6,7 +6,7 @@ class EnvironmentDesignController; def rescue_action(e) raise e end; end
6 6  
7 7 class EnvironmentDesignControllerTest < ActionController::TestCase
8 8  
9   - ALL_BLOCKS = [ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock ]
  9 + ALL_BLOCKS = [ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ]
10 10  
11 11 def setup
12 12 @controller = EnvironmentDesignController.new
... ... @@ -175,6 +175,16 @@ class EnvironmentDesignControllerTest &lt; ActionController::TestCase
175 175 assert_tag :tag => 'input', :attributes => { :id => 'block_address' }
176 176 end
177 177  
  178 + should 'be able to edit TagsBlock' do
  179 + login_as(create_admin_user(Environment.default))
  180 + b = TagsBlock.create!
  181 + e = Environment.default
  182 + e.boxes.create!
  183 + e.boxes.first.blocks << b
  184 + get :edit, :id => b.id
  185 + assert_tag :tag => 'input', :attributes => { :id => 'block_title' }
  186 + end
  187 +
178 188 should 'create back link to environment control panel' do
179 189 Environment.default.boxes.create!.blocks << CommunitiesBlock.new
180 190 Environment.default.boxes.create!.blocks << EnterprisesBlock.new
... ...
test/unit/article_test.rb
... ... @@ -1347,6 +1347,7 @@ class ArticleTest &lt; ActiveSupport::TestCase
1347 1347 a = profile.articles.create!(:name => 'a test article', :last_changed_by => author)
1348 1348 assert_equal author.name, a.author_name
1349 1349 author.destroy
  1350 + a.reload
1350 1351 a.author_name = 'some name'
1351 1352 assert_equal 'some name', a.author_name
1352 1353 end
... ...
test/unit/tags_block_test.rb
... ... @@ -22,9 +22,22 @@ class TagsBlockTest &lt; ActiveSupport::TestCase
22 22 end
23 23  
24 24 should 'generate links to tags' do
25   - assert_match /profile\/testinguser\/tags\/first-tag/, block.content
  25 + assert_match /profile\/testinguser\/tags\/first-tag/, block.content
26 26 assert_match /profile\/testinguser\/tags\/second-tag/, block.content
27   - assert_match /profile\/testinguser\/tags\/third-tag/, block.content
  27 + assert_match /profile\/testinguser\/tags\/third-tag/, block.content
  28 + end
  29 +
  30 + should 'generate links to tags on a environment page' do
  31 + @otheruser = create_user('othertestinguser').person
  32 + @otheruser.articles.build(:name => 'article A', :tag_list => 'other-tag').save!
  33 + @otheruser.articles.build(:name => 'article B', :tag_list => 'other-tag, second-tag').save!
  34 + box = Box.create!(:owner => Environment.default)
  35 + @block = TagsBlock.create!(:box => box)
  36 +
  37 + assert_match /\/tag\/first-tag" [^>]+"3 items"/, block.content
  38 + assert_match /\/tag\/second-tag" [^>]+"3 items"/, block.content
  39 + assert_match /\/tag\/third-tag" [^>]+"one item"/, block.content
  40 + assert_match /\/tag\/other-tag" [^>]+"2 items"/, block.content
28 41 end
29 42  
30 43 should 'return (none) when no tags to display' do
... ...
test/unit/user_test.rb
... ... @@ -53,6 +53,8 @@ class UserTest &lt; ActiveSupport::TestCase
53 53  
54 54 def test_should_authenticate_user
55 55 assert_equal users(:johndoe), User.authenticate('johndoe', 'test')
  56 + assert_equal users(:johndoe), User.authenticate('johndoe@localhost.localdomain', 'test')
  57 + assert_equal nil, User.authenticate('wrongemail@localhost', 'test')
56 58 end
57 59  
58 60 def test_should_authenticate_user_of_nondefault_environment
... ...