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 The samples of config file to configure a XMPP/BOSH server with ejabberd, 17 The samples of config file to configure a XMPP/BOSH server with ejabberd,
5 postgresql and apache2 can be found at util/chat directory. 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,7 +21,7 @@ This setup supposes that you are using Noosfero installed via Debian package
8 in a production environment. 21 in a production environment.
9 22
10 Steps 23 Steps
11 -===== 24 +-----
12 25
13 This is a step-by-step guide to get a XMPP service working, in a Debian system. 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,15 +157,8 @@ You should see a page with a message like that:
144 157
145 ## 9. Test chat session 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 Ruby-BOSH - SEND 163 Ruby-BOSH - SEND
158 <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"/> 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,7 +137,13 @@ class CmsController &lt; MyProfileController
137 article_data = environment.enabled?('articles_dont_accept_comments_by_default') ? { :accept_comments => false } : {} 137 article_data = environment.enabled?('articles_dont_accept_comments_by_default') ? { :accept_comments => false } : {}
138 article_data.merge!(params[:article]) if params[:article] 138 article_data.merge!(params[:article]) if params[:article]
139 article_data.merge!(:profile => profile) if profile 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 parent = check_parent(params[:parent_id]) 148 parent = check_parent(params[:parent_id])
143 if parent 149 if parent
app/controllers/my_profile/tasks_controller.rb
1 class TasksController < MyProfileController 1 class TasksController < MyProfileController
2 2
3 protect 'perform_task', :profile 3 protect 'perform_task', :profile
4 - 4 +
5 def index 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 @task_types = Task.pending_types_for(profile) 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 @failed = params ? params[:failed] : {} 10 @failed = params ? params[:failed] : {}
11 end 11 end
12 12
app/helpers/application_helper.rb
@@ -941,6 +941,19 @@ module ApplicationHelper @@ -941,6 +941,19 @@ module ApplicationHelper
941 article_helper.cms_label_for_edit 941 article_helper.cms_label_for_edit
942 end 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 def add_rss_feed_to_head(title, url) 957 def add_rss_feed_to_head(title, url)
945 content_for :feeds do 958 content_for :feeds do
946 tag(:link, :rel => 'alternate', :type => 'application/rss+xml', :title => title, :href => url_for(url)) 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,6 +12,7 @@ module ArticleHelper
12 @article = article 12 @article = article
13 13
14 visibility_options(@article, tokenized_children) + 14 visibility_options(@article, tokenized_children) +
  15 + topic_creation(@article) +
15 content_tag('h4', _('Options')) + 16 content_tag('h4', _('Options')) +
16 content_tag('div', 17 content_tag('div',
17 (article.profile.has_members? ? 18 (article.profile.has_members? ?
@@ -55,14 +56,7 @@ module ArticleHelper @@ -55,14 +56,7 @@ module ArticleHelper
55 'div', 56 'div',
56 check_box(:article, :display_versions) + 57 check_box(:article, :display_versions) +
57 content_tag('label', _('I want this article to display a link to older versions'), :for => 'article_display_versions') 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 end 61 end
68 62
@@ -81,6 +75,22 @@ module ArticleHelper @@ -81,6 +75,22 @@ module ArticleHelper
81 ) 75 )
82 end 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 def privacity_exceptions(article, tokenized_children) 94 def privacity_exceptions(article, tokenized_children)
85 content_tag('div', 95 content_tag('div',
86 content_tag('div', 96 content_tag('div',
app/models/article.rb
@@ -587,25 +587,24 @@ class Article &lt; ActiveRecord::Base @@ -587,25 +587,24 @@ class Article &lt; ActiveRecord::Base
587 profile.visible? && profile.public? && published? 587 profile.visible? && profile.public? && published?
588 end 588 end
589 589
590 -  
591 - def copy(options = {}) 590 + def copy_without_save(options = {})
592 attrs = attributes.reject! { |key, value| ATTRIBUTES_NOT_COPIED.include?(key.to_sym) } 591 attrs = attributes.reject! { |key, value| ATTRIBUTES_NOT_COPIED.include?(key.to_sym) }
593 attrs.merge!(options) 592 attrs.merge!(options)
594 object = self.class.new 593 object = self.class.new
595 attrs.each do |key, value| 594 attrs.each do |key, value|
596 object.send(key.to_s+'=', value) 595 object.send(key.to_s+'=', value)
597 end 596 end
  597 + object
  598 + end
  599 +
  600 + def copy(options = {})
  601 + object = copy_without_save(options)
598 object.save 602 object.save
599 object 603 object
600 end 604 end
601 605
602 def copy!(options = {}) 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 object.save! 608 object.save!
610 object 609 object
611 end 610 end
app/models/forum.rb
@@ -3,11 +3,11 @@ class Forum &lt; Folder @@ -3,11 +3,11 @@ class Forum &lt; Folder
3 acts_as_having_posts :order => 'updated_at DESC' 3 acts_as_having_posts :order => 'updated_at DESC'
4 include PostsLimit 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 settings_items :terms_of_use, :type => :string, :default => "" 8 settings_items :terms_of_use, :type => :string, :default => ""
9 settings_items :has_terms_of_use, :type => :boolean, :default => false 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 has_and_belongs_to_many :users_with_agreement, :class_name => 'Person', :join_table => 'terms_forum_people' 11 has_and_belongs_to_many :users_with_agreement, :class_name => 'Person', :join_table => 'terms_forum_people'
12 12
13 before_save do |forum| 13 before_save do |forum|
@@ -33,6 +33,23 @@ class Forum &lt; Folder @@ -33,6 +33,23 @@ class Forum &lt; Folder
33 _('An internet forum, also called message board, where discussions can be held.') 33 _('An internet forum, also called message board, where discussions can be held.')
34 end 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 include ActionView::Helpers::TagHelper 53 include ActionView::Helpers::TagHelper
37 def to_html(options = {}) 54 def to_html(options = {})
38 proc do 55 proc do
@@ -69,11 +86,17 @@ class Forum &lt; Folder @@ -69,11 +86,17 @@ class Forum &lt; Folder
69 self.users_with_agreement.exists? user 86 self.users_with_agreement.exists? user
70 end 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 end 97 end
75 98
76 def allow_create?(user) 99 def allow_create?(user)
77 - super || can_create_topic?(user, profile) 100 + super || can_create_topic?(user)
78 end 101 end
79 end 102 end
app/models/suggest_article.rb
@@ -25,7 +25,7 @@ class SuggestArticle &lt; Task @@ -25,7 +25,7 @@ class SuggestArticle &lt; Task
25 25
26 def article_object 26 def article_object
27 if @article_object.nil? 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 if requestor.present? 29 if requestor.present?
30 @article_object.author = requestor 30 @article_object.author = requestor
31 else 31 else
app/models/task.rb
@@ -239,9 +239,9 @@ class Task &lt; ActiveRecord::Base @@ -239,9 +239,9 @@ class Task &lt; ActiveRecord::Base
239 scope :opened, :conditions => { :status => [Task::Status::ACTIVE, Task::Status::HIDDEN] } 239 scope :opened, :conditions => { :status => [Task::Status::ACTIVE, Task::Status::HIDDEN] }
240 scope :of, lambda { |type| conditions = type ? "type LIKE '#{type}'" : "1=1"; {:conditions => [conditions]} } 240 scope :of, lambda { |type| conditions = type ? "type LIKE '#{type}'" : "1=1"; {:conditions => [conditions]} }
241 scope :order_by, lambda { |attribute, ord| {:order => "#{attribute} #{ord}"} } 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 scope :to, lambda { |profile| 247 scope :to, lambda { |profile|
app/views/content_viewer/_article_toolbar.html.erb
@@ -30,6 +30,10 @@ @@ -30,6 +30,10 @@
30 <% end %> 30 <% end %>
31 31
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) %> 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 <% end %> 37 <% end %>
34 38
35 <% if @page.accept_uploads? && @page.allow_create?(user) %> 39 <% if @page.accept_uploads? && @page.allow_create?(user) %>
app/views/tasks/index.html.erb
@@ -21,11 +21,24 @@ @@ -21,11 +21,24 @@
21 </div> 21 </div>
22 <% end %> 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 <% if @tasks.empty? %> 38 <% if @tasks.empty? %>
25 <p> 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 </p> 41 </p>
28 - <em><%= _('No pending tasks for %s') % profile.name %></em>  
29 <% else %> 42 <% else %>
30 <%= form_tag :action => 'close' do%> 43 <%= form_tag :action => 'close' do%>
31 <% button_bar do %> 44 <% button_bar do %>
@@ -38,14 +51,12 @@ @@ -38,14 +51,12 @@
38 51
39 <ul class='task-list'> 52 <ul class='task-list'>
40 <p> 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 <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "up-set-all-tasks-to") %> 54 <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "up-set-all-tasks-to") %>
45 </p> 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 <p> 60 <p>
50 <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "down-set-all-tasks-to") %> 61 <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "down-set-all-tasks-to") %>
51 </p> 62 </p>
db/migrate/20150513213939_update_topic_creation_configuration.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -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
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 # 11 #
12 # It's strongly recommended to check this file into your version control system. 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 create_table "abuse_reports", :force => true do |t| 16 create_table "abuse_reports", :force => true do |t|
17 t.integer "reporter_id" 17 t.integer "reporter_id"
debian/apache2/conf.d/noosfero-chat 0 → 100644
@@ -0,0 +1,9 @@ @@ -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 * Temporary version in heavy development 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 noosfero (1.1) wheezy; urgency=low 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,3 +85,14 @@ Description: free web-based platform for social networks (apache frontend)
85 . 85 .
86 This package contains the configuration files needed to run Noosfero with the 86 This package contains the configuration files needed to run Noosfero with the
87 Apache HTTPD server as frontend. 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 @@ @@ -0,0 +1,2 @@
  1 +POLL=true
  2 +SMP=auto
debian/noosfero-chat.install 0 → 100644
@@ -0,0 +1,6 @@ @@ -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 @@ @@ -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 @@ @@ -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,6 +17,7 @@ debian/dbupgrade usr/lib/noosfero
17 debian/default/noosfero etc/default 17 debian/default/noosfero etc/default
18 debian/noosfero-check-dbconfig usr/sbin 18 debian/noosfero-check-dbconfig usr/sbin
19 debian/noosfero-console usr/sbin 19 debian/noosfero-console usr/sbin
  20 +debian/noosfero-runner usr/sbin
20 debian/noosfero.yml etc/noosfero 21 debian/noosfero.yml etc/noosfero
21 debian/thin.yml etc/noosfero 22 debian/thin.yml etc/noosfero
22 doc usr/share/noosfero 23 doc usr/share/noosfero
debian/update-noosfero-odbc 0 → 100755
@@ -0,0 +1,14 @@ @@ -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 @@ @@ -0,0 +1 @@
  1 +session required pam_limits.so
etc/security/limits.d/noosfero-chat.conf 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +ejabberd hard nofile 65536
  2 +ejabberd soft nofile 65536
features/forum.feature
@@ -169,124 +169,3 @@ Feature: forum @@ -169,124 +169,3 @@ Feature: forum
169 | Post one | joaosilva | Hi all | Hi all | 169 | Post one | joaosilva | Hi all | Hi all |
170 When I go to /joaosilva/forum 170 When I go to /joaosilva/forum
171 Then I should see "Joao Silva" within ".forum-post-last-answer" 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,9 +56,18 @@ class ToleranceTimePlugin &lt; Noosfero::Plugin
56 end 56 end
57 57
58 def content_expire_edit(content) 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 if ToleranceTimePlugin.expired?(content) 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 end 71 end
62 end 72 end
63 -  
64 end 73 end
public/javascripts/topic-creation-config.js 0 → 100644
@@ -0,0 +1,30 @@ @@ -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,6 +6238,20 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img {
6238 .forum-posts .pagination { 6238 .forum-posts .pagination {
6239 margin-top: 20px; 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 /* Task */ 6255 /* Task */
6242 6256
6243 #user a#pending-tasks-count { 6257 #user a#pending-tasks-count {
script/apacheconf
@@ -32,6 +32,7 @@ when &#39;virtualhosts&#39; @@ -32,6 +32,7 @@ when &#39;virtualhosts&#39;
32 puts " #{server_directive} #{domain.name}" 32 puts " #{server_directive} #{domain.name}"
33 server_directive = 'ServerAlias' 33 server_directive = 'ServerAlias'
34 end 34 end
  35 + puts " Include /usr/share/noosfero/util/chat/apache/xmpp.conf"
35 puts " Include /etc/noosfero/apache/virtualhost.conf" 36 puts " Include /etc/noosfero/apache/virtualhost.conf"
36 puts "</VirtualHost>" 37 puts "</VirtualHost>"
37 end 38 end
script/noosfero-test-chat-session 0 → 100755
@@ -0,0 +1,28 @@ @@ -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 @@ @@ -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,6 +1811,14 @@ class CmsControllerTest &lt; ActionController::TestCase
1811 assert_equal 'first version', assigns(:article).name 1811 assert_equal 'first version', assigns(:article).name
1812 end 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 should 'save article with content from older version' do 1822 should 'save article with content from older version' do
1815 article = profile.articles.create(:name => 'first version') 1823 article = profile.articles.create(:name => 'first version')
1816 article.name = 'second version'; article.save 1824 article.name = 'second version'; article.save
test/functional/content_viewer_controller_test.rb
@@ -1252,9 +1252,11 @@ class ContentViewerControllerTest &lt; ActionController::TestCase @@ -1252,9 +1252,11 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
1252 should 'expire article actions button if any plugins says so' do 1252 should 'expire article actions button if any plugins says so' do
1253 class Plugin1 < Noosfero::Plugin 1253 class Plugin1 < Noosfero::Plugin
1254 def content_expire_edit(content); 'This button is expired.'; end 1254 def content_expire_edit(content); 'This button is expired.'; end
  1255 + def content_expire_clone(content); 'This button is expired.'; end
1255 end 1256 end
1256 class Plugin2 < Noosfero::Plugin 1257 class Plugin2 < Noosfero::Plugin
1257 def content_expire_edit(content); nil; end 1258 def content_expire_edit(content); nil; end
  1259 + def content_expire_clone(content); nil; end
1258 end 1260 end
1259 Noosfero::Plugin.stubs(:all).returns([Plugin1.name, Plugin2.name]) 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,13 +428,13 @@ class TasksControllerTest &lt; ActionController::TestCase
428 t2 = CleanHouse.create!(:requestor => requestor, :target => profile) 428 t2 = CleanHouse.create!(:requestor => requestor, :target => profile)
429 t3 = FeedDog.create!(:requestor => requestor, :target => profile) 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 assert_includes assigns(:tasks), t1 433 assert_includes assigns(:tasks), t1
434 assert_not_includes assigns(:tasks), t2 434 assert_not_includes assigns(:tasks), t2
435 assert_not_includes assigns(:tasks), t3 435 assert_not_includes assigns(:tasks), t3
436 436
437 - post :index 437 + get :index
438 438
439 assert_includes assigns(:tasks), t1 439 assert_includes assigns(:tasks), t1
440 assert_includes assigns(:tasks), t2 440 assert_includes assigns(:tasks), t2
test/unit/application_helper_test.rb
@@ -1009,6 +1009,14 @@ class ApplicationHelperTest &lt; ActionView::TestCase @@ -1009,6 +1009,14 @@ class ApplicationHelperTest &lt; ActionView::TestCase
1009 assert html.include?("onClick=\"toggle_fullwidth('#article')\"") 1009 assert html.include?("onClick=\"toggle_fullwidth('#article')\"")
1010 end 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 protected 1020 protected
1013 include NoosferoTestHelper 1021 include NoosferoTestHelper
1014 1022
test/unit/forum_test.rb
@@ -174,4 +174,70 @@ class ForumTest &lt; ActiveSupport::TestCase @@ -174,4 +174,70 @@ class ForumTest &lt; ActiveSupport::TestCase
174 assert_equal true, Forum.find(forum.id).agrees_with_terms?(person) 174 assert_equal true, Forum.find(forum.id).agrees_with_terms?(person)
175 end 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 end 243 end
util/chat/apache/xmpp.conf
1 # If your XMPP XMPP/BOSH isn't in localhost, change the config below to correct 1 # If your XMPP XMPP/BOSH isn't in localhost, change the config below to correct
2 # point to address 2 # point to address
3 3
  4 + RewriteEngine On
4 RewriteRule /http-bind http://localhost:5280/http-bind [P,QSA,L] 5 RewriteRule /http-bind http://localhost:5280/http-bind [P,QSA,L]
5 <Proxy http://localhost:5280/http-bind> 6 <Proxy http://localhost:5280/http-bind>
6 Order Allow,Deny 7 Order Allow,Deny