Commit 3779f8e0eb09dd2c5475fbb1e8b73a4772bbe94b

Authored by Leandro Santos
2 parents 7bc9af73 aa514668

Merge branch 'production' of gitlab.com:participa/noosfero into production

Showing 60 changed files with 906 additions and 220 deletions   Show diff stats
Gemfile
... ... @@ -57,4 +57,4 @@ eval(File.read('config/Gemfile'), binding) rescue nil
57 57 # dependencies.
58 58 Dir.glob('config/plugins/*/Gemfile').each do |gemfile|
59 59 eval File.read(gemfile)
60 60 -end
  61 +end
61 62 \ No newline at end of file
... ...
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 + @filter_responsible = params[:filter_responsible]
8 9 @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])
  10 +
  11 + @tasks = Task.pending_all(profile, @filter_type, @filter_text).order_by('created_at', 'asc')
  12 + @tasks = @tasks.where(:responsible_id => @filter_responsible.to_i != -1 ? @filter_responsible : nil) if @filter_responsible.present?
  13 + @tasks = @tasks.paginate(:per_page => Task.per_page, :page => params[:page])
  14 +
10 15 @failed = params ? params[:failed] : {}
  16 +
  17 + @responsible_candidates = profile.members.by_role(profile.roles.reject {|r| !r.has_permission?('perform_task')})
11 18 end
12 19  
13 20 def processed
14 21 @tasks = Task.to(profile).without_spam.closed.sort_by(&:created_at)
15 22 end
16 23  
  24 + def change_responsible
  25 + task = profile.tasks.find(params[:task_id])
  26 +
  27 + if task.responsible.present? && task.responsible.id != params[:old_responsible_id].to_i
  28 + return render :json => {:notice => _('Task already assigned!'), :success => false, :current_responsible => task.responsible.id}
  29 + end
  30 +
  31 + responsible = profile.members.find(params[:responsible_id]) if params[:responsible_id].present?
  32 + task.responsible = responsible
  33 + task.save!
  34 + render :json => {:notice => _('Task responsible successfully updated!'), :success => true, :new_responsible => {:id => responsible.present? ? responsible.id : nil}}
  35 + end
  36 +
17 37 VALID_DECISIONS = [ 'finish', 'cancel', 'skip' ]
18 38  
19 39 def close
20 40 failed = {}
  41 + save = false
21 42  
22 43 if params[:tasks]
23 44 params[:tasks].each do |id, value|
24 45 decision = value[:decision]
25   - if request.post? && VALID_DECISIONS.include?(decision) && id && decision != 'skip'
  46 +
  47 + if value[:task].is_a?(Hash) && value[:task][:tag_list]
  48 +
26 49 task = profile.find_in_all_tasks(id)
27   - begin
28   - task.update_attributes(value[:task])
29   - task.send(decision)
30   - rescue Exception => ex
31   - message = "#{task.title} (#{task.requestor ? task.requestor.name : task.author_name})"
32   - failed[ex.message] ? failed[ex.message] << message : failed[ex.message] = [message]
  50 + task.tag_list = value[:task][:tag_list]
  51 + value[:task].delete('tag_list')
  52 +
  53 + save = true
  54 + end
  55 +
  56 + if request.post?
  57 + if VALID_DECISIONS.include?(decision) && id && decision != 'skip'
  58 + task ||= profile.find_in_all_tasks(id)
  59 + begin
  60 + task.update_attributes(value[:task])
  61 + task.send(decision)
  62 + rescue Exception => ex
  63 + message = "#{task.title} (#{task.requestor ? task.requestor.name : task.author_name})"
  64 + failed[ex.message] ? failed[ex.message] << message : failed[ex.message] = [message]
  65 + end
  66 + elsif save
  67 + task.save!
33 68 end
34 69 end
35 70 end
... ...
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/helpers/tinymce_helper.rb
... ... @@ -20,7 +20,7 @@ module TinymceHelper
20 20 :image_advtab => true,
21 21 :language => tinymce_language
22 22  
23   - options[:toolbar1] = "insertfile undo redo | copy paste | bold italic underline | styleselect fontsizeselect | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
  23 + options[:toolbar1] = "fullscreen | insertfile undo redo | copy paste | bold italic underline | styleselect fontsizeselect | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
24 24 if options[:mode] == 'simple'
25 25 options[:menubar] = false
26 26 else
... ...
app/models/article.rb
... ... @@ -508,13 +508,13 @@ class Article &lt; ActiveRecord::Base
508 508  
509 509 scope :display_filter, lambda {|user, profile|
510 510 return published if (user.nil? && profile && profile.public?)
511   - return [] if user.nil? || profile.nil? || (profile && !profile.public? && !user.follows?(profile))
  511 + return [] if user.nil? || (profile && !profile.public? && !user.follows?(profile))
512 512 where(
513 513 [
514 514 "published = ? OR last_changed_by_id = ? OR profile_id = ? OR ?
515 515 OR (show_to_followers = ? AND ? AND profile_id = ?)", true, user.id, user.id,
516 516 profile.nil? ? false : user.has_permission?(:view_private_content, profile),
517   - true, user.follows?(profile), profile.id
  517 + true, user.follows?(profile), (profile.nil? ? nil : profile.id)
518 518 ]
519 519 )
520 520 }
... ... @@ -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/environment.rb
... ... @@ -339,6 +339,16 @@ class Environment &lt; ActiveRecord::Base
339 339 self.save!
340 340 end
341 341  
  342 + def enable_all_plugins
  343 + Noosfero::Plugin.available_plugin_names.each do |plugin|
  344 + plugin_name = plugin.to_s + "Plugin"
  345 + unless self.enabled_plugins.include?(plugin_name)
  346 + self.enabled_plugins += [plugin_name]
  347 + end
  348 + end
  349 + self.save!
  350 + end
  351 +
342 352 # Disables a feature identified by its name
343 353 def disable(feature, must_save=true)
344 354 self.settings["#{feature}_enabled".to_sym] = false
... ...
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
... ... @@ -12,6 +12,7 @@
12 12 class Task < ActiveRecord::Base
13 13  
14 14 acts_as_having_settings :field => :data
  15 + acts_as_ordered_taggable
15 16  
16 17 module Status
17 18 # the status of tasks just created
... ... @@ -33,6 +34,7 @@ class Task &lt; ActiveRecord::Base
33 34  
34 35 belongs_to :requestor, :class_name => 'Profile', :foreign_key => :requestor_id
35 36 belongs_to :target, :foreign_key => :target_id, :polymorphic => true
  37 + belongs_to :responsible, :class_name => 'Person', :foreign_key => :responsible_id
36 38  
37 39 validates_uniqueness_of :code, :on => :create
38 40 validates_presence_of :code
... ... @@ -239,9 +241,9 @@ class Task &lt; ActiveRecord::Base
239 241 scope :opened, :conditions => { :status => [Task::Status::ACTIVE, Task::Status::HIDDEN] }
240 242 scope :of, lambda { |type| conditions = type ? "type LIKE '#{type}'" : "1=1"; {:conditions => [conditions]} }
241 243 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])
  244 + scope :like, lambda { |field, value| where("LOWER(#{field}) LIKE ?", "%#{value.downcase}%") if value}
  245 + scope :pending_all, lambda { |profile, filter_type, filter_text|
  246 + self.to(profile).without_spam.pending.of(filter_type).like('data', filter_text)
245 247 }
246 248  
247 249 scope :to, lambda { |profile|
... ...
app/views/cms/_forum.html.erb
... ... @@ -2,6 +2,8 @@
2 2  
3 3 <h1><%= _('My Forum') %></h1>
4 4  
  5 +<%= required_fields_message %>
  6 +
5 7 <%= render :file => 'shared/tiny_mce' %>
6 8  
7 9 <%= required f.text_field(:name, :size => '64', :maxlength => 150, :onchange => "updateUrlField(this, 'article_slug')") %>
... ...
app/views/content_viewer/_article_toolbar.html.erb
1 1 <div<%= user && " class='logged-in'" %>>
2 2 <div id="article-actions">
  3 +
3 4 <%= fullscreen_buttons('#article') %>
  5 +
4 6 <% if @page.allow_edit?(user) && !remove_content_button(:edit, @page) %>
5 7 <% content = content_tag('span', label_for_edit_article(@page)) %>
6 8 <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }) %>
7 9 <%= expirable_button @page, :edit, content, url %>
8 10 <% end %>
9   -
  11 +
10 12 <% if @page != profile.home_page && !@page.has_posts? && @page.allow_delete?(user) && !remove_content_button(:delete, @page)%>
11 13 <% content = content_tag( 'span', _('Delete') ) %>
12 14 <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'destroy', :id => @page.id}) %>
... ... @@ -28,6 +30,10 @@
28 30 <% end %>
29 31  
30 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 %>
31 37 <% end %>
32 38  
33 39 <% if @page.accept_uploads? && @page.allow_create?(user) %>
... ... @@ -50,6 +56,7 @@
50 56 <% end %>
51 57  
52 58 <%= report_abuse(profile, :link, @page) %>
  59 +
53 60 </div>
54 61 <div id="article-header">
55 62 <% if @page.blog? and !@page.image.nil? %>
... ...
app/views/content_viewer/_article_toolbar.html.erb.orig 0 → 100644
... ... @@ -0,0 +1,69 @@
  1 +<div<%= user && " class='logged-in'" %>>
  2 + <div id="article-actions">
  3 +<<<<<<< HEAD
  4 + <%= fullscreen_buttons('#article') %>
  5 +=======
  6 +
  7 + <%= fullscreen_buttons('#article') %>
  8 +
  9 +>>>>>>> master
  10 + <% if @page.allow_edit?(user) && !remove_content_button(:edit, @page) %>
  11 + <% content = content_tag('span', label_for_edit_article(@page)) %>
  12 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }) %>
  13 + <%= expirable_button @page, :edit, content, url %>
  14 + <% end %>
  15 +
  16 + <% if @page != profile.home_page && !@page.has_posts? && @page.allow_delete?(user) && !remove_content_button(:delete, @page)%>
  17 + <% content = content_tag( 'span', _('Delete') ) %>
  18 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'destroy', :id => @page.id}) %>
  19 + <% options = {:method => :post, :confirm => delete_article_message(@page)} %>
  20 + <%= expirable_button @page, :delete, content, url, options %>
  21 + <% end %>
  22 +
  23 + <% if @page.allow_spread?(user) && !remove_content_button(:spread, @page) %>
  24 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'publish', :id => @page.id }) %>
  25 + <%= expirable_button @page, :spread, content_tag( 'span', _('Spread this') ), url, {:class => 'colorbox'} if url %>
  26 + <% end %>
  27 +
  28 + <% if !@page.gallery? && (@page.allow_create?(user) || (@page.parent && @page.parent.allow_create?(user))) %>
  29 + <% if @page.translatable? && !@page.native_translation.language.blank? && !remove_content_button(:locale, @page) %>
  30 + <% content = _('Add translation') %>
  31 + <% parent_id = (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)) %>
  32 + <% url = profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => parent_id, :type => @page.type, :article => { :translation_of_id => @page.native_translation.id })%>
  33 + <%= expirable_button @page, :locale, content, url %>
  34 + <% end %>
  35 +
  36 + <%= 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) %>
  37 + <% end %>
  38 +
  39 + <% if @page.accept_uploads? && @page.allow_create?(user) %>
  40 + <%= button('upload-file', _('Upload files'), profile.admin_url.merge(:controller => 'cms', :action => 'upload_files', :parent_id => (@page.folder? ? @page : @page.parent))) unless remove_content_button(:upload, @page)%>
  41 + <% end %>
  42 +
  43 + <% if !@page.allow_create?(user) && profile.organization? && (@page.blog? || @page.parent && @page.parent.blog?) && !remove_content_button(:suggest, @page) %>
  44 + <% content = content_tag( 'span', _('Suggest an article') ) %>
  45 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'suggest_an_article'}) %>
  46 + <% options = {:id => 'suggest-article-link'} %>
  47 + <%= expirable_button @page, :suggest, content, url, options %>
  48 + <% end %>
  49 +
  50 + <% if @page.display_versions? %>
  51 + <%= button(:clock, _('All versions'), {:controller => 'content_viewer', :profile => profile.identifier, :action => 'article_versions'}, :id => 'article-versions-link') %>
  52 + <% end %>
  53 +
  54 + <% plugins_toolbar_actions_for_article(@page).each do |plugin_button| %>
  55 + <%= button plugin_button[:icon], plugin_button[:title], plugin_button[:url], plugin_button[:html_options] %>
  56 + <% end %>
  57 +
  58 + <%= report_abuse(profile, :link, @page) %>
  59 + </div>
  60 + <div id="article-header">
  61 + <% if @page.blog? and !@page.image.nil? %>
  62 + <div class="blog-cover"><%= image_tag(@page.image.public_filename())%></div>
  63 + <% end %>
  64 + <%= link_to(image_tag('icons-mime/rss-feed.png'), @page.feed.url, :class => 'blog-feed-link') if @page.has_posts? && @page.feed %>
  65 + <%= @plugins.dispatch(:article_header_extra_contents, @page).collect { |content| instance_exec(&content) }.join("") %>
  66 + <%= article_title(@page, :no_link => true) %>
  67 + <%= article_translations(@page) %>
  68 + </div>
  69 +</div>
... ...
app/views/profile_editor/_pending_tasks.html.erb
... ... @@ -4,7 +4,7 @@
4 4 <div class='pending-tasks'>
5 5 <h2><%= _('You have pending requests') %></h2>
6 6 <ul>
7   - <%= @pending_tasks.map {|task| content_tag('li', task_information(task))}.join %>
  7 + <%= @pending_tasks.take(10).map {|task| content_tag('li', task_information(task))}.join %>
8 8 </ul>
9 9 <%= button(:todo, _('Process requests'), :controller => 'tasks', :action => 'index') %>
10 10 </div>
... ...
app/views/tasks/_task.html.erb
... ... @@ -2,6 +2,16 @@
2 2  
3 3 <%= render :partial => 'task_icon', :locals => {:task => task} %>
4 4  
  5 + <% if profile.organization? && @responsible_candidates.present? %>
  6 + <div class="task_responsible">
  7 + <span class="label"><%= _('Assign to:') %></span>
  8 + <span>
  9 + <% change_responsible_url = url_for :action => :change_responsible, :controller => :tasks %>
  10 + <%= select_tag "tasks[#{task.id}][responsible]", options_from_collection_for_select(@responsible_candidates, :id, :name, task.responsible.present? ? task.responsible.id : nil), :include_blank => true, :onchange => "change_task_responsible(this);", 'data-old-responsible' => task.responsible.present? ? task.responsible.id : nil, 'data-task' => task.id, 'data-url' => change_responsible_url %>
  11 + </span>
  12 + </div>
  13 + <% end %>
  14 +
5 15 <div class="task_decisions">
6 16 <%=
7 17 labelled_radio_button(_("Accept"), "tasks[#{task.id}][decision]", 'finish', task.default_decision == 'accept',
... ... @@ -22,6 +32,8 @@
22 32 %>
23 33 </div><!-- class="task_decisions" -->
24 34  
  35 + <div class="task_date"><%= show_time(task.created_at) %></div>
  36 +
25 37 <%= render :partial => 'task_title', :locals => {:task => task} %>
26 38  
27 39 <div class="task_information">
... ... @@ -40,6 +52,13 @@
40 52 <%= render :partial => partial_for_class(task.class, nil, :reject_details), :locals => {:task => task, :f => f} %>
41 53 </div>
42 54 <% end %>
  55 +
  56 + <div class="formfieldline">
  57 + <div class="formfield tag-list-fields">
  58 + <%= labelled_text_field(_('Tags'),"tasks[#{task.id}][task][tag_list]", task.tag_list.to_s, :size => 36, :class => 'tag-list') %>
  59 + </div>
  60 + </div>
  61 +
43 62 <% end %>
44 63  
45 64 </div><!-- class="task_box" -->
... ...
app/views/tasks/index.html.erb
... ... @@ -21,11 +21,27 @@
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 + <%= labelled_select(_('Assigned to')+': ', :filter_responsible, :id, :name, @filter_responsible, [OpenStruct.new(:name => _('All'), :id => nil), OpenStruct.new(:name => _('Unassigned'), :id => -1)] + @responsible_candidates) %>
  34 + </p>
  35 + <p>
  36 + <%= submit_button(:search, _('Search')) %>
  37 + </p>
  38 + <% end %>
  39 +<% end %>
  40 +
24 41 <% if @tasks.empty? %>
25 42 <p>
26   - <%= labelled_select(_('Filter')+': ', :filter_type, :first, :last, @filter, type_collection, :onchange => "document.location.href = '?filter_type='+this.value")%>
  43 + <em><%= _('No pending tasks for %s') % profile.name %></em>
27 44 </p>
28   - <em><%= _('No pending tasks for %s') % profile.name %></em>
29 45 <% else %>
30 46 <%= form_tag :action => 'close' do%>
31 47 <% button_bar do %>
... ... @@ -38,19 +54,24 @@
38 54  
39 55 <ul class='task-list'>
40 56 <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 57 <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "up-set-all-tasks-to") %>
45 58 </p>
46   - <% @tasks.each do |task| %>
47   - <%= render :partial => 'task', :locals => { :task => task } %>
48   - <% end %>
  59 +
  60 + <div class="task_boxes">
  61 + <%= render :partial => 'task', :collection => @tasks %>
  62 + </div>
49 63 <p>
50 64 <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "down-set-all-tasks-to") %>
51 65 </p>
52 66 </ul>
53 67  
  68 + <script>
  69 + jQuery('.tag-list').inputosaurus({
  70 + autoCompleteSource: <%= "'/myprofile/#{profile.identifier}/cms/search_tags'," %>
  71 + activateFinalResult : true
  72 + })
  73 + </script>
  74 +
54 75 <%= pagination_links(@tasks)%>
55 76  
56 77 <% button_bar do %>
... ...
config/noosfero.yml.dist
... ... @@ -11,7 +11,14 @@ development:
11 11 max_upload_size: 5MB
12 12 hours_until_user_activation_check: 72
13 13 exclude_profile_identifier_pattern: index(\..*)?|home(\..*)?
  14 + api_recaptcha_site_key: '6LdsWAcTAAAAAChTUUD6yu9fCDhdIZzNd7F53zf-'
  15 + api_recaptcha_private_key: '6LdsWAcTAAAAAB6maB_HalVyCc4asDAxPxloIMvY'
  16 + api_recaptcha_verify_uri: 'https://www.google.com/recaptcha/api/siteverify'
14 17  
15 18 test:
16 19  
17 20 production:
  21 + api_recaptcha_site_key: '6LcLPAcTAAAAAKsd0bxY_TArhD_A7OL19SRCW7_i'
  22 + api_recaptcha_private_key: '6LcLPAcTAAAAAE36SN1M2w1I7Hn8upwXYZ_YQZ5-'
  23 + api_recaptcha_verify_uri: 'https://www.google.com/recaptcha/api/siteverify'
  24 +
18 25 \ No newline at end of file
... ...
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/migrate/20150525101430_add_responsible_to_task.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +class AddResponsibleToTask < ActiveRecord::Migration
  2 +
  3 + def change
  4 + add_column :tasks, :responsible_id, :integer
  5 + end
  6 +
  7 +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 => 20150408231524) 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"
... ...
lib/noosfero/api/api.rb
1 1 require 'grape'
2 2 #require 'rack/contrib'
  3 +
3 4 Dir["#{Rails.root}/lib/noosfero/api/*.rb"].each {|file| require file unless file =~ /api\.rb/}
4 5 module Noosfero
5 6 module API
6 7 class API < Grape::API
7 8 use Rack::JSONP
8   -
9   - logger = Logger.new(File.join(Rails.root, 'log', "#{ENV['RAILS_ENV'] || 'production'}_api.log"))
10   - logger.formatter = GrapeLogging::Formatters::Default.new
11   - use RequestLogger, { logger: logger }
12   -
13   - rescue_from :all do |e|
14   - logger.error e
  9 +
  10 + @@NOOSFERO_CONF = nil
  11 +
  12 + def self.NOOSFERO_CONF
  13 + if @@NOOSFERO_CONF
  14 + @@NOOSFERO_CONF
  15 + else
  16 + file = Rails.root.join('config', 'noosfero.yml')
  17 + @@NOOSFERO_CONF = File.exists?(file) ? YAML.load_file(file)[Rails.env] || {} : {}
  18 + end
15 19 end
16 20  
17 21 before { setup_multitenancy }
... ... @@ -22,9 +26,9 @@ module Noosfero
22 26 prefix "api"
23 27 format :json
24 28 content_type :txt, "text/plain"
25   -
  29 +
26 30 helpers APIHelpers
27   -
  31 +
28 32 mount V1::Articles
29 33 mount V1::Comments
30 34 mount V1::Users
... ... @@ -33,7 +37,7 @@ module Noosfero
33 37 mount V1::Enterprises
34 38 mount V1::Categories
35 39 mount Session
36   -
  40 +
37 41 # hook point which allow plugins to add Grape::API extensions to API::API
38 42 #finds for plugins which has api mount points classes defined (the class should extends Grape::API)
39 43 @plugins = Noosfero::Plugin.all.map { |p| p.constantize }
... ...
lib/noosfero/api/helpers.rb
... ... @@ -102,7 +102,21 @@ module Noosfero
102 102 end
103 103 attrs
104 104 end
105   -
  105 +
  106 + def verify_recaptcha_v2(remote_ip, g_recaptcha_response, private_key, api_recaptcha_verify_uri)
  107 + verify_hash = {
  108 + "secret" => private_key,
  109 + "remoteip" => remote_ip,
  110 + "response" => g_recaptcha_response
  111 + }
  112 + uri = URI(api_recaptcha_verify_uri)
  113 + https = Net::HTTP.new(uri.host, uri.port)
  114 + https.use_ssl = true
  115 + request = Net::HTTP::Post.new(uri.path)
  116 + request.set_form_data(verify_hash)
  117 + JSON.parse(https.request(request).body)
  118 + end
  119 +
106 120 ##########################################
107 121 # error helpers #
108 122 ##########################################
... ...
lib/noosfero/api/request_logger.rb
... ... @@ -7,10 +7,10 @@ module Noosfero
7 7 def parameters(response, duration)
8 8 {
9 9 path: request.path,
10   - params: request.params.except('password'),
  10 + params: request.params.to_hash.except('password'),
11 11 method: request.request_method,
12 12 total: (duration * 1000).round(2),
13   - db: request.env[:db_duration].round(2),
  13 + db: @db_duration.round(2),
14 14 }
15 15 end
16 16 end
... ...
lib/noosfero/api/session.rb
  1 +require "uri"
  2 +
1 3 module Noosfero
2 4 module API
3   -
  5 +
4 6 class Session < Grape::API
5   -
  7 +
6 8 # Login to get token
7 9 #
8 10 # Parameters:
... ... @@ -13,13 +15,13 @@ module Noosfero
13 15 # POST http://localhost:3000/api/v1/login?login=adminuser&password=admin
14 16 post "/login" do
15 17 user ||= User.authenticate(params[:login], params[:password], environment)
16   -
  18 +
17 19 return unauthorized! unless user
18 20 user.generate_private_token!
19 21 @current_user = user
20 22 present user, :with => Entities::UserLogin
21 23 end
22   -
  24 +
23 25 # Create user.
24 26 #
25 27 # Parameters:
... ... @@ -37,16 +39,20 @@ module Noosfero
37 39 unique_attributes! User, [:email, :login]
38 40 attrs = attributes_for_keys [:email, :login, :password]
39 41 attrs[:password_confirmation] = attrs[:password]
40   - user = User.new(attrs)
41   - if user.save
  42 + remote_ip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
  43 + private_key = API.NOOSFERO_CONF['api_recaptcha_private_key']
  44 + api_recaptcha_verify_uri = API.NOOSFERO_CONF['api_recaptcha_verify_uri']
  45 + captcha_result = verify_recaptcha_v2(remote_ip, params['g-recaptcha-response'], private_key, api_recaptcha_verify_uri)
  46 + user = User.new(attrs)
  47 + if captcha_result["success"] and user.save!
42 48 user.activate
43 49 user.generate_private_token!
44 50 present user, :with => Entities::UserLogin
45 51 else
46   - something_wrong!
  52 + message = user.errors.to_json
  53 + render_api_error!(message, 400)
47 54 end
48 55 end
49   -
50 56 end
51 57 end
52 58 end
... ...
lib/noosfero/api/v1/articles.rb
... ... @@ -34,8 +34,8 @@ module Noosfero
34 34 # FIXME verify allowed values
35 35 render_api_error!('Vote value not allowed', 400) unless [-1, 1].include?(value)
36 36 article = find_article(environment.articles, params[:id])
37   - Vote.create!(:voteable => article, :voter => nil, :vote => value)
38   - {:vote => true}
  37 + vote = Vote.new(:voteable => article, :voter => current_person, :vote => value)
  38 + {:vote => vote.save}
39 39 end
40 40  
41 41 get ':id/children' do
... ...
lib/noosfero/plugin.rb
... ... @@ -731,7 +731,7 @@ class Noosfero::Plugin
731 731 def content_actions
732 732 #FIXME 'new' and 'upload' only works for content_remove. It should work for
733 733 #content_expire too.
734   - %w[edit delete spread locale suggest home new upload undo]
  734 + %w[edit delete spread locale suggest home new upload undo clone]
735 735 end
736 736  
737 737 end
... ...
lib/tasks/enable_plugins.rake 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +namespace :noosfero do
  2 + namespace :plugins do
  3 + task :enable_all => :environment do
  4 + Environment.all.each do |env|
  5 + puts "Plugins Activated on #{env.name}" if env.enable_all_plugins
  6 + end
  7 + end
  8 + end
  9 +end
... ...
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/designs/icons/tango/style.css
... ... @@ -115,6 +115,7 @@
115 115 .icon-set-admin-role { background-image: url(mod/16x16/apps/user.png) }
116 116 .icon-reset-admin-role { background-image: url(../../../images/icons-app/person-icon.png) }
117 117 .icon-clock { background-image: url(Tango/16x16/actions/appointment.png) }
  118 +.icon-fullscreen { background-image: url(Tango/16x16/actions/view-fullscreen.png) }
118 119  
119 120 /******************LARGE ICONS********************/
120 121 .image-gallery-item .folder { background-image: url(mod/96x96/places/folder.png) }
... ...
public/javascripts/application.js
... ... @@ -1217,3 +1217,35 @@ function add_new_file_fields() {
1217 1217 }
1218 1218  
1219 1219 window.isHidden = function isHidden() { return (typeof(document.hidden) != 'undefined') ? document.hidden : !document.hasFocus() };
  1220 +
  1221 +function $_GET(id){
  1222 + var a = new RegExp(id+"=([^&#=]*)");
  1223 + return decodeURIComponent(a.exec(window.location.search)[1]);
  1224 +}
  1225 +
  1226 +var fullwidth=false;
  1227 +function toggle_fullwidth(itemId){
  1228 + if(fullwidth){
  1229 + jQuery(itemId).removeClass("fullwidth");
  1230 + jQuery("#fullscreen-btn").show()
  1231 + jQuery("#exit-fullscreen-btn").hide()
  1232 + fullwidth = false;
  1233 + }
  1234 + else{
  1235 + jQuery(itemId).addClass("fullwidth");
  1236 + jQuery("#exit-fullscreen-btn").show()
  1237 + jQuery("#fullscreen-btn").hide()
  1238 + fullwidth = true;
  1239 + }
  1240 + jQuery(window).trigger("toggleFullwidth", fullwidth);
  1241 +}
  1242 +
  1243 +function fullscreenPageLoad(itemId){
  1244 + jQuery(document).ready(function(){
  1245 +
  1246 + if ($_GET('fullscreen') == 1){
  1247 + toggle_fullwidth(itemId);
  1248 + }
  1249 + });
  1250 +}
  1251 +
... ...
public/javascripts/tasks.js
... ... @@ -63,3 +63,19 @@
63 63  
64 64 })(jQuery)
65 65  
  66 +function change_task_responsible(el) {
  67 + jQuery.post($(el).data('url'), {task_id: $(el).data('task'),
  68 + responsible_id: $(el).val(),
  69 + old_responsible_id: $(el).data('old-responsible')}, function(data) {
  70 + if (data.success) {
  71 + $(el).effect("highlight");
  72 + $(el).data('old-responsible', data.new_responsible.id);
  73 + } else {
  74 + $(el).effect("highlight", {color: 'red'});
  75 + }
  76 + if (data.notice) {
  77 + display_notice(data.notice);
  78 + }
  79 + });
  80 +}
  81 +
... ...
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 {
... ...
public/stylesheets/tasks.css
... ... @@ -17,6 +17,11 @@
17 17 font-size: 120%;
18 18 }
19 19  
  20 +.task_date {
  21 + color: gray;
  22 + font-size: 12px;
  23 +}
  24 +
20 25 .task_icon {
21 26 float: left;
22 27 margin-right: 10px;
... ... @@ -44,3 +49,27 @@
44 49 text-decoration: underline;
45 50 font-weight: bold;
46 51 }
  52 +
  53 +.task_responsible {
  54 + text-align: right;
  55 +}
  56 +
  57 +/* Task tags list */
  58 +
  59 +.formfieldline label {
  60 + font-weight: bold;
  61 + margin: 0 5px 0 10px;
  62 +}
  63 +
  64 +.formfieldline .inputosaurus-container {
  65 + vertical-align: middle;
  66 +}
  67 +
  68 +.tag-list-fields input {
  69 + background-color: #fff !important;
  70 +}
  71 +
  72 +.formfield.tag-list-fields .ui-autocomplete-loading {
  73 + background: url('/images/loading-small.gif') right center no-repeat !important;
  74 + background-color: #fff !important;
  75 +}
... ...
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
... ... @@ -80,6 +80,12 @@ class TasksControllerTest &lt; ActionController::TestCase
80 80 assert_kind_of Array, assigns(:tasks)
81 81 end
82 82  
  83 + should 'display task created_at' do
  84 + Task.create!(:requestor => fast_create(Person), :target => profile, :spam => false)
  85 + get :index
  86 + assert_select '.task_date'
  87 + end
  88 +
83 89 should 'list processed tasks without spam' do
84 90 requestor = fast_create(Person)
85 91 task_spam = create(Task, :status => Task::Status::FINISHED, :requestor => requestor, :target => profile, :spam => true)
... ... @@ -428,13 +434,13 @@ class TasksControllerTest &lt; ActionController::TestCase
428 434 t2 = CleanHouse.create!(:requestor => requestor, :target => profile)
429 435 t3 = FeedDog.create!(:requestor => requestor, :target => profile)
430 436  
431   - post :index, :filter_type => t1.type, :filter_text => 'test'
  437 + get :index, :filter_type => t1.type, :filter_text => 'test'
432 438  
433 439 assert_includes assigns(:tasks), t1
434 440 assert_not_includes assigns(:tasks), t2
435 441 assert_not_includes assigns(:tasks), t3
436 442  
437   - post :index
  443 + get :index
438 444  
439 445 assert_includes assigns(:tasks), t1
440 446 assert_includes assigns(:tasks), t2
... ... @@ -458,4 +464,82 @@ class TasksControllerTest &lt; ActionController::TestCase
458 464 post :index, :page => 2
459 465 assert_equal [t4], assigns(:tasks)
460 466 end
  467 +
  468 + should 'filter tasks by responsible' do
  469 + Task.stubs(:per_page).returns(3)
  470 + requestor = fast_create(Person)
  471 + responsible = fast_create(Person)
  472 + t1 = Task.create!(:requestor => requestor, :target => profile, :responsible => responsible)
  473 + t2 = Task.create!(:requestor => requestor, :target => profile, :responsible => responsible)
  474 + t3 = Task.create!(:requestor => requestor, :target => profile)
  475 +
  476 + get :index, :filter_responsible => responsible.id
  477 +
  478 + assert_includes assigns(:tasks), t1
  479 + assert_includes assigns(:tasks), t2
  480 + assert_not_includes assigns(:tasks), t3
  481 +
  482 + get :index
  483 +
  484 + assert_includes assigns(:tasks), t1
  485 + assert_includes assigns(:tasks), t2
  486 + assert_includes assigns(:tasks), t3
  487 + end
  488 +
  489 + should 'do not display responsible assignment if profile is not an organization' do
  490 + profile = create_user('personprofile').person
  491 + t1 = Task.create!(:requestor => profile, :target => profile)
  492 + @controller.stubs(:profile).returns(profile)
  493 + login_as profile.user.login
  494 + get :index
  495 +
  496 + assert_select "#task-#{t1.id}"
  497 + assert_select '.task_responsible', 0
  498 + end
  499 +
  500 + should 'display responsible assignment if profile is an organization' do
  501 + profile = fast_create(Community)
  502 + person1 = create_user('person1').person
  503 + person2 = create_user('person2').person
  504 + person3 = create_user('person3').person
  505 + profile.add_admin(person1)
  506 + profile.add_admin(person2)
  507 + profile.add_member(person3)
  508 + Task.create!(:requestor => person3, :target => profile)
  509 + @controller.stubs(:profile).returns(profile)
  510 +
  511 + login_as person1.user.login
  512 + get :index
  513 + assert_equivalent [person1, person2], assigns(:responsible_candidates)
  514 + assert_select '.task_responsible'
  515 + end
  516 +
  517 + should 'change task responsible' do
  518 + profile = fast_create(Community)
  519 + @controller.stubs(:profile).returns(profile)
  520 + person = create_user('person1').person
  521 + profile.add_admin(person)
  522 + task = Task.create!(:requestor => person, :target => profile)
  523 +
  524 + assert_equal nil, task.responsible
  525 + login_as person.user.login
  526 + post :change_responsible, :task_id => task.id, :responsible_id => person.id
  527 + assert_equal person, task.reload.responsible
  528 + end
  529 +
  530 + should 'not change task responsible if old responsible is not the current' do
  531 + profile = fast_create(Community)
  532 + @controller.stubs(:profile).returns(profile)
  533 + person1 = create_user('person1').person
  534 + person2 = create_user('person2').person
  535 + profile.add_admin(person1)
  536 + task = Task.create!(:requestor => person1, :target => profile, :responsible => person1)
  537 +
  538 + login_as person1.user.login
  539 + post :change_responsible, :task_id => task.id, :responsible_id => person2.id, :old_responsible => nil
  540 + assert_equal person1, task.reload.responsible
  541 + json_response = ActiveSupport::JSON.decode(response.body)
  542 + assert !json_response['success']
  543 + end
  544 +
461 545 end
... ...
test/unit/application_helper_test.rb
... ... @@ -1002,6 +1002,21 @@ class ApplicationHelperTest &lt; ActionView::TestCase
1002 1002 assert_equal file, from_theme_include('atheme', 'afile')[:file] # exists? = true
1003 1003 end
1004 1004  
  1005 + should 'enable fullscreen buttons' do
  1006 + html = fullscreen_buttons("#article")
  1007 + assert html.include?("<script>fullscreenPageLoad('#article')</script>")
  1008 + assert html.include?("class=\"button with-text icon-fullscreen\"")
  1009 + assert html.include?("onClick=\"toggle_fullwidth('#article')\"")
  1010 + end
  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 +
1005 1020 protected
1006 1021 include NoosferoTestHelper
1007 1022  
... ...
test/unit/article_test.rb
... ... @@ -2040,6 +2040,17 @@ class ArticleTest &lt; ActiveSupport::TestCase
2040 2040 assert_equal [], Article.display_filter(user, nil)
2041 2041 end
2042 2042  
  2043 + should 'display_filter show person public content to non friends passing nil as profile parameter' do
  2044 + user = create_user('someuser').person
  2045 + p = fast_create(Person)
  2046 + assert !p.is_a_friend?(user)
  2047 + assert !user.is_admin?
  2048 + Article.delete_all
  2049 + a1 = fast_create(Article, :profile_id => p.id)
  2050 + a2 = fast_create(Article)
  2051 + assert_equivalent [a1,a2], Article.display_filter(user, nil)
  2052 + end
  2053 +
2043 2054 should 'display_filter do not show community private content to non members passing nil as profile parameter' do
2044 2055 user = create_user('someuser').person
2045 2056 p = fast_create(Community)
... ... @@ -2049,6 +2060,16 @@ class ArticleTest &lt; ActiveSupport::TestCase
2049 2060 assert_equal [], Article.display_filter(user, nil)
2050 2061 end
2051 2062  
  2063 + should 'display_filter show community public content to non members passing nil as profile parameter' do
  2064 + user = create_user('someuser').person
  2065 + p = fast_create(Community)
  2066 + assert !user.is_member_of?(p)
  2067 + Article.delete_all
  2068 + a1 = fast_create(Article, :profile_id => p.id)
  2069 + a2 = fast_create(Article)
  2070 + assert_equivalent [a1,a2], Article.display_filter(user, nil)
  2071 + end
  2072 +
2052 2073 should 'display_filter show community public content of private community for user members' do
2053 2074 user = create_user('someuser').person
2054 2075 p = fast_create(Community, :public_profile => false)
... ...
test/unit/environment_test.rb
... ... @@ -524,7 +524,7 @@ class EnvironmentTest &lt; ActiveSupport::TestCase
524 524 p1= fast_create(Person, :is_template => true, :environment_id => e.id)
525 525 p2 = fast_create(Person, :environment_id => e.id)
526 526 p3 = fast_create(Person, :is_template => true, :environment_id => e.id)
527   - assert_equivalent [p1,p3], e.person_templates
  527 + assert_equivalent [p1,p3], e.person_templates
528 528 end
529 529  
530 530 should 'person_templates return an empty array if there is no templates of person' do
... ... @@ -532,7 +532,7 @@ class EnvironmentTest &lt; ActiveSupport::TestCase
532 532  
533 533 fast_create(Person, :environment_id => e.id)
534 534 fast_create(Person, :environment_id => e.id)
535   - assert_equivalent [], e.person_templates
  535 + assert_equivalent [], e.person_templates
536 536 end
537 537  
538 538 should 'person_default_template return the template defined as default' do
... ... @@ -585,7 +585,7 @@ class EnvironmentTest &lt; ActiveSupport::TestCase
585 585 c1= fast_create(Community, :is_template => true, :environment_id => e.id)
586 586 c2 = fast_create(Community, :environment_id => e.id)
587 587 c3 = fast_create(Community, :is_template => true, :environment_id => e.id)
588   - assert_equivalent [c1,c3], e.community_templates
  588 + assert_equivalent [c1,c3], e.community_templates
589 589 end
590 590  
591 591 should 'community_templates return an empty array if there is no templates of community' do
... ... @@ -646,7 +646,7 @@ class EnvironmentTest &lt; ActiveSupport::TestCase
646 646 e1= fast_create(Enterprise, :is_template => true, :environment_id => env.id)
647 647 e2 = fast_create(Enterprise, :environment_id => env.id)
648 648 e3 = fast_create(Enterprise, :is_template => true, :environment_id => env.id)
649   - assert_equivalent [e1,e3], env.enterprise_templates
  649 + assert_equivalent [e1,e3], env.enterprise_templates
650 650 end
651 651  
652 652 should 'enterprise_templates return an empty array if there is no templates of enterprise' do
... ... @@ -654,7 +654,7 @@ class EnvironmentTest &lt; ActiveSupport::TestCase
654 654  
655 655 fast_create(Enterprise, :environment_id => env.id)
656 656 fast_create(Enterprise, :environment_id => env.id)
657   - assert_equivalent [], env.enterprise_templates
  657 + assert_equivalent [], env.enterprise_templates
658 658 end
659 659  
660 660 should 'enterprise_default_template return the template defined as default' do
... ... @@ -1428,6 +1428,36 @@ class EnvironmentTest &lt; ActiveSupport::TestCase
1428 1428 assert !environment.plugin_enabled?(Plugin)
1429 1429 end
1430 1430  
  1431 + should 'activate on database all available plugins' do
  1432 + plugins_enable = ["Statistics", "Foo", "PeopleBlock"]
  1433 + Noosfero::Plugins.stubs(:available_plugin_names).returns(plugins_enable)
  1434 + env1 = Environment.create(:name => "Test")
  1435 + env2 = Environment.create(:name => "Test 2")
  1436 +
  1437 + env1.enable_all_plugins
  1438 + env2.enable_all_plugins
  1439 +
  1440 + plugins = ["PeopleBlockPlugin", "StatisticsPlugin", "FooPlugin"]
  1441 + plugins.each do |plugin|
  1442 + assert env1.enabled_plugins.include?(plugin)
  1443 + assert env2.enabled_plugins.include?(plugin)
  1444 + end
  1445 + end
  1446 +
  1447 + should 'dont activate plugins that are not available' do
  1448 + env1 = Environment.create(:name => "Test")
  1449 + env2 = Environment.create(:name => "Test 2")
  1450 +
  1451 + env1.enable_all_plugins
  1452 + env2.enable_all_plugins
  1453 +
  1454 + plugins = ["SomePlugin", "OtherPlugin", "ThirdPlugin"]
  1455 + plugins.each do |plugin|
  1456 + assert_equal false, env1.enabled_plugins.include?(plugin)
  1457 + assert_equal false, env2.enabled_plugins.include?(plugin)
  1458 + end
  1459 + end
  1460 +
1431 1461 should 'have production costs' do
1432 1462 assert_respond_to Environment.default, :production_costs
1433 1463 end
... ...
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
... ...
test/unit/task_test.rb
... ... @@ -444,6 +444,14 @@ class TaskTest &lt; ActiveSupport::TestCase
444 444 assert t1.ham?
445 445 end
446 446  
  447 + should 'be able to assign a responsible to a task' do
  448 + person = fast_create(Person)
  449 + task = fast_create(Task)
  450 + task.responsible = person
  451 + task.save!
  452 + assert_equal person, task.responsible
  453 + end
  454 +
447 455 protected
448 456  
449 457 def sample_user
... ...
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
... ...