Commit 9c63de564cac997adbf35cccc1a9f07baee40a64

Authored by Victor Costa
2 parents 431bb618 78331cae

Merge branch 'master' into production

Conflicts:
	app/controllers/my_profile/tasks_controller.rb
	app/models/task.rb
	test/functional/tasks_controller_test.rb
INSTALL.chat.md
1   -XMPP/Chat Setup
2   -===============
  1 +Automatic XMPP/Chat Setup
  2 +=========================
  3 +
  4 +Since Noosfero 1.2, the XMPP/Chat can be installed via `noosfero-chat` Debian
  5 +package. So you don't need to follow the manual instructions here if you
  6 +already have it installed on your system.
  7 +
  8 +But if you are going to install the `noosfero-chat` package on a system that
  9 +already has `noosfero` older 1.2 installed then you need to check if apache's
  10 +configuration file `/etc/apache2/sites-available/noosfero` has this line below:
  11 +
  12 + Include /usr/share/noosfero/util/chat/apache/xmpp.conf
  13 +
  14 +Manual XMPP/Chat Setup
  15 +======================
3 16  
4 17 The samples of config file to configure a XMPP/BOSH server with ejabberd,
5 18 postgresql and apache2 can be found at util/chat directory.
... ... @@ -8,7 +21,7 @@ This setup supposes that you are using Noosfero installed via Debian package
8 21 in a production environment.
9 22  
10 23 Steps
11   -=====
  24 +-----
12 25  
13 26 This is a step-by-step guide to get a XMPP service working, in a Debian system.
14 27  
... ... @@ -144,15 +157,8 @@ You should see a page with a message like that:
144 157  
145 158 ## 9. Test chat session
146 159  
147   -Open Noosfero console and execute:
148   -
149   ->> environment = Environment.default
150   ->> user = Person['guest']
151   ->> password = user.user.crypted_password
152   ->> login = user.jid
153   ->> RubyBOSH.initialize_session(login, password, "http://#{environment.default_hostname}/http-bind", :wait => 30, :hold => 1, :window => 5
154   -
155   -If you have luck, should see something like that:
  160 +Run `./script/noosfero-test-chat-session`. If you have luck, should see
  161 +something like that:
156 162  
157 163 Ruby-BOSH - SEND
158 164 <body window="5" rid="60265" xmlns="http://jabber.org/protocol/httpbind" xmlns:xmpp="urn:xmpp:xbosh" to="vagrant-debian-squeeze.vagrantup.com" wait="30" xmpp:version="1.0" hold="1"/>
... ...
app/controllers/my_profile/cms_controller.rb
... ... @@ -137,7 +137,13 @@ class CmsController &lt; MyProfileController
137 137 article_data = environment.enabled?('articles_dont_accept_comments_by_default') ? { :accept_comments => false } : {}
138 138 article_data.merge!(params[:article]) if params[:article]
139 139 article_data.merge!(:profile => profile) if profile
140   - @article = klass.new(article_data)
  140 +
  141 + @article = if params[:clone]
  142 + current_article = profile.articles.find(params[:id])
  143 + current_article.copy_without_save
  144 + else
  145 + klass.new(article_data)
  146 + end
141 147  
142 148 parent = check_parent(params[:parent_id])
143 149 if parent
... ...
app/controllers/my_profile/tasks_controller.rb
1 1 class TasksController < MyProfileController
2 2  
3 3 protect 'perform_task', :profile
4   -
  4 +
5 5 def index
6   - @filter_type = params[:filter_type] = params[:filter_type].blank? ? nil : params[:filter_type]
7   - @filter_text = params[:filter_text].blank? ? nil : params[:filter_text]
  6 + @filter_type = params[:filter_type].presence
  7 + @filter_text = params[:filter_text].presence
8 8 @task_types = Task.pending_types_for(profile)
9   - @tasks = Task.pending_all(profile,params).order_by('created_at', 'asc').paginate(:per_page => Task.per_page, :page => params[:page])
  9 + @tasks = Task.pending_all(profile, @filter_type, @filter_text).order_by('created_at', 'asc').paginate(:per_page => Task.per_page, :page => params[:page])
10 10 @failed = params ? params[:failed] : {}
11 11 end
12 12  
... ...
app/helpers/application_helper.rb
... ... @@ -941,6 +941,19 @@ module ApplicationHelper
941 941 article_helper.cms_label_for_edit
942 942 end
943 943  
  944 + def label_for_clone_article(article)
  945 + translated_types = {
  946 + Folder => _('Folder'),
  947 + Blog => _('Blog'),
  948 + Event => _('Event'),
  949 + Forum => _('Forum')
  950 + }
  951 +
  952 + translated_type = translated_types[article.class] || _('Article')
  953 +
  954 + _('Clone %s') % translated_type
  955 + end
  956 +
944 957 def add_rss_feed_to_head(title, url)
945 958 content_for :feeds do
946 959 tag(:link, :rel => 'alternate', :type => 'application/rss+xml', :title => title, :href => url_for(url))
... ...
app/helpers/article_helper.rb
... ... @@ -12,6 +12,7 @@ module ArticleHelper
12 12 @article = article
13 13  
14 14 visibility_options(@article, tokenized_children) +
  15 + topic_creation(@article) +
15 16 content_tag('h4', _('Options')) +
16 17 content_tag('div',
17 18 (article.profile.has_members? ?
... ... @@ -55,14 +56,7 @@ module ArticleHelper
55 56 'div',
56 57 check_box(:article, :display_versions) +
57 58 content_tag('label', _('I want this article to display a link to older versions'), :for => 'article_display_versions')
58   - ) : '') +
59   -
60   - (article.forum? && article.profile.community? ?
61   - content_tag(
62   - 'div',
63   - check_box(:article, :allows_members_to_create_topics) +
64   - content_tag('label', _('Allow members to create topics'), :for => 'article_allows_members_to_create_topics')
65   - ) : '')
  59 + ) : '')
66 60 )
67 61 end
68 62  
... ... @@ -81,6 +75,22 @@ module ArticleHelper
81 75 )
82 76 end
83 77  
  78 + def topic_creation(article)
  79 + return '' unless article.forum?
  80 +
  81 + general_options = Forum::TopicCreation.general_options(article)
  82 + slider_options = {:id => 'topic-creation-slider'}
  83 + slider_options = general_options.keys.inject(slider_options) do |result, option|
  84 + result.merge!({'data-'+option => general_options[option]})
  85 + end
  86 +
  87 + content_tag('h4', _('Topic creation')) +
  88 + content_tag( 'small', _('Who will be able to create new topics on this forum?')) +
  89 + content_tag('div', '', slider_options) +
  90 + hidden_field_tag('article[topic_creation]', article.topic_creation) +
  91 + javascript_include_tag('topic-creation-config')
  92 + end
  93 +
84 94 def privacity_exceptions(article, tokenized_children)
85 95 content_tag('div',
86 96 content_tag('div',
... ...
app/models/article.rb
... ... @@ -587,25 +587,24 @@ class Article &lt; ActiveRecord::Base
587 587 profile.visible? && profile.public? && published?
588 588 end
589 589  
590   -
591   - def copy(options = {})
  590 + def copy_without_save(options = {})
592 591 attrs = attributes.reject! { |key, value| ATTRIBUTES_NOT_COPIED.include?(key.to_sym) }
593 592 attrs.merge!(options)
594 593 object = self.class.new
595 594 attrs.each do |key, value|
596 595 object.send(key.to_s+'=', value)
597 596 end
  597 + object
  598 + end
  599 +
  600 + def copy(options = {})
  601 + object = copy_without_save(options)
598 602 object.save
599 603 object
600 604 end
601 605  
602 606 def copy!(options = {})
603   - attrs = attributes.reject! { |key, value| ATTRIBUTES_NOT_COPIED.include?(key.to_sym) }
604   - attrs.merge!(options)
605   - object = self.class.new
606   - attrs.each do |key, value|
607   - object.send(key.to_s+'=', value)
608   - end
  607 + object = copy_without_save(options)
609 608 object.save!
610 609 object
611 610 end
... ...
app/models/forum.rb
... ... @@ -3,11 +3,11 @@ class Forum &lt; Folder
3 3 acts_as_having_posts :order => 'updated_at DESC'
4 4 include PostsLimit
5 5  
6   - attr_accessible :has_terms_of_use, :terms_of_use, :allows_members_to_create_topics
  6 + attr_accessible :has_terms_of_use, :terms_of_use, :topic_creation
7 7  
8 8 settings_items :terms_of_use, :type => :string, :default => ""
9 9 settings_items :has_terms_of_use, :type => :boolean, :default => false
10   - settings_items :allows_members_to_create_topics, :type => :boolean, :default => false
  10 + settings_items :topic_creation, :type => :string, :default => 'self'
11 11 has_and_belongs_to_many :users_with_agreement, :class_name => 'Person', :join_table => 'terms_forum_people'
12 12  
13 13 before_save do |forum|
... ... @@ -33,6 +33,23 @@ class Forum &lt; Folder
33 33 _('An internet forum, also called message board, where discussions can be held.')
34 34 end
35 35  
  36 + module TopicCreation
  37 + BASE = ActiveSupport::OrderedHash.new
  38 + BASE['users'] = _('Logged users')
  39 +
  40 + PERSON = ActiveSupport::OrderedHash.new
  41 + PERSON['self'] = _('Me')
  42 + PERSON['related'] = _('Friends')
  43 +
  44 + GROUP = ActiveSupport::OrderedHash.new
  45 + GROUP['self'] = _('Administrators')
  46 + GROUP['related'] = _('Members')
  47 +
  48 + def self.general_options(forum)
  49 + forum.profile.person? ? PERSON.merge(BASE) : GROUP.merge(BASE)
  50 + end
  51 + end
  52 +
36 53 include ActionView::Helpers::TagHelper
37 54 def to_html(options = {})
38 55 proc do
... ... @@ -69,11 +86,17 @@ class Forum &lt; Folder
69 86 self.users_with_agreement.exists? user
70 87 end
71 88  
72   - def can_create_topic?(user, profile)
73   - return profile.community? && profile.members.include?(user) && self.allows_members_to_create_topics
  89 + def can_create_topic?(user)
  90 + return true if user == profile || profile.admins.include?(user) || profile.environment.admins.include?(user)
  91 + case topic_creation
  92 + when 'related'
  93 + profile.person? ? profile.friends.include?(user) : profile.members.include?(user)
  94 + when 'users'
  95 + user.present?
  96 + end
74 97 end
75 98  
76 99 def allow_create?(user)
77   - super || can_create_topic?(user, profile)
  100 + super || can_create_topic?(user)
78 101 end
79 102 end
... ...
app/models/suggest_article.rb
... ... @@ -25,7 +25,7 @@ class SuggestArticle &lt; Task
25 25  
26 26 def article_object
27 27 if @article_object.nil?
28   - @article_object = article_type.new(article.merge({:profile => target}).except(:type))
  28 + @article_object = article_type.new(article.merge(target.present? ? {:profile => target} : {}).except(:type))
29 29 if requestor.present?
30 30 @article_object.author = requestor
31 31 else
... ...
app/models/task.rb
... ... @@ -239,9 +239,9 @@ class Task &lt; ActiveRecord::Base
239 239 scope :opened, :conditions => { :status => [Task::Status::ACTIVE, Task::Status::HIDDEN] }
240 240 scope :of, lambda { |type| conditions = type ? "type LIKE '#{type}'" : "1=1"; {:conditions => [conditions]} }
241 241 scope :order_by, lambda { |attribute, ord| {:order => "#{attribute} #{ord}"} }
242   - scope :like, ->(field,value) { where("LOWER(#{field}) LIKE ?", "%#{value.downcase}%") if value}
243   - scope :pending_all, ->(profile, params){
244   - self.to(profile).without_spam.pending.of(params[:filter_type]).like('data', params[:filter_text])
  242 + scope :like, lambda { |field, value| where("LOWER(#{field}) LIKE ?", "%#{value.downcase}%") if value}
  243 + scope :pending_all, lambda { |profile, filter_type, filter_text|
  244 + self.to(profile).without_spam.pending.of(filter_type).like('data', filter_text)
245 245 }
246 246  
247 247 scope :to, lambda { |profile|
... ...
app/views/content_viewer/_article_toolbar.html.erb
... ... @@ -30,6 +30,10 @@
30 30 <% end %>
31 31  
32 32 <%= modal_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) unless remove_content_button(:new, @page) %>
  33 +
  34 + <% content = content_tag('span', label_for_clone_article(@page)) %>
  35 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'new', :id => @page.id, :clone => true, :type => @page.class }) %>
  36 + <%= expirable_button @page, :clone, content, url %>
33 37 <% end %>
34 38  
35 39 <% if @page.accept_uploads? && @page.allow_create?(user) %>
... ...
app/views/tasks/index.html.erb
... ... @@ -21,11 +21,24 @@
21 21 </div>
22 22 <% end %>
23 23  
  24 +<%= form_tag '#', :method => 'get' do %>
  25 + <%= field_set_tag _('Filter'), :class => 'filter_fields' do %>
  26 + <p>
  27 + <%= labelled_select(_('Type of task')+': ', :filter_type, :first, :last, @filter_type, type_collection, {:id => 'filter-type'}) %>
  28 + </p>
  29 + <p>
  30 + <%= labelled_text_field(_("Text filter")+': ', :filter_text, nil, {:id => 'filter-text',:value => @filter_text}) %>
  31 + </p>
  32 + <p>
  33 + <%= submit_button(:search, _('Search')) %>
  34 + </p>
  35 + <% end %>
  36 +<% end %>
  37 +
24 38 <% if @tasks.empty? %>
25 39 <p>
26   - <%= labelled_select(_('Filter')+': ', :filter_type, :first, :last, @filter, type_collection, :onchange => "document.location.href = '?filter_type='+this.value")%>
  40 + <em><%= _('No pending tasks for %s') % profile.name %></em>
27 41 </p>
28   - <em><%= _('No pending tasks for %s') % profile.name %></em>
29 42 <% else %>
30 43 <%= form_tag :action => 'close' do%>
31 44 <% button_bar do %>
... ... @@ -38,14 +51,12 @@
38 51  
39 52 <ul class='task-list'>
40 53 <p>
41   - <%= labelled_select(_('Filter')+': ', :filter_type, :first, :last, @filter, type_collection, :onchange => "document.location.href = '?filter_type='+this.value") %>
42   - </p>
43   - <p>
44 54 <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "up-set-all-tasks-to") %>
45 55 </p>
46   - <% @tasks.each do |task| %>
47   - <%= render :partial => 'task', :locals => { :task => task } %>
48   - <% end %>
  56 +
  57 + <div class="task_boxes">
  58 + <%= render :partial => 'task', :collection => @tasks %>
  59 + </div>
49 60 <p>
50 61 <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "down-set-all-tasks-to") %>
51 62 </p>
... ...
db/migrate/20150513213939_update_topic_creation_configuration.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class UpdateTopicCreationConfiguration < ActiveRecord::Migration
  2 + def up
  3 + Forum.where("setting LIKE '%:allows_members_to_create_topics: true%'").find_each do |forum|
  4 + forum.setting.delete(:allows_members_to_create_topics)
  5 + forum.setting.merge!(:topic_creation => 'related')
  6 + forum.save
  7 + end
  8 + end
  9 +
  10 + def down
  11 + say "this migration can't be reverted"
  12 + end
  13 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(:version => 20150423144533) do
  14 +ActiveRecord::Schema.define(:version => 20150513213939) do
15 15  
16 16 create_table "abuse_reports", :force => true do |t|
17 17 t.integer "reporter_id"
... ...
debian/apache2/conf.d/noosfero-chat 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +<IfModule mpm_worker_module>
  2 + StartServers 8
  3 + MinSpareThreads 25
  4 + MaxSpareThreads 75
  5 + ThreadLimit 128
  6 + ThreadsPerChild 128
  7 + MaxClients 2048
  8 + MaxRequestsPerChild 0
  9 +</IfModule>
... ...
debian/changelog
1   -noosfero (1.2~0) UNRELEASED; urgency=medium
  1 +noosfero (1.2~1) UNRELEASED; urgency=medium
2 2  
  3 + [ Antonio Terceiro ]
3 4 * Temporary version in heavy development
4 5  
5   - -- Antonio Terceiro <terceiro@debian.org> Fri, 08 May 2015 16:08:18 -0300
  6 + [ Joenio Costa ]
  7 + * Build noosfero-chat package
  8 +
  9 + -- Joenio Costa <joenio@colivre.coop.br> Mon, 18 May 2015 14:32:21 -0300
6 10  
7 11 noosfero (1.1) wheezy; urgency=low
8 12  
... ...
debian/control
... ... @@ -85,3 +85,14 @@ Description: free web-based platform for social networks (apache frontend)
85 85 .
86 86 This package contains the configuration files needed to run Noosfero with the
87 87 Apache HTTPD server as frontend.
  88 +
  89 +Package: noosfero-chat
  90 +Architecture: all
  91 +Depends: noosfero (>= 1.2), ejabberd, odbc-postgresql, pidgin-data
  92 +Description: free web-based platform for social networks (ejabberd based chat)
  93 + Noosfero is a web platform for social and solidarity economy networks with
  94 + blog, e-Porfolios, CMS, RSS, thematic discussion, events agenda and collective
  95 + inteligence for solidarity economy in the same system.
  96 + .
  97 + This package contains the configuration files needed to run Noosfero with the
  98 + Ejabberd XMPP chat server.
... ...
debian/default/noosfero-chat 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +POLL=true
  2 +SMP=auto
... ...
debian/noosfero-chat.install 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +debian/default/noosfero-chat etc/default
  2 +util/chat/ejabberd.cfg etc/ejabberd
  3 +debian/update-noosfero-odbc usr/sbin
  4 +etc/security/limits.d/noosfero-chat.conf etc/security/limits.d
  5 +etc/pam.d/noosfero-chat etc/pam.d
  6 +debian/apache2/conf.d/noosfero-chat etc/apache2/conf.d
... ...
debian/noosfero-chat.postinst 0 → 100644
... ... @@ -0,0 +1,50 @@
  1 +#!/bin/bash
  2 +
  3 +set -e
  4 +
  5 +. /usr/share/debconf/confmodule
  6 +
  7 +echo -n "Fetching noosfero domain ..."
  8 +domain=$(noosfero-runner 'puts Environment.default.default_hostname(true)')
  9 +echo " [domain = $domain]"
  10 +
  11 +ejabberd_config='/etc/ejabberd/ejabberd.cfg'
  12 +if test -f $ejabberd_config; then
  13 + sed -i "s/acl, *\([^,]*\), *{user, *\([^,]*\), *[^}]*/acl, \1, {user, \2, \"$domain\"/" /etc/ejabberd/ejabberd.cfg
  14 + sed -i "s/hosts, *\[[^]]*/hosts, [\"$domain\"/" /etc/ejabberd/ejabberd.cfg
  15 +fi
  16 +
  17 +echo -n 'Fetching noosfero database name ...'
  18 +noosfero_database=$(noosfero-runner 'puts Environment.connection_config[:database]')
  19 +echo " [database = $noosfero_database]"
  20 +
  21 +echo 'Creating ejabberd schema ...'
  22 +. /etc/default/noosfero
  23 +noosfero_user="$NOOSFERO_USER"
  24 +su - postgres -c "psql -c 'GRANT CREATE ON DATABASE $noosfero_database TO $noosfero_user;' > /dev/null"
  25 +su - $noosfero_user -c 'rails dbconsole production < /usr/share/noosfero/util/chat/postgresql/ejabberd.sql > /dev/null'
  26 +
  27 +if which update-noosfero-odbc > /dev/null ; then
  28 + update-noosfero-odbc
  29 +fi
  30 +
  31 +ejabberd_default='/etc/default/ejabberd'
  32 +noosfero_chat_default='/etc/default/noosfero-chat'
  33 +if test -f $ejabberd_default; then
  34 + if ! cat $ejabberd_default | grep "^\. $noosfero_chat_default" > /dev/null ; then
  35 + echo 'Extending ejabberd defaults with noosfero-chat defaults ...'
  36 + echo ". $noosfero_chat_default" >> $ejabberd_default
  37 + fi
  38 +fi
  39 +
  40 +a2enmod proxy_http
  41 +
  42 +invoke-rc.d ejabberd restart
  43 +invoke-rc.d noosfero restart
  44 +invoke-rc.d apache2 restart
  45 +
  46 +# stop debconf to avoid the problem with infinite hanging, cfe
  47 +# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=295477
  48 +db_stop
  49 +
  50 +#DEBHELPER#
... ...
debian/noosfero-runner 0 → 100755
... ... @@ -0,0 +1,8 @@
  1 +#!/bin/sh
  2 +
  3 +set -e
  4 +
  5 +environment="$2"
  6 +test -z "$environment" && environment=production
  7 +
  8 +su - noosfero -c "rails runner -e $environment '$1'"
... ...
debian/noosfero.install
... ... @@ -17,6 +17,7 @@ debian/dbupgrade usr/lib/noosfero
17 17 debian/default/noosfero etc/default
18 18 debian/noosfero-check-dbconfig usr/sbin
19 19 debian/noosfero-console usr/sbin
  20 +debian/noosfero-runner usr/sbin
20 21 debian/noosfero.yml etc/noosfero
21 22 debian/thin.yml etc/noosfero
22 23 doc usr/share/noosfero
... ...
debian/update-noosfero-odbc 0 → 100755
... ... @@ -0,0 +1,14 @@
  1 +#!/bin/sh
  2 +
  3 +set -e
  4 +
  5 +# automatically update configuration, but if package noosfero is also installed
  6 +if test -x /usr/share/noosfero/script/odbcconf; then
  7 + config_file="/etc/odbc.ini"
  8 + if test -e "$config_file"; then
  9 + echo "Overwriting $config_file ..."
  10 + fi
  11 + /usr/share/noosfero/script/odbcconf > "$config_file"
  12 +
  13 + echo 'Noosfero ODBC configuration updated.'
  14 +fi
... ...
etc/pam.d/noosfero-chat 0 → 100644
... ... @@ -0,0 +1 @@
  1 +session required pam_limits.so
... ...
etc/security/limits.d/noosfero-chat.conf 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +ejabberd hard nofile 65536
  2 +ejabberd soft nofile 65536
... ...
features/forum.feature
... ... @@ -169,124 +169,3 @@ Feature: forum
169 169 | Post one | joaosilva | Hi all | Hi all |
170 170 When I go to /joaosilva/forum
171 171 Then I should see "Joao Silva" within ".forum-post-last-answer"
172   -
173   - @selenium
174   - Scenario: community member should be able to see the discussion topic button
175   - Given the following community
176   - | identifier | name | owner |
177   - | sample-community | Sample Community | joaosilva |
178   - And the following forums
179   - | owner | name |
180   - | sample-community | Forum |
181   - And the following users
182   - | login | name |
183   - | mariasilva | Maria Silva|
184   - And "Maria Silva" is a member of "Sample Community"
185   - And I am logged in as "joaosilva"
186   - When I go to /sample-community/forum
187   - And I follow "Configure forum"
188   - And I check "Allow members to create topics"
189   - And I press "Save"
190   - And I am logged in as "mariasilva"
191   - And I go to /sample-community/forum
192   - Then I should see "New discussion topic"
193   -
194   - @selenium
195   - Scenario: a non community member should not be able to see the discussion topic button
196   - Given the following community
197   - | identifier | name | owner |
198   - | sample-community | Sample Community | joaosilva |
199   - And the following forums
200   - | owner | name |
201   - | sample-community | Forum |
202   - And the following users
203   - | login | name |
204   - | mariasilva | Maria Silva|
205   - And I am logged in as "joaosilva"
206   - When I go to /sample-community/forum
207   - And I follow "Configure forum"
208   - And I check "Allow members to create topics"
209   - And I press "Save"
210   - And I am logged in as "mariasilva"
211   - And I go to /sample-community/forum
212   - Then I should not see "New discussion topic"
213   -
214   - @selenium
215   - Scenario: community member should not be able to see the discussion topic button
216   - Given the following community
217   - | identifier | name | owner |
218   - | sample-community | Sample Community | joaosilva |
219   - And the following forums
220   - | owner | name |
221   - | sample-community | Forum |
222   - And the following users
223   - | login | name |
224   - | mariasilva | Maria Silva|
225   - And "Maria Silva" is a member of "Sample Community"
226   - And I am logged in as "joaosilva"
227   - When I go to /sample-community/forum
228   - And I follow "Configure forum"
229   - And I uncheck "Allow members to create topics"
230   - And I press "Save"
231   - And I am logged in as "mariasilva"
232   - And I go to /sample-community/forum
233   - Then I should not see "New discussion topic"
234   -
235   - @selenium
236   - Scenario: community member should be able to create a topic with the discussion topic button
237   - Given the following community
238   - | identifier | name | owner |
239   - | sample-community | Sample Community | joaosilva |
240   - And the following forums
241   - | owner | name |
242   - | sample-community | Forum |
243   - And the following users
244   - | login | name |
245   - | mariasilva | Maria Silva|
246   - And "Maria Silva" is a member of "Sample Community"
247   - And I am logged in as "joaosilva"
248   - When I go to /sample-community/forum
249   - And I follow "Configure forum"
250   - And I check "Allow members to create topics"
251   - And I press "Save"
252   - And I am logged in as "mariasilva"
253   - And I go to /sample-community/forum
254   - And I follow "New discussion topic"
255   - And I should see "Text article with visual editor"
256   - And I follow "Text article with visual editor"
257   - And I fill in "Title" with "Test"
258   - And I press "Save"
259   - Then I should see "Test"
260   -
261   - @selenium
262   - Scenario: community member should be able to create a topic on a topic page
263   - Given the following community
264   - | identifier | name | owner |
265   - | sample-community | Sample Community | joaosilva |
266   - And the following forums
267   - | owner | name |
268   - | sample-community | Forum |
269   - And the following users
270   - | login | name |
271   - | mariasilva | Maria Silva|
272   - And "Maria Silva" is a member of "Sample Community"
273   - And I am logged in as "joaosilva"
274   - When I go to /sample-community/forum
275   - And I follow "Configure forum"
276   - And I check "Allow members to create topics"
277   - And I press "Save"
278   - And I am logged in as "mariasilva"
279   - And I go to /sample-community/forum
280   - And I follow "New discussion topic"
281   - And I should see "Text article with visual editor"
282   - And I follow "Text article with visual editor"
283   - And I fill in "Title" with "Test"
284   - And I press "Save"
285   - And I go to /sample-community/forum/test
286   - And I follow "New discussion topic"
287   - And I should see "Text article with visual editor"
288   - And I follow "Text article with visual editor"
289   - And I fill in "Title" with "Test inside the topic page"
290   - And I press "Save"
291   - And I go to /sample-community/forum
292   - Then I should see "Test inside the topic page"
... ...
plugins/tolerance_time/lib/tolerance_time_plugin.rb
... ... @@ -56,9 +56,18 @@ class ToleranceTimePlugin &lt; Noosfero::Plugin
56 56 end
57 57  
58 58 def content_expire_edit(content)
  59 + content_expire_for(content, _('editing'))
  60 + end
  61 +
  62 + def content_expire_clone(content)
  63 + content_expire_for(content, _('cloning'))
  64 + end
  65 +
  66 + private
  67 +
  68 + def content_expire_for(content, action)
59 69 if ToleranceTimePlugin.expired?(content)
60   - _('The tolerance time for editing this content is over.')
  70 + _('The tolerance time for %s this content is over.') % action
61 71 end
62 72 end
63   -
64 73 end
... ...
public/javascripts/topic-creation-config.js 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +var values_map = {2: 'self', 1: 'related', 0: 'users'};
  2 +var keys_map = {};
  3 +Object.keys(values_map).forEach(function(value){
  4 + keys_map[values_map[value]] = value;
  5 +});
  6 +var s = jQuery('#topic-creation-slider');
  7 +
  8 +function setValue(event, ui){
  9 + jQuery('#article_topic_creation').val(values_map[ui.value]);
  10 +}
  11 +
  12 +s.slider({
  13 + orientation: 'vertical',
  14 + min: 0,
  15 + max: 2,
  16 + step: 1,
  17 + value: keys_map[jQuery('#article_topic_creation').val()],
  18 + range: 'max',
  19 + change: setValue
  20 +}).each(function() {
  21 + var opt = jQuery(this).data()['ui-slider'].options;
  22 + var vals = opt.max - opt.min;
  23 +
  24 + for (var i = 0; i <= vals; i++) {
  25 + var n = vals - i;
  26 + var el = jQuery('<label>' + s.data(values_map[i]) + '</label>').css('top', ((n/vals*100) - 7 - n) + '%');
  27 + s.append(el);
  28 + }
  29 +});
  30 +
... ...
public/stylesheets/application.css
... ... @@ -6238,6 +6238,20 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img {
6238 6238 .forum-posts .pagination {
6239 6239 margin-top: 20px;
6240 6240 }
  6241 +
  6242 +#topic-creation-slider{
  6243 + margin-top: 15px;
  6244 +}
  6245 +
  6246 +#topic-creation-slider .ui-slider-range {
  6247 + background: #73D216;
  6248 +}
  6249 +
  6250 +#topic-creation-slider label {
  6251 + left: 20px;
  6252 + position: absolute;
  6253 + width: 200px;
  6254 +}
6241 6255 /* Task */
6242 6256  
6243 6257 #user a#pending-tasks-count {
... ...
script/apacheconf
... ... @@ -32,6 +32,7 @@ when &#39;virtualhosts&#39;
32 32 puts " #{server_directive} #{domain.name}"
33 33 server_directive = 'ServerAlias'
34 34 end
  35 + puts " Include /usr/share/noosfero/util/chat/apache/xmpp.conf"
35 36 puts " Include /etc/noosfero/apache/virtualhost.conf"
36 37 puts "</VirtualHost>"
37 38 end
... ...
script/noosfero-test-chat-session 0 → 100755
... ... @@ -0,0 +1,28 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../config/environment'
  3 +
  4 +environment = Environment.default
  5 +person = Person.first
  6 +password = person.user.crypted_password
  7 +login = person.jid
  8 +
  9 +begin
  10 + RubyBOSH.initialize_session(
  11 + login,
  12 + password,
  13 + "http://#{environment.default_hostname}/http-bind",
  14 + :wait => 30,
  15 + :hold => 1,
  16 + :window => 5
  17 + )
  18 +rescue Exception => e
  19 + puts ""
  20 + puts "[ERROR] XMPP/Chat setup isn't working"
  21 + puts "-------------------------------------"
  22 + puts e.to_s
  23 + exit 1
  24 +else
  25 + puts ""
  26 + puts "[OK] XMPP/Chat setup is working"
  27 + exit 0
  28 +end
... ...
script/odbcconf 0 → 100755
... ... @@ -0,0 +1,22 @@
  1 +#!/usr/bin/env ruby
  2 +
  3 +require 'yaml'
  4 +config = YAML.load_file(File.dirname(__FILE__) + '/../config/database.yml')['production']
  5 +
  6 +
  7 +puts "[PostgreSQLEjabberdNoosfero]"
  8 +puts "Description = PostgreSQL Noosfero ejabberd database"
  9 +puts "Driver = PostgreSQL Unicode"
  10 +puts "Trace = No"
  11 +puts "TraceFile = /tmp/psqlodbc.log"
  12 +puts "Database = #{config['database']}"
  13 +puts "Servername = #{config['host'] || 'localhost'}"
  14 +puts "UserName = #{config['username']}"
  15 +puts "Password = #{config['password']}"
  16 +puts "Port = #{config['port'] || '5432'}"
  17 +puts "ReadOnly = No"
  18 +puts "RowVersioning = No"
  19 +puts "ShowSystemTables = No"
  20 +puts "ShowOidColumn = No"
  21 +puts "FakeOidIndex = No"
  22 +puts "ConnSettings = SET search_path TO ejabberd"
... ...
test/functional/cms_controller_test.rb
... ... @@ -1811,6 +1811,14 @@ class CmsControllerTest &lt; ActionController::TestCase
1811 1811 assert_equal 'first version', assigns(:article).name
1812 1812 end
1813 1813  
  1814 + should 'clone article with its content' do
  1815 + article = profile.articles.create(:name => 'first version')
  1816 +
  1817 + get :new, :profile => profile.identifier, :id => article.id, :clone => true, :type => 'TinyMceArticle'
  1818 +
  1819 + assert_match article.name, @response.body
  1820 + end
  1821 +
1814 1822 should 'save article with content from older version' do
1815 1823 article = profile.articles.create(:name => 'first version')
1816 1824 article.name = 'second version'; article.save
... ...
test/functional/content_viewer_controller_test.rb
... ... @@ -1252,9 +1252,11 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
1252 1252 should 'expire article actions button if any plugins says so' do
1253 1253 class Plugin1 < Noosfero::Plugin
1254 1254 def content_expire_edit(content); 'This button is expired.'; end
  1255 + def content_expire_clone(content); 'This button is expired.'; end
1255 1256 end
1256 1257 class Plugin2 < Noosfero::Plugin
1257 1258 def content_expire_edit(content); nil; end
  1259 + def content_expire_clone(content); nil; end
1258 1260 end
1259 1261 Noosfero::Plugin.stubs(:all).returns([Plugin1.name, Plugin2.name])
1260 1262  
... ...
test/functional/tasks_controller_test.rb
... ... @@ -428,13 +428,13 @@ class TasksControllerTest &lt; ActionController::TestCase
428 428 t2 = CleanHouse.create!(:requestor => requestor, :target => profile)
429 429 t3 = FeedDog.create!(:requestor => requestor, :target => profile)
430 430  
431   - post :index, :filter_type => t1.type, :filter_text => 'test'
  431 + get :index, :filter_type => t1.type, :filter_text => 'test'
432 432  
433 433 assert_includes assigns(:tasks), t1
434 434 assert_not_includes assigns(:tasks), t2
435 435 assert_not_includes assigns(:tasks), t3
436 436  
437   - post :index
  437 + get :index
438 438  
439 439 assert_includes assigns(:tasks), t1
440 440 assert_includes assigns(:tasks), t2
... ...
test/unit/application_helper_test.rb
... ... @@ -1009,6 +1009,14 @@ class ApplicationHelperTest &lt; ActionView::TestCase
1009 1009 assert html.include?("onClick=\"toggle_fullwidth('#article')\"")
1010 1010 end
1011 1011  
  1012 + should "return the related class string" do
  1013 + assert_equal "Clone Folder", label_for_clone_article(Folder.new)
  1014 + assert_equal "Clone Blog", label_for_clone_article(Blog.new)
  1015 + assert_equal "Clone Event", label_for_clone_article(Event.new)
  1016 + assert_equal "Clone Forum", label_for_clone_article(Forum.new)
  1017 + assert_equal "Clone Article", label_for_clone_article(TinyMceArticle.new)
  1018 + end
  1019 +
1012 1020 protected
1013 1021 include NoosferoTestHelper
1014 1022  
... ...
test/unit/forum_test.rb
... ... @@ -174,4 +174,70 @@ class ForumTest &lt; ActiveSupport::TestCase
174 174 assert_equal true, Forum.find(forum.id).agrees_with_terms?(person)
175 175 end
176 176  
  177 + should 'always allow topic creation to the person himself' do
  178 + person = fast_create(Person)
  179 + someone = fast_create(Person)
  180 + forum = Forum.new(:profile => person)
  181 +
  182 + assert forum.can_create_topic?(person)
  183 + assert !forum.can_create_topic?(someone)
  184 + end
  185 +
  186 + should 'always allow topic creation to profile admins' do
  187 + admin = fast_create(Person)
  188 + someone = fast_create(Person)
  189 + profile = fast_create(Profile)
  190 + admins = [admin]
  191 + profile.stubs(:admins).returns(admins)
  192 + forum = Forum.new(:profile => profile)
  193 +
  194 + assert forum.can_create_topic?(admin)
  195 + assert !forum.can_create_topic?(someone)
  196 + end
  197 +
  198 + should 'always allow topic creation to environment admins' do
  199 + admin = fast_create(Person)
  200 + someone = fast_create(Person)
  201 + profile = fast_create(Profile)
  202 + admins = [admin]
  203 + environment = profile.environment
  204 + environment.stubs(:admins).returns(admins)
  205 + forum = Forum.new(:profile => profile)
  206 +
  207 + assert forum.can_create_topic?(admin)
  208 + assert !forum.can_create_topic?(someone)
  209 + end
  210 +
  211 + should 'allow only person friends to create topics when topic_creation is related' do
  212 + person = fast_create(Person)
  213 + friend = fast_create(Person)
  214 + someone = fast_create(Person)
  215 + friends = [friend]
  216 + person.stubs(:friends).returns(friends)
  217 + forum = Forum.new(:profile => person, :topic_creation => 'related')
  218 +
  219 + assert forum.can_create_topic?(friend)
  220 + assert !forum.can_create_topic?(someone)
  221 + end
  222 +
  223 + should 'allow only group members to create topics when topic_creation is related' do
  224 + organization = fast_create(Organization)
  225 + member = fast_create(Person)
  226 + someone = fast_create(Person)
  227 + members = [member]
  228 + organization.stubs(:members).returns(members)
  229 + forum = Forum.new(:profile => organization, :topic_creation => 'related')
  230 +
  231 + assert forum.can_create_topic?(member)
  232 + assert !forum.can_create_topic?(someone)
  233 + end
  234 +
  235 + should 'allow every user to create topics when topic_creation is users' do
  236 + profile = fast_create(Profile)
  237 + user = fast_create(Person)
  238 + forum = Forum.new(:profile => profile, :topic_creation => 'users')
  239 +
  240 + assert forum.can_create_topic?(user)
  241 + assert !forum.can_create_topic?(nil)
  242 + end
177 243 end
... ...
util/chat/apache/xmpp.conf
1 1 # If your XMPP XMPP/BOSH isn't in localhost, change the config below to correct
2 2 # point to address
3 3  
  4 + RewriteEngine On
4 5 RewriteRule /http-bind http://localhost:5280/http-bind [P,QSA,L]
5 6 <Proxy http://localhost:5280/http-bind>
6 7 Order Allow,Deny
... ...