Commit b05904aa8a770154a028a642cd9051babe36d100

Authored by Antonio Terceiro
2 parents 212e8b0b c5635326

Merge branch 'master' into rails3

Conflicts:
	app/helpers/boxes_helper.rb
	app/models/comment.rb
	app/views/comment/_comment.rhtml
	app/views/comment/_comment_form.rhtml
	app/views/content_viewer/_comment.html.erb
	app/views/content_viewer/_comment.rhtml
	app/views/content_viewer/_comment_form.html.erb
	app/views/content_viewer/_comment_form.rhtml
	app/views/profile/send_mail.html.erb
	config/routes.rb
	debian/control
Showing 147 changed files with 4492 additions and 1529 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 147 files displayed.

@@ -110,6 +110,7 @@ Diego Martinez <diegoamc90@gmail.com> @@ -110,6 +110,7 @@ Diego Martinez <diegoamc90@gmail.com>
110 Diego Martinez <diego@diego-K55A.(none)> 110 Diego Martinez <diego@diego-K55A.(none)>
111 Diego + Renan <renanteruoc@gmail.com> 111 Diego + Renan <renanteruoc@gmail.com>
112 Fernanda Lopes <nanda.listas+psl@gmail.com> 112 Fernanda Lopes <nanda.listas+psl@gmail.com>
  113 +Francisco Marcelo de Araujo Lima Junior <79350259591@serpro-1457614.(none)>
113 Grazieno Pellegrino <grazieno@gmail.com> 114 Grazieno Pellegrino <grazieno@gmail.com>
114 Isaac Canan <isaac@intelletto.com.br> 115 Isaac Canan <isaac@intelletto.com.br>
115 Italo Valcy <italo@dcc.ufba.br> 116 Italo Valcy <italo@dcc.ufba.br>
@@ -153,6 +154,7 @@ Leandro Nunes dos Santos &lt;leandronunes@gmail.com&gt; @@ -153,6 +154,7 @@ Leandro Nunes dos Santos &lt;leandronunes@gmail.com&gt;
153 Leandro Nunes dos Santos <leandro.santos@serpro.gov.br> 154 Leandro Nunes dos Santos <leandro.santos@serpro.gov.br>
154 LinguÁgil 2010 <linguagil.bahia@gmail.com> 155 LinguÁgil 2010 <linguagil.bahia@gmail.com>
155 Lucas Melo <lucas@colivre.coop.br> 156 Lucas Melo <lucas@colivre.coop.br>
  157 +Lucas Melo <lucaspradomelo@gmail.com>
156 Luis David Aguilar Carlos <ludwig9003@gmail.com> 158 Luis David Aguilar Carlos <ludwig9003@gmail.com>
157 Martín Olivera <molivera@solar.org.ar> 159 Martín Olivera <molivera@solar.org.ar>
158 Moises Machado <moises@colivre.coop.br> 160 Moises Machado <moises@colivre.coop.br>
@@ -6,15 +6,13 @@ To configure XMPP/BOSH in Noosfero you need: @@ -6,15 +6,13 @@ To configure XMPP/BOSH in Noosfero you need:
6 * SystemTimer - http://ph7spot.com/musings/system-timer 6 * SystemTimer - http://ph7spot.com/musings/system-timer
7 * Pidgin data files - http://www.pidgin.im/ 7 * Pidgin data files - http://www.pidgin.im/
8 8
9 -If you use Debian Lenny: 9 +If you use Debian Wheezy:
10 10
11 -# apt-get install librestclient-ruby (from backports)  
12 -# apt-get install pidgin-data  
13 -# apt-get install ruby1.8-dev 11 +# apt-get install librestclient-ruby pidgin-data ruby1.8-dev
14 # gem install SystemTimer 12 # gem install SystemTimer
15 13
16 -Take a look at util/chat directory to see samples of config file to configure a  
17 -XMPP/BOSH server with ejabberd, postgresql and apache2. 14 +The samples of config file to configure a XMPP/BOSH server with
  15 +ejabberd, postgresql and apache2 can be found at util/chat directory.
18 16
19 == XMPP/Chat Server Setup 17 == XMPP/Chat Server Setup
20 18
@@ -22,8 +20,7 @@ This is a step-by-step guide to get a XMPP service working, in a Debian system. @@ -22,8 +20,7 @@ This is a step-by-step guide to get a XMPP service working, in a Debian system.
22 20
23 1. Install the required packages 21 1. Install the required packages
24 22
25 -# apt-get -t lenny-backports install ejabberd  
26 -# apt-get install odbc-postgresql 23 +# apt-get install ejabberd odbc-postgresql
27 24
28 2. Ejabberd configuration 25 2. Ejabberd configuration
29 26
@@ -108,7 +105,7 @@ Unused modules can be disabled, for example: @@ -108,7 +105,7 @@ Unused modules can be disabled, for example:
108 * web_admin 105 * web_admin
109 * mod_pubsub 106 * mod_pubsub
110 * mod_irc 107 * mod_irc
111 - * mod_offine 108 + * mod_offline
112 * mod_admin_extra 109 * mod_admin_extra
113 * mod_register 110 * mod_register
114 111
@@ -132,7 +129,7 @@ This will create a new schema inside the noosfero database, called &#39;ejabberd&#39;. @@ -132,7 +129,7 @@ This will create a new schema inside the noosfero database, called &#39;ejabberd&#39;.
132 129
133 Note 'noosfero' user should have permission to create Postgresql schemas. Also, 130 Note 'noosfero' user should have permission to create Postgresql schemas. Also,
134 there should be at least one domain with 'is_default = true' in 'domains' 131 there should be at least one domain with 'is_default = true' in 'domains'
135 -table, otherwise people couldn't see your friends online. 132 +table, otherwise people won't be able to see their friends online.
136 133
137 134
138 4. ODBC configuration 135 4. ODBC configuration
@@ -168,9 +165,12 @@ Debug = 0 @@ -168,9 +165,12 @@ Debug = 0
168 CommLog = 1 165 CommLog = 1
169 UsageCount = 3 166 UsageCount = 3
170 167
171 - * testing all: 168 + 4.1 testing all:
172 169
173 -# isql 'PostgreSQLEjabberdNoosfero' DBUSER 170 +# isql 'PostgreSQLEjabberdNoosfero'
  171 +
  172 +If the configuration was done right, the message "Connected!"
  173 +will be displayed.
174 174
175 175
176 5. Enabling kernel polling and SMP in /etc/default/ejabberd 176 5. Enabling kernel polling and SMP in /etc/default/ejabberd
@@ -19,11 +19,12 @@ To prepare a release of noosfero, you must follow the steps below: @@ -19,11 +19,12 @@ To prepare a release of noosfero, you must follow the steps below:
19 19
20 * Finish all requirements and bugs assigned to the to-be-released version 20 * Finish all requirements and bugs assigned to the to-be-released version
21 * Make sure all tests pass 21 * Make sure all tests pass
22 -* Change the version in lib/noosfero.rb and debian/changelog to the  
23 - to-be-released version (e.g. 0.2.0, 0.3.1)  
24 * Write release notes at the version's wiki topic 22 * Write release notes at the version's wiki topic
25 -* Generate packages with <tt>rake noosfero:release</tt>. Your tarball and deb  
26 - pkg will be under the pkg/ directory. This task will create a git tag too. 23 +* Generate packages with <tt>rake noosfero:release[(stable|test)]</tt>. This task will:
  24 + * Update the version in lib/noosfero.rb and debian/changelog.
  25 + * Create the tarbal and the deb pkg under pkg/ directory.
  26 + * Create a git tag and push it.
  27 + * Upload the pkg to the configured repository (if configured) on ~/.dput.cf.
27 * Test that the tarball and deb package are ok 28 * Test that the tarball and deb package are ok
28 * Go to the version's wiki topic and edit it to reflect the new reality 29 * Go to the version's wiki topic and edit it to reflect the new reality
29 * Edit the topic WebPreferences and update DEBIAN_REPOSITORY_TOPICS setting 30 * Edit the topic WebPreferences and update DEBIAN_REPOSITORY_TOPICS setting
@@ -31,7 +32,6 @@ To prepare a release of noosfero, you must follow the steps below: @@ -31,7 +32,6 @@ To prepare a release of noosfero, you must follow the steps below:
31 sha1 of the package (with sha1sum and paste the SHA1 hash as comment in the 32 sha1 of the package (with sha1sum and paste the SHA1 hash as comment in the
32 attachment form) 33 attachment form)
33 * Download the attached and verify the MD5 hash 34 * Download the attached and verify the MD5 hash
34 -* Push the new version tag (with git push --tags)  
35 * Update an eventual demonstration version that you run. 35 * Update an eventual demonstration version that you run.
36 * Write an announcement e-mail to the relevant mailing lists pointing to the 36 * Write an announcement e-mail to the relevant mailing lists pointing to the
37 release notes, and maybe to the demonstration version. 37 release notes, and maybe to the demonstration version.
app/controllers/admin/trusted_sites_controller.rb 0 → 100644
@@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
  1 +class TrustedSitesController < AdminController
  2 + protect 'manage_environment_trusted_sites', :environment
  3 +
  4 + def index
  5 + @sites = environment.trusted_sites_for_iframe
  6 + end
  7 +
  8 + def new
  9 + @site = ""
  10 + end
  11 +
  12 + def create
  13 + if add_trusted_site(params[:site])
  14 + session[:notice] = _('New trusted site added.')
  15 + redirect_to :action => 'index'
  16 + else
  17 + session[:notice] = _('Failed to add trusted site.')
  18 + render :action => 'new'
  19 + end
  20 + end
  21 +
  22 + def edit
  23 + if is_trusted_site? params[:site]
  24 + @site = params[:site]
  25 + else
  26 + session[:notice] = _('Trusted site was not found')
  27 + redirect_to :action => 'index'
  28 + end
  29 + end
  30 +
  31 + def update
  32 + site = params[:site]
  33 + orig_site = params[:orig_site]
  34 + if rename_trusted_site(orig_site, site)
  35 + redirect_to :action => 'edit', :site => @site
  36 + else
  37 + session[:notice] = _('Failed to edit trusted site.')
  38 + render :action => 'edit'
  39 + end
  40 + end
  41 +
  42 + def destroy
  43 + if delete_trusted_site(params[:site])
  44 + session[:notice] = _('Trusted site removed')
  45 + else
  46 + session[:notice] = _('Trusted site could not be removed')
  47 + end
  48 + redirect_to :action => 'index'
  49 + end
  50 +
  51 + protected
  52 + def add_trusted_site (site)
  53 + trusted_sites = environment.trusted_sites_for_iframe
  54 + trusted_sites << site
  55 + environment.trusted_sites_for_iframe = trusted_sites
  56 + environment.save
  57 + end
  58 +
  59 + def rename_trusted_site(orig_site, site)
  60 + trusted_sites = environment.trusted_sites_for_iframe
  61 + i = trusted_sites.index orig_site
  62 + if i.nil?
  63 + return false
  64 + else
  65 + trusted_sites[i] = site
  66 + environment.trusted_sites_for_iframe = trusted_sites
  67 + environment.save
  68 + end
  69 + end
  70 +
  71 +
  72 + def delete_trusted_site (site)
  73 + trusted_sites = environment.trusted_sites_for_iframe
  74 + trusted_sites.delete site
  75 + environment.trusted_sites_for_iframe = trusted_sites
  76 + environment.save
  77 + end
  78 +
  79 + def is_trusted_site? (site)
  80 + environment.trusted_sites_for_iframe.include? site
  81 + end
  82 +end
app/controllers/application_controller.rb
@@ -6,6 +6,20 @@ class ApplicationController &lt; ActionController::Base @@ -6,6 +6,20 @@ class ApplicationController &lt; ActionController::Base
6 before_filter :setup_multitenancy 6 before_filter :setup_multitenancy
7 before_filter :detect_stuff_by_domain 7 before_filter :detect_stuff_by_domain
8 before_filter :init_noosfero_plugins 8 before_filter :init_noosfero_plugins
  9 + before_filter :allow_cross_domain_access
  10 +
  11 + def allow_cross_domain_access
  12 + origin = request.headers['Origin']
  13 + return if origin.blank?
  14 + if environment.access_control_allow_origin.include? origin
  15 + response.headers["Access-Control-Allow-Origin"] = origin
  16 + unless environment.access_control_allow_methods.blank?
  17 + response.headers["Access-Control-Allow-Methods"] = environment.access_control_allow_methods
  18 + end
  19 + elsif environment.restrict_to_access_control_origins
  20 + render_access_denied _('Origin not in allowed.')
  21 + end
  22 + end
9 23
10 include ApplicationHelper 24 include ApplicationHelper
11 layout :get_layout 25 layout :get_layout
@@ -82,11 +96,10 @@ class ApplicationController &lt; ActionController::Base @@ -82,11 +96,10 @@ class ApplicationController &lt; ActionController::Base
82 false 96 false
83 end 97 end
84 98
85 -  
86 def user 99 def user
87 current_user.person if logged_in? 100 current_user.person if logged_in?
88 end 101 end
89 - 102 +
90 alias :current_person :user 103 alias :current_person :user
91 104
92 # TODO: move this logic somewhere else (Domain class?) 105 # TODO: move this logic somewhere else (Domain class?)
@@ -104,6 +117,12 @@ class ApplicationController &lt; ActionController::Base @@ -104,6 +117,12 @@ class ApplicationController &lt; ActionController::Base
104 else 117 else
105 @environment = @domain.environment 118 @environment = @domain.environment
106 @profile = @domain.profile 119 @profile = @domain.profile
  120 +
  121 + # Check if the requested profile belongs to another domain
  122 + if @profile && !params[:profile].blank? && params[:profile] != @profile.identifier
  123 + @profile = @environment.profiles.find_by_identifier params[:profile]
  124 + redirect_to params.merge(:host => @profile.default_hostname)
  125 + end
107 end 126 end
108 end 127 end
109 128
@@ -156,7 +175,7 @@ class ApplicationController &lt; ActionController::Base @@ -156,7 +175,7 @@ class ApplicationController &lt; ActionController::Base
156 def find_by_contents(asset, scope, query, paginate_options={:page => 1}, options={}) 175 def find_by_contents(asset, scope, query, paginate_options={:page => 1}, options={})
157 scope = scope.send(options[:filter]) if options[:filter] 176 scope = scope.send(options[:filter]) if options[:filter]
158 177
159 - @plugins.first(:find_by_contents, asset, scope, query, paginate_options, options) || 178 + @plugins.dispatch_first(:find_by_contents, asset, scope, query, paginate_options, options) ||
160 fallback_find_by_contents(asset, scope, query, paginate_options, options) 179 fallback_find_by_contents(asset, scope, query, paginate_options, options)
161 end 180 end
162 181
app/controllers/my_profile/cms_controller.rb
@@ -182,7 +182,14 @@ class CmsController &lt; MyProfileController @@ -182,7 +182,14 @@ class CmsController &lt; MyProfileController
182 if request.post? 182 if request.post?
183 @article.destroy 183 @article.destroy
184 session[:notice] = _("\"#{@article.name}\" was removed.") 184 session[:notice] = _("\"#{@article.name}\" was removed.")
185 - redirect_to :action => (@article.parent ? 'view' : 'index'), :id => @article.parent 185 + referer = ActionController::Routing::Routes.recognize_path URI.parse(request.referer).path rescue nil
  186 + if referer and referer[:controller] == 'cms'
  187 + redirect_to referer
  188 + elsif @article.parent
  189 + redirect_to @article.parent.url
  190 + else
  191 + redirect_to profile.url
  192 + end
186 end 193 end
187 end 194 end
188 195
app/controllers/public/account_controller.rb
@@ -26,7 +26,8 @@ class AccountController &lt; ApplicationController @@ -26,7 +26,8 @@ class AccountController &lt; ApplicationController
26 26
27 # action to perform login to the application 27 # action to perform login to the application
28 def login 28 def login
29 - store_location(request.referer) unless session[:return_to] 29 + store_location(request.referer) unless params[:return_to] or session[:return_to]
  30 +
30 return unless request.post? 31 return unless request.post?
31 32
32 self.current_user = plugins_alternative_authentication 33 self.current_user = plugins_alternative_authentication
@@ -125,7 +126,7 @@ class AccountController &lt; ApplicationController @@ -125,7 +126,7 @@ class AccountController &lt; ApplicationController
125 def change_password 126 def change_password
126 if request.post? 127 if request.post?
127 @user = current_user 128 @user = current_user
128 - begin 129 + begin
129 @user.change_password!(params[:current_password], 130 @user.change_password!(params[:current_password],
130 params[:new_password], 131 params[:new_password],
131 params[:new_password_confirmation]) 132 params[:new_password_confirmation])
@@ -218,7 +219,7 @@ class AccountController &lt; ApplicationController @@ -218,7 +219,7 @@ class AccountController &lt; ApplicationController
218 @question = @enterprise.question 219 @question = @enterprise.question
219 return unless check_answer 220 return unless check_answer
220 return unless check_acceptance_of_terms 221 return unless check_acceptance_of_terms
221 - 222 +
222 activation = load_enterprise_activation 223 activation = load_enterprise_activation
223 if activation && user 224 if activation && user
224 activation.requestor = user 225 activation.requestor = user
@@ -355,7 +356,9 @@ class AccountController &lt; ApplicationController @@ -355,7 +356,9 @@ class AccountController &lt; ApplicationController
355 end 356 end
356 357
357 def go_to_initial_page 358 def go_to_initial_page
358 - if environment.enabled?('allow_change_of_redirection_after_login') 359 + if params[:return_to]
  360 + redirect_to params[:return_to]
  361 + elsif environment.enabled?('allow_change_of_redirection_after_login')
359 case user.preferred_login_redirection 362 case user.preferred_login_redirection
360 when 'keep_on_same_page' 363 when 'keep_on_same_page'
361 redirect_back_or_default(user.admin_url) 364 redirect_back_or_default(user.admin_url)
app/controllers/public/catalog_controller.rb
@@ -5,15 +5,14 @@ class CatalogController &lt; PublicController @@ -5,15 +5,14 @@ class CatalogController &lt; PublicController
5 before_filter :check_enterprise_and_environment 5 before_filter :check_enterprise_and_environment
6 6
7 def index 7 def index
8 - @category = params[:level] ? ProductCategory.find(params[:level]) : nil  
9 - @products = @profile.products.from_category(@category).paginate(:order => 'available desc, highlighted desc, name asc', :per_page => 9, :page => params[:page])  
10 - @categories = ProductCategory.on_level(params[:level]).order(:name) 8 + extend CatalogHelper
  9 + catalog_load_index
11 end 10 end
12 11
13 protected 12 protected
14 13
15 def check_enterprise_and_environment 14 def check_enterprise_and_environment
16 - unless @profile.kind_of?(Enterprise) && !@profile.environment.enabled?('disable_products_for_enterprises') 15 + unless profile.kind_of?(Enterprise) && !profile.environment.enabled?('disable_products_for_enterprises')
17 redirect_to :controller => 'profile', :profile => profile.identifier, :action => 'index' 16 redirect_to :controller => 'profile', :profile => profile.identifier, :action => 'index'
18 end 17 end
19 end 18 end
app/controllers/public/comment_controller.rb 0 → 100644
@@ -0,0 +1,164 @@ @@ -0,0 +1,164 @@
  1 +class CommentController < ApplicationController
  2 +
  3 + needs_profile
  4 +
  5 + before_filter :can_update?, :only => [:edit, :update]
  6 +
  7 + def create
  8 + begin
  9 + @page = profile.articles.find(params[:id])
  10 + rescue
  11 + @page = nil
  12 + end
  13 +
  14 + # page not found, give error
  15 + if @page.nil?
  16 + respond_to do |format|
  17 + format.js do
  18 + render :json => { :msg => _('Page not found.')}
  19 + end
  20 + end
  21 + return
  22 + end
  23 +
  24 + unless @page.accept_comments?
  25 + respond_to do |format|
  26 + format.js do
  27 + render :json => { :msg => _('Comment not allowed in this article')}
  28 + end
  29 + end
  30 + return
  31 + end
  32 +
  33 + @comment = Comment.new(params[:comment])
  34 + @comment.author = user if logged_in?
  35 + @comment.article = @page
  36 + @comment.ip_address = request.remote_ip
  37 + @comment.user_agent = request.user_agent
  38 + @comment.referrer = request.referrer
  39 + @plugins.dispatch(:filter_comment, @comment)
  40 +
  41 + if @comment.rejected?
  42 + respond_to do |format|
  43 + format.js do
  44 + render :json => { :msg => _('Comment was rejected')}
  45 + end
  46 + end
  47 + return
  48 + end
  49 +
  50 + if !@comment.valid? || (not pass_without_comment_captcha? and not verify_recaptcha(:model => @comment, :message => _('Please type the words correctly')))
  51 + respond_to do |format|
  52 + format.js do
  53 + render :json => {
  54 + :render_target => 'form',
  55 + :html => render_to_string(:partial => 'comment_form', :object => @comment, :locals => {:comment => @comment, :display_link => true, :show_form => true})
  56 + }
  57 + end
  58 + end
  59 + return
  60 + end
  61 +
  62 + if @comment.need_moderation?
  63 + @comment.created_at = Time.now
  64 + ApproveComment.create!(:requestor => @comment.author, :target => profile, :comment_attributes => @comment.attributes.to_json)
  65 +
  66 + respond_to do |format|
  67 + format.js do
  68 + render :json => { :render_target => nil, :msg => _('Your comment is waiting for approval.') }
  69 + end
  70 + end
  71 + return
  72 + end
  73 +
  74 + @comment.save
  75 +
  76 + respond_to do |format|
  77 + format.js do
  78 + comment_to_render = @comment.comment_root
  79 + render :json => {
  80 + :render_target => comment_to_render.anchor,
  81 + :html => render_to_string(:partial => 'comment', :locals => {:comment => comment_to_render, :display_link => true}),
  82 + :msg => _('Comment successfully created.')
  83 + }
  84 + end
  85 + end
  86 + end
  87 +
  88 + def destroy
  89 + comment = profile.comments_received.find(params[:id])
  90 +
  91 + if comment && comment.can_be_destroyed_by?(user) && comment.destroy
  92 + render :text => {'ok' => true}.to_json, :content_type => 'application/json'
  93 + else
  94 + session[:notice] = _("The comment was not removed.")
  95 + render :text => {'ok' => false}.to_json, :content_type => 'application/json'
  96 + end
  97 + end
  98 +
  99 + def mark_as_spam
  100 + comment = profile.comments_received.find(params[:id])
  101 + if comment.can_be_marked_as_spam_by?(user)
  102 + comment.spam!
  103 + render :text => {'ok' => true}.to_json, :content_type => 'application/json'
  104 + else
  105 + session[:notice] = _("You couldn't mark this comment as spam.")
  106 + render :text => {'ok' => false}.to_json, :content_type => 'application/json'
  107 + end
  108 + end
  109 +
  110 + def edit
  111 + render :partial => "comment_form", :locals => {:comment => @comment, :display_link => params[:reply_of_id].present?, :edition_mode => true, :show_form => true}
  112 + end
  113 +
  114 + def update
  115 + if @comment.update_attributes(params[:comment])
  116 + respond_to do |format|
  117 + format.js do
  118 + comment_to_render = @comment.comment_root
  119 + render :json => {
  120 + :ok => true,
  121 + :render_target => comment_to_render.anchor,
  122 + :html => render_to_string(:partial => 'comment', :locals => {:comment => comment_to_render})
  123 + }
  124 + end
  125 + end
  126 + else
  127 + respond_to do |format|
  128 + format.js do
  129 + render :json => {
  130 + :ok => false,
  131 + :render_target => 'form',
  132 + :html => render_to_string(:partial => 'comment_form', :object => @comment, :locals => {:comment => @comment, :display_link => false, :edition_mode => true, :show_form => true})
  133 + }
  134 + end
  135 + end
  136 + end
  137 + end
  138 +
  139 + def check_actions
  140 + comment = profile.comments_received.find(params[:id])
  141 + ids = @plugins.dispatch(:check_comment_actions, comment).collect do |action|
  142 + action.kind_of?(Proc) ? self.instance_eval(&action) : action
  143 + end.flatten.compact
  144 + render :json => {:ids => ids}
  145 + end
  146 +
  147 + protected
  148 +
  149 + def pass_without_comment_captcha?
  150 + logged_in? && !environment.enabled?('captcha_for_logged_users')
  151 + end
  152 + helper_method :pass_without_comment_captcha?
  153 +
  154 + def can_update?
  155 + begin
  156 + @comment = profile.comments_received.find(params[:id])
  157 + raise ActiveRecord::RecordNotFound unless @comment.can_be_updated_by?(user) # Not reveal that the comment exists
  158 + rescue ActiveRecord::RecordNotFound
  159 + render_not_found
  160 + return
  161 + end
  162 + end
  163 +
  164 +end
app/controllers/public/content_viewer_controller.rb
@@ -2,8 +2,6 @@ class ContentViewerController &lt; ApplicationController @@ -2,8 +2,6 @@ class ContentViewerController &lt; ApplicationController
2 2
3 needs_profile 3 needs_profile
4 4
5 - before_filter :comment_author, :only => :edit_comment  
6 -  
7 helper ProfileHelper 5 helper ProfileHelper
8 helper TagsHelper 6 helper TagsHelper
9 7
@@ -70,24 +68,8 @@ class ContentViewerController &lt; ApplicationController @@ -70,24 +68,8 @@ class ContentViewerController &lt; ApplicationController
70 68
71 @form_div = params[:form] 69 @form_div = params[:form]
72 70
73 - if params[:comment] && params[:confirm] == 'true'  
74 - @comment = Comment.new(params[:comment])  
75 - if request.post? && @page.accept_comments?  
76 - add_comment  
77 - end  
78 - else  
79 - @comment = Comment.new  
80 - end  
81 -  
82 - if request.post?  
83 - if params[:remove_comment]  
84 - remove_comment  
85 - return  
86 - elsif params[:mark_comment_as_spam]  
87 - mark_comment_as_spam  
88 - return  
89 - end  
90 - end 71 + #FIXME see a better way to do this. It's not need to pass this variable anymore
  72 + @comment = Comment.new
91 73
92 if @page.has_posts? 74 if @page.has_posts?
93 posts = if params[:year] and params[:month] 75 posts = if params[:year] and params[:month]
@@ -117,89 +99,18 @@ class ContentViewerController &lt; ApplicationController @@ -117,89 +99,18 @@ class ContentViewerController &lt; ApplicationController
117 end 99 end
118 end 100 end
119 101
120 - comments = @page.comments.without_spam  
121 - @comments = comments.as_thread  
122 - @comments_count = comments.count 102 + @comments = @page.comments.without_spam
  103 + @comments_count = @comments.count
  104 + @comments = @plugins.filter(:unavailable_comments, @comments.without_reply)
  105 + @comments = @comments.paginate(:per_page => per_page, :page => params[:comment_page] )
  106 +
123 if params[:slideshow] 107 if params[:slideshow]
124 render :action => 'slideshow', :layout => 'slideshow' 108 render :action => 'slideshow', :layout => 'slideshow'
125 end 109 end
126 end 110 end
127 111
128 - def edit_comment  
129 - path = params[:page].join('/')  
130 - @page = profile.articles.find_by_path(path)  
131 - @form_div = 'opened'  
132 - @comment = @page.comments.find_by_id(params[:id])  
133 - if @comment  
134 - if request.post?  
135 - begin  
136 - @comment.update_attributes(params[:comment])  
137 - session[:notice] = _('Comment succesfully updated')  
138 - redirect_to :action => 'view_page', :profile => profile.identifier, :page => @comment.article.explode_path  
139 - rescue  
140 - session[:notice] = _('Comment could not be updated')  
141 - end  
142 - end  
143 - else  
144 - redirect_to @page.view_url  
145 - session[:notice] = _('Could not find the comment in the article')  
146 - end  
147 - end  
148 -  
149 protected 112 protected
150 113
151 - def add_comment  
152 - @comment.author = user if logged_in?  
153 - @comment.article = @page  
154 - @comment.ip_address = request.remote_ip  
155 - @comment.user_agent = request.user_agent  
156 - @comment.referrer = request.referrer  
157 - plugins_filter_comment(@comment)  
158 - return if @comment.rejected?  
159 - if (pass_without_comment_captcha? || verify_recaptcha(:model => @comment, :message => _('Please type the words correctly'))) && @comment.save  
160 - @page.touch  
161 - @comment = nil # clear the comment form  
162 - redirect_to :action => 'view_page', :profile => params[:profile], :page => @page.explode_path, :view => params[:view]  
163 - else  
164 - @form_div = 'opened' if params[:comment][:reply_of_id].blank?  
165 - end  
166 - end  
167 -  
168 - def plugins_filter_comment(comment)  
169 - @plugins.each do |plugin|  
170 - plugin.filter_comment(comment)  
171 - end  
172 - end  
173 -  
174 - def pass_without_comment_captcha?  
175 - logged_in? && !environment.enabled?('captcha_for_logged_users')  
176 - end  
177 - helper_method :pass_without_comment_captcha?  
178 -  
179 - def remove_comment  
180 - @comment = @page.comments.find(params[:remove_comment])  
181 - if (user == @comment.author || user == @page.profile || user.has_permission?(:moderate_comments, @page.profile))  
182 - @comment.destroy  
183 - end  
184 - finish_comment_handling  
185 - end  
186 -  
187 - def mark_comment_as_spam  
188 - @comment = @page.comments.find(params[:mark_comment_as_spam])  
189 - if logged_in? && (user == @page.profile || user.has_permission?(:moderate_comments, @page.profile))  
190 - @comment.spam!  
191 - end  
192 - finish_comment_handling  
193 - end  
194 -  
195 - def finish_comment_handling  
196 - if request.xhr?  
197 - render :text => {'ok' => true}.to_json, :content_type => 'application/json'  
198 - else  
199 - redirect_to :action => 'view_page', :profile => params[:profile], :page => @page.explode_path, :view => params[:view]  
200 - end  
201 - end  
202 -  
203 def per_page 114 def per_page
204 12 115 12
205 end 116 end
@@ -223,13 +134,9 @@ class ContentViewerController &lt; ApplicationController @@ -223,13 +134,9 @@ class ContentViewerController &lt; ApplicationController
223 end 134 end
224 end 135 end
225 136
226 - def comment_author  
227 - comment = Comment.find_by_id(params[:id])  
228 - if comment  
229 - render_access_denied if comment.author.blank? || comment.author != user  
230 - else  
231 - render_not_found  
232 - end 137 + def pass_without_comment_captcha?
  138 + logged_in? && !environment.enabled?('captcha_for_logged_users')
233 end 139 end
  140 + helper_method :pass_without_comment_captcha?
234 141
235 end 142 end
app/controllers/public/profile_controller.rb
@@ -278,7 +278,7 @@ class ProfileController &lt; PublicController @@ -278,7 +278,7 @@ class ProfileController &lt; PublicController
278 end 278 end
279 279
280 def register_report 280 def register_report
281 - if !verify_recaptcha 281 + unless user.is_admin? || verify_recaptcha
282 render :text => { 282 render :text => {
283 :ok => false, 283 :ok => false,
284 :error => { 284 :error => {
app/helpers/application_helper.rb
@@ -32,6 +32,8 @@ module ApplicationHelper @@ -32,6 +32,8 @@ module ApplicationHelper
32 32
33 include AccountHelper 33 include AccountHelper
34 34
  35 + include CommentHelper
  36 +
35 include BlogHelper 37 include BlogHelper
36 38
37 include ContentViewerHelper 39 include ContentViewerHelper
@@ -956,10 +958,7 @@ module ApplicationHelper @@ -956,10 +958,7 @@ module ApplicationHelper
956 options.merge!(:page => params[:npage]) 958 options.merge!(:page => params[:npage])
957 content = article.to_html(options) 959 content = article.to_html(options)
958 content = content.kind_of?(Proc) ? self.instance_eval(&content).html_safe : content.html_safe 960 content = content.kind_of?(Proc) ? self.instance_eval(&content).html_safe : content.html_safe
959 - @plugins && @plugins.each do |plugin|  
960 - content = plugin.parse_content(content)  
961 - end  
962 - content 961 + filter_html(content, article)
963 end 962 end
964 963
965 # Please, use link_to by default! 964 # Please, use link_to by default!
@@ -1357,10 +1356,7 @@ module ApplicationHelper @@ -1357,10 +1356,7 @@ module ApplicationHelper
1357 options[:on_ready] ||= 'null' 1356 options[:on_ready] ||= 'null'
1358 1357
1359 result = text_field_tag(name, nil, text_field_options.merge(html_options.merge({:id => element_id}))) 1358 result = text_field_tag(name, nil, text_field_options.merge(html_options.merge({:id => element_id})))
1360 - result +=  
1361 - "  
1362 - <script type='text/javascript'>  
1363 - jQuery('##{element_id}') 1359 + result += javascript_tag("jQuery('##{element_id}')
1364 .tokenInput('#{url_for(search_action)}', { 1360 .tokenInput('#{url_for(search_action)}', {
1365 minChars: #{options[:min_chars].to_json}, 1361 minChars: #{options[:min_chars].to_json},
1366 prePopulate: #{options[:pre_populate].to_json}, 1362 prePopulate: #{options[:pre_populate].to_json},
@@ -1376,16 +1372,15 @@ module ApplicationHelper @@ -1376,16 +1372,15 @@ module ApplicationHelper
1376 onAdd: #{options[:on_add]}, 1372 onAdd: #{options[:on_add]},
1377 onDelete: #{options[:on_delete]}, 1373 onDelete: #{options[:on_delete]},
1378 onReady: #{options[:on_ready]}, 1374 onReady: #{options[:on_ready]},
1379 - })  
1380 - "  
1381 - result += options[:focus] ? ".focus();" : ";" 1375 + });
  1376 + ")
  1377 + result += javascript_tag("jQuery('##{element_id}').focus();") if options[:focus]
1382 if options[:avoid_enter] 1378 if options[:avoid_enter]
1383 - result += "jQuery('#token-input-#{element_id}') 1379 + result += javascript_tag("jQuery('#token-input-#{element_id}')
1384 .live('keydown', function(event){ 1380 .live('keydown', function(event){
1385 if(event.keyCode == '13') return false; 1381 if(event.keyCode == '13') return false;
1386 - });" 1382 + });")
1387 end 1383 end
1388 - result += "</script>"  
1389 result 1384 result
1390 end 1385 end
1391 1386
@@ -1396,12 +1391,12 @@ module ApplicationHelper @@ -1396,12 +1391,12 @@ module ApplicationHelper
1396 end 1391 end
1397 1392
1398 def expirable_button(content, action, text, url, options = {}) 1393 def expirable_button(content, action, text, url, options = {})
1399 - options[:class] = "button with-text icon-#{action.to_s}" 1394 + options[:class] = ["button with-text icon-#{action.to_s}", options[:class]].compact.join(' ')
1400 expirable_content_reference content, action, text, url, options 1395 expirable_content_reference content, action, text, url, options
1401 end 1396 end
1402 1397
1403 def expirable_comment_link(content, action, text, url, options = {}) 1398 def expirable_comment_link(content, action, text, url, options = {})
1404 - options[:class] = "comment-footer comment-footer-link comment-footer-hide" 1399 + options[:class] = ["comment-footer comment-footer-link comment-footer-hide", options[:class]].compact.join(' ')
1405 expirable_content_reference content, action, text, url, options 1400 expirable_content_reference content, action, text, url, options
1406 end 1401 end
1407 1402
@@ -1440,6 +1435,29 @@ module ApplicationHelper @@ -1440,6 +1435,29 @@ module ApplicationHelper
1440 @no_design_blocks = true 1435 @no_design_blocks = true
1441 end 1436 end
1442 1437
  1438 + def filter_html(html, source)
  1439 + if @plugins
  1440 + html = convert_macro(html, source)
  1441 + #TODO This parse should be done through the macro infra, but since there
  1442 + # are old things that do not support it we are keeping this hot spot.
  1443 + html = @plugins.pipeline(:parse_content, html, source).first
  1444 + end
  1445 + html
  1446 + end
  1447 +
  1448 + def convert_macro(html, source)
  1449 + doc = Hpricot(html)
  1450 + #TODO This way is more efficient but do not support macro inside of
  1451 + # macro. You must parse them from the inside-out in order to enable
  1452 + # that.
  1453 + doc.search('.macro').each do |macro|
  1454 + macro_name = macro['data-macro']
  1455 + result = @plugins.parse_macro(macro_name, macro, source)
  1456 + macro.inner_html = result.kind_of?(Proc) ? self.instance_eval(&result) : result
  1457 + end
  1458 + doc.html
  1459 + end
  1460 +
1443 def default_folder_for_image_upload(profile) 1461 def default_folder_for_image_upload(profile)
1444 default_folder = profile.folders.find_by_type('Gallery') 1462 default_folder = profile.folders.find_by_type('Gallery')
1445 default_folder = profile.folders.find_by_type('Folder') if default_folder.nil? 1463 default_folder = profile.folders.find_by_type('Folder') if default_folder.nil?
app/helpers/article_helper.rb
@@ -35,7 +35,13 @@ module ArticleHelper @@ -35,7 +35,13 @@ module ArticleHelper
35 'div', 35 'div',
36 check_box(:article, :notify_comments) + 36 check_box(:article, :notify_comments) +
37 content_tag('label', _('I want to receive a notification of each comment written by e-mail'), :for => 'article_notify_comments') + 37 content_tag('label', _('I want to receive a notification of each comment written by e-mail'), :for => 'article_notify_comments') +
38 - observe_field(:article_accept_comments, :function => "$('article_notify_comments').disabled = ! $('article_accept_comments').checked") 38 + observe_field(:article_accept_comments, :function => "$('article_notify_comments').disabled = ! $('article_accept_comments').checked;$('article_moderate_comments').disabled = ! $('article_accept_comments').checked")
  39 + ) +
  40 +
  41 + content_tag(
  42 + 'div',
  43 + check_box(:article, :moderate_comments) +
  44 + content_tag('label', _('I want to approve comments on this article'), :for => 'article_moderate_comments')
39 ) + 45 ) +
40 46
41 (article.can_display_hits? ? 47 (article.can_display_hits? ?
app/helpers/boxes_helper.rb
@@ -99,8 +99,8 @@ module BoxesHelper @@ -99,8 +99,8 @@ module BoxesHelper
99 unless block.visible? 99 unless block.visible?
100 options[:title] = _("This block is invisible. Your visitors will not see it.") 100 options[:title] = _("This block is invisible. Your visitors will not see it.")
101 end 101 end
102 - controller.send(:content_editor?) || @plugins.each do |plugin|  
103 - result = plugin.parse_content(result) 102 + if @controller.send(:content_editor?)
  103 + result = filter_html(result, block)
104 end 104 end
105 box_decorator.block_target(block.box, block) + 105 box_decorator.block_target(block.box, block) +
106 content_tag('div', 106 content_tag('div',
app/helpers/catalog_helper.rb
@@ -3,9 +3,18 @@ module CatalogHelper @@ -3,9 +3,18 @@ module CatalogHelper
3 include DisplayHelper 3 include DisplayHelper
4 include ManageProductsHelper 4 include ManageProductsHelper
5 5
  6 + def catalog_load_index options = {:page => params[:page], :show_categories => true}
  7 + if options[:show_categories]
  8 + @category = params[:level] ? ProductCategory.find(params[:level]) : nil
  9 + @categories = ProductCategory.on_level(params[:level]).order(:name)
  10 + end
  11 +
  12 + @products = profile.products.from_category(@category).paginate(:order => 'available desc, highlighted desc, name asc', :per_page => 9, :page => options[:page])
  13 + end
  14 +
6 def breadcrumb(category) 15 def breadcrumb(category)
7 - start = link_to(_('Start'), {:action => 'index'})  
8 - ancestors = category.ancestors.map { |c| link_to(c.name, {:action => 'index', :level => c.id}) }.reverse 16 + start = link_to(_('Start'), {:controller => :catalog, :action => 'index'})
  17 + ancestors = category.ancestors.map { |c| link_to(c.name, {:controller => :catalog, :action => 'index', :level => c.id}) }.reverse
9 current_level = content_tag('strong', category.name) 18 current_level = content_tag('strong', category.name)
10 all_items = [start] + ancestors + [current_level] 19 all_items = [start] + ancestors + [current_level]
11 content_tag('div', all_items.join(' &rarr; '), :id => 'breadcrumb') 20 content_tag('div', all_items.join(' &rarr; '), :id => 'breadcrumb')
@@ -15,7 +24,7 @@ module CatalogHelper @@ -15,7 +24,7 @@ module CatalogHelper
15 count = profile.products.from_category(category).count 24 count = profile.products.from_category(category).count
16 name = truncate(category.name, :length => 22 - count.to_s.size) 25 name = truncate(category.name, :length => 22 - count.to_s.size)
17 link_name = sub ? name : content_tag('strong', name) 26 link_name = sub ? name : content_tag('strong', name)
18 - link = link_to(link_name, {:action => 'index', :level => category.id}, :title => category.name) 27 + link = link_to(link_name, {:controller => :catalog, :action => 'index', :level => category.id}, :title => category.name)
19 content_tag('li', "#{link} (#{count})") if count > 0 28 content_tag('li', "#{link} (#{count})") if count > 0
20 end 29 end
21 30
app/helpers/comment_helper.rb 0 → 100644
@@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
  1 +module CommentHelper
  2 +
  3 + def article_title(article, args = {})
  4 + title = article.title
  5 + title = article.display_title if article.kind_of?(UploadedFile) && article.image?
  6 + title = content_tag('h1', h(title), :class => 'title')
  7 + if article.belongs_to_blog?
  8 + unless args[:no_link]
  9 + title = content_tag('h1', link_to(article.name, article.url), :class => 'title')
  10 + end
  11 + comments = ''
  12 + unless args[:no_comments] || !article.accept_comments
  13 + comments = (" - %s") % link_to_comments(article)
  14 + end
  15 + title << content_tag('span',
  16 + content_tag('span', show_date(article.published_at), :class => 'date') +
  17 + content_tag('span', [_(", by %s") % link_to(article.author_name, article.author_url)], :class => 'author') +
  18 + content_tag('span', comments, :class => 'comments'),
  19 + :class => 'created-at'
  20 + )
  21 + end
  22 + title
  23 + end
  24 +
  25 + def comment_actions(comment)
  26 + url = url_for(:profile => profile.identifier, :controller => :comment, :action => :check_actions, :id => comment.id)
  27 + links = links_for_comment_actions(comment)
  28 + content_tag(:li, link_to(content_tag(:span, _('Contents menu')), '#', :onclick => "toggleSubmenu(this,'',#{links.to_json}); return false", :class => 'menu-submenu-trigger comment-trigger', :url => url), :class=> 'vcard') unless links.empty?
  29 + end
  30 +
  31 + private
  32 +
  33 + def links_for_comment_actions(comment)
  34 + actions = [link_for_report_abuse(comment), link_for_spam(comment), link_for_edit(comment), link_for_remove(comment)]
  35 + @plugins.dispatch(:comment_actions, comment).collect do |action|
  36 + actions << (action.kind_of?(Proc) ? self.instance_eval(&action) : action)
  37 + end
  38 + actions.flatten.compact
  39 + end
  40 +
  41 + def link_for_report_abuse(comment)
  42 + if comment.author
  43 + report_abuse_link = report_abuse(comment.author, :comment_link, comment)
  44 + {:link => report_abuse_link} if report_abuse_link
  45 + end
  46 + end
  47 +
  48 + def link_for_spam(comment)
  49 + if comment.can_be_marked_as_spam_by?(user)
  50 + if comment.spam?
  51 + {:link => link_to_function(_('Mark as NOT SPAM'), 'remove_comment(this, %s); return false;' % url_for(:profile => profile.identifier, :mark_comment_as_ham => comment.id).to_json, :class => 'comment-footer comment-footer-link comment-footer-hide')}
  52 + else
  53 + {:link => link_to_function(_('Mark as SPAM'), 'remove_comment(this, %s, %s); return false;' % [url_for(:profile => profile.identifier, :controller => 'comment', :action => :mark_as_spam, :id => comment.id).to_json, _('Are you sure you want to mark this comment as SPAM?').to_json], :class => 'comment-footer comment-footer-link comment-footer-hide')}
  54 + end
  55 + end
  56 + end
  57 +
  58 + def link_for_edit(comment)
  59 + if comment.can_be_updated_by?(user)
  60 + {:link => expirable_comment_link(comment, :edit, _('Edit'), url_for(:profile => profile.identifier, :controller => :comment, :action => :edit, :id => comment.id),:class => 'colorbox')}
  61 + end
  62 + end
  63 +
  64 + def link_for_remove(comment)
  65 + if comment.can_be_destroyed_by?(user)
  66 + {:link => link_to_function(_('Remove'), 'remove_comment(this, %s, %s); return false ;' % [url_for(:profile => profile.identifier, :controller => 'comment', :action => :destroy, :id => comment.id).to_json, _('Are you sure you want to remove this comment and all its replies?').to_json], :class => 'comment-footer comment-footer-link comment-footer-hide remove-children')}
  67 + end
  68 + end
  69 +
  70 +end
app/helpers/content_viewer_helper.rb
@@ -3,13 +3,14 @@ module ContentViewerHelper @@ -3,13 +3,14 @@ module ContentViewerHelper
3 include BlogHelper 3 include BlogHelper
4 include ForumHelper 4 include ForumHelper
5 5
  6 + def display_number_of_comments(n)
  7 + base_str = "<span class='comment-count hide'>#{n}</span>"
  8 + amount_str = n == 0 ? _('no comments yet') : (n == 1 ? _('One comment') : _('%s comments') % n)
  9 + base_str + "<span class='comment-count-write-out'>#{amount_str}</span>"
  10 + end
  11 +
6 def number_of_comments(article) 12 def number_of_comments(article)
7 - n = article.comments.without_spam.count  
8 - if n == 0  
9 - _('No comments yet')  
10 - else  
11 - n_('One comment', '<span class="comment-count">%{comments}</span> comments', n) % { :comments => n }  
12 - end 13 + display_number_of_comments(article.comments.without_spam.count)
13 end 14 end
14 15
15 def article_title(article, args = {}) 16 def article_title(article, args = {})
app/helpers/forms_helper.rb
@@ -244,7 +244,7 @@ module FormsHelper @@ -244,7 +244,7 @@ module FormsHelper
244 yearSuffix: #{datepicker_options[:year_suffix].to_json} 244 yearSuffix: #{datepicker_options[:year_suffix].to_json}
245 }) 245 })
246 </script> 246 </script>
247 - " 247 + ".html_safe
248 result 248 result
249 end 249 end
250 250
app/helpers/forum_helper.rb
@@ -29,9 +29,9 @@ module ForumHelper @@ -29,9 +29,9 @@ module ForumHelper
29 css_add << 'not-published' if !art.published? 29 css_add << 'not-published' if !art.published?
30 css_add << position 30 css_add << position
31 content << content_tag('tr', 31 content << content_tag('tr',
32 - content_tag('td', link_to(art.title, art.url)) +  
33 - content_tag('td', link_to(art.comments.count, art.url.merge(:anchor => 'comments_list'))) +  
34 - content_tag('td', last_topic_update(art)), 32 + content_tag('td', link_to(art.title, art.url), :class => "forum-post-title") +
  33 + content_tag('td', link_to(art.comments.count, art.url.merge(:anchor => 'comments_list')), :class => "forum-post-answers") +
  34 + content_tag('td', last_topic_update(art), :class => "forum-post-last-answer"),
35 :class => 'forum-post ' + css_add.join(' '), 35 :class => 'forum-post ' + css_add.join(' '),
36 :id => "post-#{art.id}" 36 :id => "post-#{art.id}"
37 ) 37 )
app/helpers/layout_helper.rb
@@ -84,5 +84,11 @@ module LayoutHelper @@ -84,5 +84,11 @@ module LayoutHelper
84 theme_path + '/style.css' 84 theme_path + '/style.css'
85 end 85 end
86 86
  87 + def addthis_javascript
  88 + if NOOSFERO_CONF['addthis_enabled']
  89 + '<script src="http://s7.addthis.com/js/152/addthis_widget.js"></script>'
  90 + end
  91 + end
  92 +
87 end 93 end
88 94
app/helpers/macros_helper.rb 0 → 100644
@@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
  1 +module MacrosHelper
  2 +
  3 + def macros_in_menu
  4 + @plugins.dispatch(:macros).reject{ |macro| macro.configuration[:icon_path] }
  5 + end
  6 +
  7 + def macros_with_buttons
  8 + @plugins.dispatch(:macros).reject{ |macro| !macro.configuration[:icon_path] }
  9 + end
  10 +
  11 + def macro_title(macro)
  12 + macro.configuration[:title] || macro.name.humanize
  13 + end
  14 +
  15 + def generate_macro_config_dialog(macro)
  16 + if macro.configuration[:skip_dialog]
  17 + "function(){#{macro_generator(macro)}}"
  18 + else
  19 + "function(){
  20 + jQuery('<div>'+#{macro_configuration_dialog(macro).to_json}+'</div>').dialog({
  21 + title: #{macro_title(macro).to_json},
  22 + modal: true,
  23 + buttons: [
  24 + {text: #{_('Ok').to_json}, click: function(){
  25 + tinyMCE.activeEditor.execCommand('mceInsertContent', false,
  26 + (function(dialog){ #{macro_generator(macro)} })(this));
  27 + jQuery(this).dialog('close');
  28 + }},
  29 + {text: #{_('Cancel').to_json}, click: function(){jQuery(this).dialog('close');}}
  30 + ]
  31 + });
  32 + }"
  33 + end
  34 + end
  35 +
  36 + def include_macro_js_files
  37 + plugins_javascripts = []
  38 + @plugins.dispatch(:macros).map do |macro|
  39 + if macro.configuration[:js_files]
  40 + macro.configuration[:js_files].map { |js| plugins_javascripts << macro.plugin.public_path(js) }
  41 + end
  42 + end
  43 + javascript_include_tag(plugins_javascripts, :cache => 'cache/plugins-' + Digest::MD5.hexdigest(plugins_javascripts.to_s)) unless plugins_javascripts.empty?
  44 + end
  45 +
  46 + def macro_css_files
  47 + plugins_css = []
  48 + @plugins.dispatch(:macros).map do |macro|
  49 + if macro.configuration[:css_files]
  50 + macro.configuration[:css_files].map { |css| plugins_css << macro.plugin.public_path(css) }
  51 + end
  52 + end
  53 + plugins_css.join(',')
  54 + end
  55 +
  56 + protected
  57 +
  58 + def macro_generator(macro)
  59 + if macro.configuration[:generator]
  60 + macro.configuration[:generator]
  61 + else
  62 + macro_default_generator(macro)
  63 + end
  64 +
  65 + end
  66 +
  67 + def macro_default_generator(macro)
  68 + code = "var params = {};"
  69 + configuration = macro_configuration(macro)
  70 + configuration[:params].map do |field|
  71 + code += "params.#{field[:name]} = jQuery('*[name=#{field[:name]}]', dialog).val();"
  72 + end
  73 + code + "
  74 + var html = jQuery('<div class=\"macro mceNonEditable\" data-macro=\"#{macro.identifier}\">'+#{macro_title(macro).to_json}+'</div>')[0];
  75 + for(key in params) html.setAttribute('data-macro-'+key,params[key]);
  76 + return html.outerHTML;
  77 + "
  78 + end
  79 +
  80 + def macro_configuration_dialog(macro)
  81 + macro.configuration[:params].map do |field|
  82 + label_name = field[:label] || field[:name].to_s.humanize
  83 + case field[:type]
  84 + when 'text'
  85 + labelled_form_field(label_name, text_field_tag(field[:name], field[:default]))
  86 + when 'select'
  87 + labelled_form_field(label_name, select_tag(field[:name], options_for_select(field[:values], field[:default])))
  88 + end
  89 + end.join("\n")
  90 + end
  91 +
  92 +end
app/models/approve_comment.rb 0 → 100644
@@ -0,0 +1,104 @@ @@ -0,0 +1,104 @@
  1 +class ApproveComment < Task
  2 + validates_presence_of :target_id
  3 +
  4 + settings_items :comment_attributes, :closing_statment
  5 +
  6 + validates_presence_of :comment_attributes
  7 +
  8 + def comment
  9 + @comment ||= Comment.new(JSON.parse(self.comment_attributes)) unless self.comment_attributes.nil?
  10 + end
  11 +
  12 + def requestor_name
  13 + requestor ? requestor.name : (comment.name || _('Anonymous'))
  14 + end
  15 +
  16 + def article
  17 + Article.find_by_id comment.source_id unless self.comment.nil?
  18 + end
  19 +
  20 + def article_name
  21 + article ? article.name : _("Article removed.")
  22 + end
  23 +
  24 + def perform
  25 + comment.save!
  26 + end
  27 +
  28 + def title
  29 + _("New comment to article")
  30 + end
  31 +
  32 + def icon
  33 + result = {:type => :defined_image, :src => '/images/icons-app/article-minor.png'}
  34 + result.merge!({:url => article.url}) if article
  35 + result
  36 + end
  37 +
  38 + def linked_subject
  39 + {:text => article_name, :url => article.url} if article
  40 + end
  41 +
  42 + def information
  43 + if article
  44 + if requestor
  45 + {:message => _('%{requestor} commented on the the article: %{linked_subject}.')}
  46 + else
  47 + { :message => _('%{requestor} commented on the the article: %{linked_subject}.'),
  48 + :variables => {:requestor => requestor_name} }
  49 + end
  50 + else
  51 + {:message => _("The article was removed.")}
  52 + end
  53 + end
  54 +
  55 + def accept_details
  56 + true
  57 + end
  58 +
  59 + def reject_details
  60 + true
  61 + end
  62 +
  63 + def default_decision
  64 + if article
  65 + 'skip'
  66 + else
  67 + 'reject'
  68 + end
  69 + end
  70 +
  71 + def accept_disabled?
  72 + article.blank?
  73 + end
  74 +
  75 + def target_notification_description
  76 + if article
  77 + _('%{requestor} wants to comment the article: %{article}.') % {:requestor => requestor_name, :article => article.name}
  78 + else
  79 + _('%{requestor} wanted to comment the article but it was removed.') % {:requestor => requestor_name}
  80 + end
  81 + end
  82 +
  83 + def target_notification_message
  84 + target_notification_description + "\n\n" +
  85 + _('You need to login on %{system} in order to approve or reject this comment.') % { :system => target.environment.name }
  86 + end
  87 +
  88 + def task_finished_message
  89 + if !closing_statment.blank?
  90 + _("Your comment to the article \"%{article}\" was approved. Here is the comment left by the admin who approved your comment:\n\n%{comment} ") % {:article => article_name, :comment => closing_statment}
  91 + else
  92 + _('Your request for comment the article "%{article}" was approved.') % {:article => article_name}
  93 + end
  94 + end
  95 +
  96 + def task_cancelled_message
  97 + message = _('Your request for commenting the article "%{article}" was rejected.') % {:article => article_name}
  98 + if !reject_explanation.blank?
  99 + message += " " + _("Here is the reject explanation left by the administrator who rejected your comment: \n\n%{reject_explanation}") % {:reject_explanation => reject_explanation}
  100 + end
  101 + message
  102 + end
  103 +
  104 +end
app/models/article.rb
@@ -67,6 +67,7 @@ class Article &lt; ActiveRecord::Base @@ -67,6 +67,7 @@ class Article &lt; ActiveRecord::Base
67 settings_items :display_hits, :type => :boolean, :default => true 67 settings_items :display_hits, :type => :boolean, :default => true
68 settings_items :author_name, :type => :string, :default => "" 68 settings_items :author_name, :type => :string, :default => ""
69 settings_items :allow_members_to_edit, :type => :boolean, :default => false 69 settings_items :allow_members_to_edit, :type => :boolean, :default => false
  70 + settings_items :moderate_comments, :type => :boolean, :default => false
70 settings_items :followers, :type => Array, :default => [] 71 settings_items :followers, :type => Array, :default => []
71 72
72 belongs_to :reference_article, :class_name => "Article", :foreign_key => 'reference_article_id' 73 belongs_to :reference_article, :class_name => "Article", :foreign_key => 'reference_article_id'
@@ -329,6 +330,14 @@ class Article &lt; ActiveRecord::Base @@ -329,6 +330,14 @@ class Article &lt; ActiveRecord::Base
329 @view_url ||= image? ? url.merge(:view => true) : url 330 @view_url ||= image? ? url.merge(:view => true) : url
330 end 331 end
331 332
  333 + def comment_url_structure(comment, action = :edit)
  334 + if comment.new_record?
  335 + profile.url.merge(:page => path.split("/"), :controller => :comment, :action => :create)
  336 + else
  337 + profile.url.merge(:page => path.split("/"), :controller => :comment, :action => action || :edit, :id => comment.id)
  338 + end
  339 + end
  340 +
332 def allow_children? 341 def allow_children?
333 true 342 true
334 end 343 end
@@ -491,6 +500,14 @@ class Article &lt; ActiveRecord::Base @@ -491,6 +500,14 @@ class Article &lt; ActiveRecord::Base
491 allow_post_content?(user) || user && allow_members_to_edit && user.is_member_of?(profile) 500 allow_post_content?(user) || user && allow_members_to_edit && user.is_member_of?(profile)
492 end 501 end
493 502
  503 + def moderate_comments?
  504 + moderate_comments == true
  505 + end
  506 +
  507 + def comments_updated
  508 + solr_save
  509 + end
  510 +
494 def accept_category?(cat) 511 def accept_category?(cat)
495 !cat.is_a?(ProductCategory) 512 !cat.is_a?(ProductCategory)
496 end 513 end
@@ -597,7 +614,7 @@ class Article &lt; ActiveRecord::Base @@ -597,7 +614,7 @@ class Article &lt; ActiveRecord::Base
597 end 614 end
598 615
599 def lead 616 def lead
600 - abstract.blank? ? first_paragraph : abstract 617 + abstract.blank? ? first_paragraph.html_safe : abstract.html_safe
601 end 618 end
602 619
603 def short_lead 620 def short_lead
app/models/blog.rb
1 class Blog < Folder 1 class Blog < Folder
2 2
3 acts_as_having_posts 3 acts_as_having_posts
  4 + include PostsLimit
4 5
5 #FIXME This should be used until there is a migration to fix all blogs that 6 #FIXME This should be used until there is a migration to fix all blogs that
6 # already have folders inside them 7 # already have folders inside them
@@ -81,4 +82,8 @@ class Blog &lt; Folder @@ -81,4 +82,8 @@ class Blog &lt; Folder
81 posts.empty? 82 posts.empty?
82 end 83 end
83 84
  85 + def last_posts(limit=3)
  86 + posts.where("type != 'RssFeed'").order(:updated_at).limit(limit)
  87 + end
  88 +
84 end 89 end
app/models/blog_archives_block.rb
@@ -21,18 +21,22 @@ class BlogArchivesBlock &lt; Block @@ -21,18 +21,22 @@ class BlogArchivesBlock &lt; Block
21 end 21 end
22 22
23 def visible_posts(person) 23 def visible_posts(person)
24 - blog.posts.native_translations.select {|post| post.display_to?(person)} 24 + #FIXME Performance issues with display_to. Must convert it to a scope.
  25 + # Checkout this page for further information: http://noosfero.org/Development/ActionItem2705
  26 + blog.posts.published.native_translations #.select {|post| post.display_to?(person)}
25 end 27 end
26 28
27 def content(args={}) 29 def content(args={})
28 owner_blog = self.blog 30 owner_blog = self.blog
29 return nil unless owner_blog 31 return nil unless owner_blog
30 results = '' 32 results = ''
31 - visible_posts(args[:person]).group_by {|i| i.published_at.year }.sort_by { |year,count| -year }.each do |year, results_by_year|  
32 - results << content_tag('li', content_tag('strong', "#{year} (#{results_by_year.size})")) 33 + posts = visible_posts(args[:person])
  34 + posts.count(:all, :group => 'EXTRACT(YEAR FROM published_at)').sort_by {|year, count| -year.to_i}.each do |year, count|
  35 + results << content_tag('li', content_tag('strong', "#{year} (#{count})"))
33 results << "<ul class='#{year}-archive'>" 36 results << "<ul class='#{year}-archive'>"
34 - results_by_year.group_by{|i| [ ('%02d' % i.published_at.month()), gettext(MONTHS[i.published_at.month() - 1])]}.sort.reverse.each do |month, results_by_month|  
35 - results << content_tag('li', link_to("#{month[1]} (#{results_by_month.size})", owner_blog.url.merge(:year => year, :month => month[0]))) 37 + posts.count(:all, :conditions => ['EXTRACT(YEAR FROM published_at)=?', year], :group => 'EXTRACT(MONTH FROM published_at)').sort_by {|month, count| -month.to_i}.each do |month, count|
  38 + month_name = gettext(MONTHS[month.to_i - 1])
  39 + results << content_tag('li', link_to("#{month_name} (#{count})", owner_blog.url.merge(:year => year, :month => month)))
36 end 40 end
37 results << "</ul>" 41 results << "</ul>"
38 end 42 end
app/models/comment.rb
@@ -17,6 +17,7 @@ class Comment &lt; ActiveRecord::Base @@ -17,6 +17,7 @@ class Comment &lt; ActiveRecord::Base
17 belongs_to :reply_of, :class_name => 'Comment', :foreign_key => 'reply_of_id' 17 belongs_to :reply_of, :class_name => 'Comment', :foreign_key => 'reply_of_id'
18 18
19 scope :without_spam, :conditions => ['spam IS NULL OR spam = ?', false] 19 scope :without_spam, :conditions => ['spam IS NULL OR spam = ?', false]
  20 + scope :without_reply, :conditions => ['reply_of_id IS NULL']
20 scope :spam, :conditions => ['spam = ?', true] 21 scope :spam, :conditions => ['spam = ?', true]
21 22
22 # unauthenticated authors: 23 # unauthenticated authors:
@@ -34,7 +35,9 @@ class Comment &lt; ActiveRecord::Base @@ -34,7 +35,9 @@ class Comment &lt; ActiveRecord::Base
34 35
35 xss_terminate :only => [ :body, :title, :name ], :on => 'validation' 36 xss_terminate :only => [ :body, :title, :name ], :on => 'validation'
36 37
37 - delegate :environment, :to => :source 38 + def comment_root
  39 + (reply_of && reply_of.comment_root) || self
  40 + end
38 41
39 def action_tracker_target 42 def action_tracker_target
40 self.article.profile 43 self.article.profile
@@ -150,21 +153,6 @@ class Comment &lt; ActiveRecord::Base @@ -150,21 +153,6 @@ class Comment &lt; ActiveRecord::Base
150 @replies = comments_list 153 @replies = comments_list
151 end 154 end
152 155
153 - def self.as_thread  
154 - result = {}  
155 - root = []  
156 - order(:id).each do |c|  
157 - c.replies = []  
158 - result[c.id] ||= c  
159 - if result[c.reply_of_id]  
160 - result[c.reply_of_id].replies << c  
161 - else  
162 - root << c  
163 - end  
164 - end  
165 - root  
166 - end  
167 -  
168 include ApplicationHelper 156 include ApplicationHelper
169 def reported_version(options = {}) 157 def reported_version(options = {})
170 comment = self 158 comment = self
@@ -248,4 +236,22 @@ class Comment &lt; ActiveRecord::Base @@ -248,4 +236,22 @@ class Comment &lt; ActiveRecord::Base
248 plugins.dispatch(:comment_marked_as_ham, self) 236 plugins.dispatch(:comment_marked_as_ham, self)
249 end 237 end
250 238
  239 + def need_moderation?
  240 + article.moderate_comments? && (author.nil? || article.author != author)
  241 + end
  242 +
  243 + def can_be_destroyed_by?(user)
  244 + return if user.nil?
  245 + user == author || user == profile || user.has_permission?(:moderate_comments, profile)
  246 + end
  247 +
  248 + def can_be_marked_as_spam_by?(user)
  249 + return if user.nil?
  250 + user == profile || user.has_permission?(:moderate_comments, profile)
  251 + end
  252 +
  253 + def can_be_updated_by?(user)
  254 + user.present? && user == author
  255 + end
  256 +
251 end 257 end
app/models/enterprise_homepage.rb
@@ -20,7 +20,8 @@ class EnterpriseHomepage &lt; Article @@ -20,7 +20,8 @@ class EnterpriseHomepage &lt; Article
20 enterprise_homepage = self 20 enterprise_homepage = self
21 lambda do 21 lambda do
22 extend EnterpriseHomepageHelper 22 extend EnterpriseHomepageHelper
23 - @products = profile.products.paginate(:order => 'id asc', :per_page => 9, :page => 1) 23 + extend CatalogHelper
  24 + catalog_load_index :page => 1, :show_categories => false
24 render :partial => 'content_viewer/enterprise_homepage', :object => enterprise_homepage 25 render :partial => 'content_viewer/enterprise_homepage', :object => enterprise_homepage
25 end 26 end
26 end 27 end
app/models/environment.rb
@@ -28,6 +28,7 @@ class Environment &lt; ActiveRecord::Base @@ -28,6 +28,7 @@ class Environment &lt; ActiveRecord::Base
28 'manage_environment_users' => N_('Manage environment users'), 28 'manage_environment_users' => N_('Manage environment users'),
29 'manage_environment_templates' => N_('Manage environment templates'), 29 'manage_environment_templates' => N_('Manage environment templates'),
30 'manage_environment_licenses' => N_('Manage environment licenses'), 30 'manage_environment_licenses' => N_('Manage environment licenses'),
  31 + 'manage_environment_trusted_sites' => N_('Manage environment trusted sites')
31 } 32 }
32 33
33 module Roles 34 module Roles
@@ -270,6 +271,13 @@ class Environment &lt; ActiveRecord::Base @@ -270,6 +271,13 @@ class Environment &lt; ActiveRecord::Base
270 271
271 settings_items :search_hints, :type => Hash, :default => {} 272 settings_items :search_hints, :type => Hash, :default => {}
272 273
  274 + # Set to return http forbidden to host not on the allow origin list bellow
  275 + settings_items :restrict_to_access_control_origins, :default => false
  276 + # Set this according to http://www.w3.org/TR/cors/. Headers are set at every response
  277 + # For multiple domains acts as suggested in http://stackoverflow.com/questions/1653308/access-control-allow-origin-multiple-origin-domains
  278 + settings_items :access_control_allow_origin, :type => Array
  279 + settings_items :access_control_allow_methods, :type => String
  280 +
273 def news_amount_by_folder=(amount) 281 def news_amount_by_folder=(amount)
274 settings[:news_amount_by_folder] = amount.to_i 282 settings[:news_amount_by_folder] = amount.to_i
275 end 283 end
app/models/event.rb
@@ -16,7 +16,6 @@ class Event &lt; Article @@ -16,7 +16,6 @@ class Event &lt; Article
16 maybe_add_http(self.setting[:link]) 16 maybe_add_http(self.setting[:link])
17 end 17 end
18 18
19 - xss_terminate :only => [ :link ], :on => 'validation'  
20 xss_terminate :only => [ :body, :link, :address ], :with => 'white_list', :on => 'validation' 19 xss_terminate :only => [ :body, :link, :address ], :with => 'white_list', :on => 'validation'
21 20
22 def initialize(*args) 21 def initialize(*args)
app/models/forum.rb
1 class Forum < Folder 1 class Forum < Folder
2 2
3 acts_as_having_posts :order => 'updated_at DESC' 3 acts_as_having_posts :order => 'updated_at DESC'
  4 + include PostsLimit
4 5
5 def self.type_name 6 def self.type_name
6 _('Forum') 7 _('Forum')
app/models/layout_template.rb
@@ -15,6 +15,10 @@ class LayoutTemplate @@ -15,6 +15,10 @@ class LayoutTemplate
15 @id = id 15 @id = id
16 end 16 end
17 17
  18 + def name
  19 + @config['name']
  20 + end
  21 +
18 def title 22 def title
19 @config['title'] 23 @config['title']
20 end 24 end
app/models/posts_limit.rb 0 → 100644
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +module PostsLimit
  2 + module ClassMethods
  3 + def posts_per_page_limit
  4 + 15
  5 + end
  6 +
  7 + def posts_per_page_options
  8 + [5, 10, 15]
  9 + end
  10 + end
  11 +
  12 + def self.included(klass)
  13 + klass.send(:extend, PostsLimit::ClassMethods)
  14 + klass.class_eval do
  15 + def posts_per_page_with_limit
  16 + [self.class.posts_per_page_limit, posts_per_page_without_limit].min
  17 + end
  18 + alias_method_chain :posts_per_page, :limit
  19 + end
  20 + end
  21 +end
app/models/task.rb
@@ -74,7 +74,7 @@ class Task &lt; ActiveRecord::Base @@ -74,7 +74,7 @@ class Task &lt; ActiveRecord::Base
74 end 74 end
75 75
76 def self.all_types 76 def self.all_types
77 - %w[Invitation EnterpriseActivation AddMember Ticket SuggestArticle AddFriend CreateCommunity AbuseComplaint ApproveArticle CreateEnterprise ChangePassword EmailActivation InviteFriend InviteMember] 77 + %w[Invitation EnterpriseActivation AddMember Ticket SuggestArticle AddFriend CreateCommunity AbuseComplaint ApproveComment ApproveArticle CreateEnterprise ChangePassword EmailActivation InviteFriend InviteMember]
78 end 78 end
79 79
80 # this method finished the task. It calls #perform, which must be overriden 80 # this method finished the task. It calls #perform, which must be overriden
app/models/user.rb
@@ -312,7 +312,8 @@ class User &lt; ActiveRecord::Base @@ -312,7 +312,8 @@ class User &lt; ActiveRecord::Base
312 'email_domain' => self.enable_email ? self.email_domain : nil, 312 'email_domain' => self.enable_email ? self.email_domain : nil,
313 'friends_list' => friends_list, 313 'friends_list' => friends_list,
314 'enterprises' => enterprises, 314 'enterprises' => enterprises,
315 - 'amount_of_friends' => friends_list.count 315 + 'amount_of_friends' => friends_list.count,
  316 + 'chat_enabled' => person.environment.enabled?('xmpp_chat')
316 } 317 }
317 end 318 end
318 319
app/views/admin_panel/index.html.erb
@@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
9 <tr><td><%= link_to _('Sideboxes'), :controller => 'environment_design'%></td></tr> 9 <tr><td><%= link_to _('Sideboxes'), :controller => 'environment_design'%></td></tr>
10 <tr><td><%= link_to _('Homepage'), :action => 'set_portal_community' %></td></tr> 10 <tr><td><%= link_to _('Homepage'), :action => 'set_portal_community' %></td></tr>
11 <tr><td><%= link_to _('Licenses'), :controller =>'licenses' %></td></tr> 11 <tr><td><%= link_to _('Licenses'), :controller =>'licenses' %></td></tr>
  12 + <tr><td><%= link_to _('Trusted sites'), :controller =>'trusted_sites' %></td></tr>
12 </table> 13 </table>
13 14
14 <h2><%= _('Profiles') %></h2> 15 <h2><%= _('Profiles') %></h2>
app/views/catalog/index.html.erb
@@ -3,26 +3,28 @@ @@ -3,26 +3,28 @@
3 3
4 <h1><%= _('Products/Services') %></h1> 4 <h1><%= _('Products/Services') %></h1>
5 5
6 -<%= breadcrumb(@category) if params[:level] %>  
7 -  
8 -<div class='l-sidebar-left-bar'>  
9 - <ul>  
10 - <%= content_tag('li', link_to(_('Homepage'), profile.url), :class => 'catalog-categories-link') %>  
11 - <%= content_tag('li', link_to(_('Catalog start'), profile.catalog_url), :class => 'catalog-categories-link') %>  
12 - <% if @categories.present? %>  
13 - <% @categories.each do |category| %>  
14 - <%= category_link(category) %>  
15 - <%= category_sub_links(category) %> 6 +<% if @categories %>
  7 + <%= breadcrumb(@category) if params[:level] %>
  8 +
  9 + <div class='l-sidebar-left-bar'>
  10 + <ul>
  11 + <%= content_tag('li', link_to(_('Homepage'), profile.url), :class => 'catalog-categories-link') %>
  12 + <%= content_tag('li', link_to(_('Catalog start'), profile.catalog_url), :class => 'catalog-categories-link') %>
  13 + <% if @categories.present? %>
  14 + <% @categories.each do |category| %>
  15 + <%= category_link(category) %>
  16 + <%= category_sub_links(category) %>
  17 + <% end %>
  18 + <% elsif @category.present? %>
  19 + <%= content_tag('li', _('There are no sub-categories for %s') % @category.name, :id => 'catalog-categories-notice') %>
  20 + <% else %>
  21 + <%= content_tag('li', _('There are no categories available.'), :id => 'catalog-categories-notice') %>
16 <% end %> 22 <% end %>
17 - <% elsif @category.present? %>  
18 - <%= content_tag('li', _('There are no sub-categories for %s') % @category.name, :id => 'catalog-categories-notice') %>  
19 - <% else %>  
20 - <%= content_tag('li', _('There are no categories available.'), :id => 'catalog-categories-notice') %>  
21 - <% end %>  
22 - </ul>  
23 -</div>  
24 -  
25 -<ul id="product-list" class="l-sidebar-left-content"> 23 + </ul>
  24 + </div>
  25 +<% end %>
  26 +
  27 +<ul id="product-list" class="<%="l-sidebar-left-content" if @categories %>">
26 <% @products.each do |product| %> 28 <% @products.each do |product| %>
27 <% extra_content = @plugins.dispatch(:catalog_item_extras, product).collect { |content| instance_eval(&content) } %> 29 <% extra_content = @plugins.dispatch(:catalog_item_extras, product).collect { |content| instance_eval(&content) } %>
28 <% extra_content_list = @plugins.dispatch(:catalog_list_item_extras, product).collect { |content| instance_eval(&content) } %> 30 <% extra_content_list = @plugins.dispatch(:catalog_list_item_extras, product).collect { |content| instance_eval(&content) } %>
app/views/cms/_blog.html.erb
@@ -56,7 +56,7 @@ @@ -56,7 +56,7 @@
56 56
57 <%= labelled_form_field(_('How to display posts:'), f.select(:visualization_format, [ [ _('Full post'), 'full'], [ _('First paragraph'), 'short'] ])) %> 57 <%= labelled_form_field(_('How to display posts:'), f.select(:visualization_format, [ [ _('Full post'), 'full'], [ _('First paragraph'), 'short'] ])) %>
58 58
59 -<%= labelled_form_field(_('Posts per page:'), f.select(:posts_per_page, [5, 10, 20, 50, 100])) %> 59 +<%= labelled_form_field(_('Posts per page:'), f.select(:posts_per_page, Blog.posts_per_page_options)) %>
60 60
61 <%= labelled_check_box(_("List only translated posts"), 'article[display_posts_in_current_language]', '1', @article.display_posts_in_current_language?) %> 61 <%= labelled_check_box(_("List only translated posts"), 'article[display_posts_in_current_language]', '1', @article.display_posts_in_current_language?) %>
62 62
app/views/cms/_forum.html.erb
@@ -10,4 +10,4 @@ @@ -10,4 +10,4 @@
10 10
11 <%= labelled_form_field(_('Description:'), text_area(:article, :body, :cols => 64, :rows => 10)) %> 11 <%= labelled_form_field(_('Description:'), text_area(:article, :body, :cols => 64, :rows => 10)) %>
12 12
13 -<%= labelled_form_field(_('Posts per page:'), f.select(:posts_per_page, [5, 10, 20, 50, 100])) %> 13 +<%= labelled_form_field(_('Posts per page:'), f.select(:posts_per_page, Forum.posts_per_page_options)) %>
app/views/comment/_comment.rhtml 0 → 100644
@@ -0,0 +1,85 @@ @@ -0,0 +1,85 @@
  1 +<li id="<%= comment.anchor %>" class="article-comment">
  2 + <div class="article-comment-inner">
  3 +
  4 + <div class="comment-content comment-logged-<%= comment.author ? 'in' : 'out' %> <%= 'comment-from-owner' if ( comment.author && (profile == comment.author) ) %>">
  5 +
  6 + <% if comment.author %>
  7 + <%= link_to image_tag(profile_icon(comment.author, :minor)) +
  8 + content_tag('span', comment.author_name, :class => 'comment-info'),
  9 + comment.author.url,
  10 + :class => 'comment-picture',
  11 + :title => comment.author_name
  12 + %>
  13 + <% else %>
  14 + <% url_image, status_class = comment.author_id ?
  15 + [comment.removed_user_image, 'icon-user-removed'] :
  16 + [str_gravatar_url_for( comment.email, :size => 50, :d=>404 ), 'icon-user-unknown'] %>
  17 +
  18 + <%= link_to(
  19 + image_tag(url_image, :onerror=>'gravatarCommentFailback(this)',
  20 + 'data-gravatar'=>str_gravatar_url_for(comment.email, :size=>50)) +
  21 + content_tag('span', comment.author_name, :class => 'comment-info') +
  22 + content_tag('span', comment.message,
  23 + :class => 'comment-user-status ' + status_class),
  24 + gravatar_profile_url(comment.email),
  25 + :target => '_blank',
  26 + :class => 'comment-picture',
  27 + :title => '%s %s' % [comment.author_name, comment.message]
  28 + )%>
  29 + <% end %>
  30 +
  31 + <% comment_balloon do %>
  32 +
  33 + <div class="comment-details">
  34 + <div class="comment-header">
  35 + <ul>
  36 + <div class="comment-actions">
  37 + <%= comment_actions(comment) %>
  38 + </div>
  39 + </ul>
  40 + <% unless comment.spam? %>
  41 + <%= link_to_function '',
  42 + "var f = add_comment_reply_form(this, %s); f.find('comment_title, textarea').val(''); return false" % comment.id,
  43 + :class => 'comment-footer comment-footer-link comment-footer-hide comment-actions-reply button',
  44 + :id => 'comment-reply-to-' + comment.id.to_s,
  45 + :title => _('Reply')
  46 + %>
  47 + <% end %>
  48 + </div>
  49 +
  50 + <div class="comment-created-at">
  51 + <%= show_time(comment.created_at) %>
  52 + </div>
  53 + <h4><%= comment.title.blank? && '&nbsp;' || comment.title %></h4>
  54 + <div class="comment-text">
  55 + <p/>
  56 + <%= txt2html comment.body %>
  57 + </div>
  58 + </div>
  59 +
  60 + <div class="comment_reply post_comment_box closed" id="comment_reply_to_<%= comment.id %>">
  61 + <% if @comment && @comment.errors.any? && @comment.reply_of_id.to_i == comment.id %>
  62 + <%= error_messages_for :comment %>
  63 + <script type="text/javascript">
  64 + jQuery(function() {
  65 + document.location.href = '#<%= comment.anchor %>';
  66 + add_comment_reply_form('#comment-reply-to-<%= comment.id %>', <%= comment.id %>);
  67 + });
  68 + </script>
  69 + <% end %>
  70 + </div>
  71 +
  72 + <% end %>
  73 +
  74 + </div>
  75 +
  76 + <% unless comment.replies.blank? || comment.spam? %>
  77 + <ul class="comment-replies">
  78 + <% comment.replies.each do |reply| %>
  79 + <%= render :partial => 'comment/comment', :locals => { :comment => reply } %>
  80 + <% end %>
  81 + </ul>
  82 + <% end %>
  83 +
  84 + </div>
  85 +</li>
app/views/comment/_comment_form.rhtml 0 → 100644
@@ -0,0 +1,98 @@ @@ -0,0 +1,98 @@
  1 +<% edition_mode = (defined? edition_mode) ? edition_mode : false %>
  2 +<div class="<%= edition_mode ? '' : 'page-comment-form' %>">
  3 +
  4 +<% focus_on = logged_in? ? 'title' : 'name' %>
  5 +
  6 +
  7 +<% if !edition_mode && !pass_without_comment_captcha? %>
  8 + <div id="recaptcha-container" style="display: none">
  9 + <h3><%= _('Please type the two words below') %></h3>
  10 + <%= recaptcha_tags(:display => { :theme => 'clean' }, :ajax => true) %>
  11 + <% button_bar do %>
  12 + <%= button_to_function :add, _('Confirm'), "return false", :id => "confirm-captcha" %>
  13 + <%= button_to_function :cancel, _('Cancel'), "jQuery.colorbox.close()" %>
  14 + <% end %>
  15 + </div>
  16 +
  17 + <script type="text/javascript">
  18 + jQuery(document).bind('cbox_cleanup', function() {
  19 + jQuery('#recaptcha-container').hide();
  20 + });
  21 + </script>
  22 +<% end %>
  23 +
  24 +<script type="text/javascript">
  25 +function check_captcha(button, confirm_action) {
  26 + <% if edition_mode %>
  27 + return true;
  28 + <% elsif pass_without_comment_captcha? %>
  29 + button.form.confirm.value = 'true';
  30 + button.disabled = true;
  31 + return true;
  32 + <% else %>
  33 + jQuery('#recaptcha-container').show();
  34 + jQuery.colorbox({ inline : true, href : '#recaptcha-container', maxWidth : '600px', maxHeight : '300px' });
  35 + jQuery('#confirm-captcha').unbind('click');
  36 + jQuery('#confirm-captcha').bind('click', function() {
  37 + jQuery.colorbox.close();
  38 + button.form.recaptcha_response_field.value = jQuery('#recaptcha_response_field').val();
  39 + button.form.recaptcha_challenge_field.value = jQuery('#recaptcha_challenge_field').val();
  40 + button.form.confirm.value = 'true';
  41 + button.disabled = false;
  42 + confirm_action(button);
  43 + });
  44 + return false;
  45 + <% end %>
  46 +}
  47 +</script>
  48 +
  49 +<% if @comment && @comment.errors.any? %>
  50 + <%= error_messages_for :comment %>
  51 +<% end %>
  52 +
  53 +<div class="post_comment_box <%= ((defined? show_form) && show_form) ? 'opened' : 'closed' %>">
  54 +
  55 + <%= link_to(_('Post a comment'), '#', :class => 'display-comment-form') if display_link && @comment.reply_of_id.blank? %>
  56 +<% remote_form_for(:comment, comment, :url => {:profile => profile.identifier, :controller => 'comment', :action => (edition_mode ? 'update' : 'create'), :id => (edition_mode ? comment.id : @page.id)}, :html => { :class => 'comment_form' } ) do |f| %>
  57 +
  58 + <%= required_fields_message %>
  59 +
  60 + <% unless logged_in? %>
  61 +
  62 + <%= required labelled_form_field(_('Name'), f.text_field(:name)) %>
  63 + <%= required labelled_form_field(_('e-mail'), f.text_field(:email)) %>
  64 + <p>
  65 + <%= _('If you are a registered user, you can login and be automatically recognized.') %>
  66 + </p>
  67 +
  68 + <% end %>
  69 +
  70 + <% if !edition_mode && !pass_without_comment_captcha? %>
  71 + <%= hidden_field_tag(:recaptcha_response_field, nil, :id => nil) %>
  72 + <%= hidden_field_tag(:recaptcha_challenge_field, nil, :id => nil) %>
  73 + <% end %>
  74 +
  75 + <%= labelled_form_field(_('Title'), f.text_field(:title)) %>
  76 + <%= required labelled_form_field(_('Enter your comment'), f.text_area(:body, :rows => 5)) %>
  77 +
  78 + <%= hidden_field_tag(:confirm, 'false') %>
  79 + <%= hidden_field_tag(:view, params[:view])%>
  80 + <%= f.hidden_field(:reply_of_id) %>
  81 +
  82 + <%= @plugins.dispatch(:comment_form_extra_contents, local_assigns).collect { |content| instance_eval(&content) }.join("") %>
  83 +
  84 + <% button_bar do %>
  85 + <%= submit_button('add', _('Post comment'), :onclick => "if(check_captcha(this)) { save_comment(this) } else { check_captcha(this, save_comment)};return false;") %>
  86 + <% if !edition_mode %>
  87 + <%= button :cancel, _('Cancel'), '', :id => 'cancel-comment' %>
  88 + <% else %>
  89 + <%= button :cancel, _('Cancel'), '#', :onclick => "jQuery.colorbox.close();" %>
  90 + <% end %>
  91 + <% end %>
  92 +<% end %>
  93 +
  94 +
  95 +</div><!-- end class="post_comment_box" -->
  96 +</div><!-- end class="page-comment-form" -->
  97 +
  98 +<%= javascript_include_tag 'comment_form'%>
app/views/comment/edit.rhtml 0 → 100644
app/views/contact/new.html.erb
@@ -19,7 +19,10 @@ @@ -19,7 +19,10 @@
19 <%= labelled_form_field _('City and state'), location_fields %> 19 <%= labelled_form_field _('City and state'), location_fields %>
20 <% end %> 20 <% end %>
21 <%= required f.text_field(:subject) %> 21 <%= required f.text_field(:subject) %>
22 - <%= required f.text_area(:message, :rows => 10, :cols => 60) %> 22 +
  23 + <%= render :file => 'shared/tiny_mce' %>
  24 + <%= required f.text_area(:message, :class => 'mceEditor') %>
  25 +
23 <%= labelled_form_field check_box(:contact, :receive_a_copy) + _('I want to receive a copy of the message in my e-mail.'), '' %> 26 <%= labelled_form_field check_box(:contact, :receive_a_copy) + _('I want to receive a copy of the message in my e-mail.'), '' %>
24 27
25 <%= submit_button(:send, _('Send'), :onclick => "$('confirm').value = 'true'") %> 28 <%= submit_button(:send, _('Send'), :onclick => "$('confirm').value = 'true'") %>
app/views/content_viewer/edit_comment.html.erb
@@ -1,3 +0,0 @@ @@ -1,3 +0,0 @@
1 -<h1><%= _('Edit comment') %></h1>  
2 -  
3 -<%= render :partial => 'comment_form', :locals => {:url => {:action => 'edit_comment', :profile => profile.identifier}, :display_link => false, :cancel_triggers_hide => false} %>  
app/views/content_viewer/view_page.html.erb
@@ -8,6 +8,12 @@ @@ -8,6 +8,12 @@
8 8
9 <%= render :partial => 'confirm_unfollow' %> 9 <%= render :partial => 'confirm_unfollow' %>
10 10
  11 +<script type="text/javascript">
  12 + window.ONE_COMMENT = "<%= _('One comment') %>";
  13 + window.COMMENT_PLURAL = "<%= _('comments') %>";
  14 + window.NO_COMMENT_YET = "<%= _('No comments yet') %>";
  15 +</script>
  16 +
11 <div id="article-toolbar"></div> 17 <div id="article-toolbar"></div>
12 18
13 <script type="text/javascript"> 19 <script type="text/javascript">
@@ -60,7 +66,6 @@ @@ -60,7 +66,6 @@
60 addthis_options = '<%= escape_javascript( NOOSFERO_CONF['addthis_options'] ) %>'; 66 addthis_options = '<%= escape_javascript( NOOSFERO_CONF['addthis_options'] ) %>';
61 </script> 67 </script>
62 <a href="http://www.addthis.com/bookmark.php" id="bt_addThis" target="_blank" onmouseover="return addthis_open(this, '', '[URL]')" onmouseout="addthis_close()" onclick="return addthis_sendto()"><%= addthis_image_tag %></a> 68 <a href="http://www.addthis.com/bookmark.php" id="bt_addThis" target="_blank" onmouseover="return addthis_open(this, '', '[URL]')" onmouseout="addthis_close()" onclick="return addthis_sendto()"><%= addthis_image_tag %></a>
63 -<script type="text/javascript" src="http://s7.addthis.com/js/152/addthis_widget.js"></script>  
64 </div> 69 </div>
65 <% end %> 70 <% end %>
66 71
@@ -85,20 +90,21 @@ @@ -85,20 +90,21 @@
85 90
86 <% if @page.accept_comments? || @comments_count > 0 %> 91 <% if @page.accept_comments? || @comments_count > 0 %>
87 <h3 <%= 'class="no-comments-yet"' if @comments_count == 0 %>> 92 <h3 <%= 'class="no-comments-yet"' if @comments_count == 0 %>>
88 - <%= number_of_comments(@page) %> 93 + <%= display_number_of_comments(@comments_count) %>
89 </h3> 94 </h3>
90 <% end %> 95 <% end %>
91 96
92 - <% if @page.accept_comments? && @comments_count > 1 %>  
93 - <%= link_to(_('Post a comment'), '#', :class => 'display-comment-form', :id => 'top-post-comment-button') %> 97 + <% if @page.accept_comments? && @comments.count > 1 %>
  98 + <%= link_to(_('Post a comment'), '#', :class => 'display-comment-form', :id => 'top-post-comment-button', :onclick => "jQuery('#page-comment-form .display-comment-form').first().click();") %>
94 <% end %> 99 <% end %>
95 100
96 <ul class="article-comments-list"> 101 <ul class="article-comments-list">
97 - <%= render :partial => 'comment', :collection => @comments %> 102 + <%= render :partial => 'comment/comment', :collection => @comments %>
  103 + <%= pagination_links @comments, :param_name => 'comment_page' %>
98 </ul> 104 </ul>
99 105
100 <% if @page.accept_comments? %> 106 <% if @page.accept_comments? %>
101 - <div id="page-comment-form"><%= render :partial => 'comment_form', :locals => {:url => url_for(@page.view_url.merge({:only_path => true})), :display_link => true, :cancel_triggers_hide => true}%></div> 107 + <div id='page-comment-form' class='page-comment-form'><%= render :partial => 'comment/comment_form', :locals =>{:comment => Comment.new, :url => {:controller => :comment, :action => :create}, :display_link => true, :cancel_triggers_hide => true}%></div>
102 <% end %> 108 <% end %>
103 </div><!-- end class="comments" --> 109 </div><!-- end class="comments" -->
104 110
app/views/layouts/application-ng.html.erb
@@ -17,6 +17,10 @@ @@ -17,6 +17,10 @@
17 content.respond_to?(:call) ? content.call : content 17 content.respond_to?(:call) ? content.call : content
18 end.join("\n") 18 end.join("\n")
19 %> 19 %>
  20 +
  21 + <script type='text/javascript'>
  22 + DEFAULT_LOADING_MESSAGE = <%="'#{ _('loading...') }'" %>;
  23 + </script>
20 </head> 24 </head>
21 <body class="<%= body_classes %>"> 25 <body class="<%= body_classes %>">
22 <a href="#content" id="link-go-content"><span><%= _("Go to the content") %></span></a> 26 <a href="#content" id="link-go-content"><span><%= _("Go to the content") %></span></a>
@@ -56,5 +60,6 @@ @@ -56,5 +60,6 @@
56 </div><!-- end id="theme-footer" --> 60 </div><!-- end id="theme-footer" -->
57 <%= noosfero_layout_features %> 61 <%= noosfero_layout_features %>
58 <%= theme_javascript_ng %> 62 <%= theme_javascript_ng %>
  63 + <%= addthis_javascript %>
59 </body> 64 </body>
60 </html> 65 </html>
app/views/profile/report_abuse.html.erb
@@ -4,7 +4,9 @@ @@ -4,7 +4,9 @@
4 <%= hidden_field_tag(:content_type, params[:content_type]) %> 4 <%= hidden_field_tag(:content_type, params[:content_type]) %>
5 <%= hidden_field_tag(:content_id, params[:content_id]) %> 5 <%= hidden_field_tag(:content_id, params[:content_id]) %>
6 6
7 - <p><%= recaptcha_tags :ajax => true, :display => {:theme => 'clean'} %> </p> 7 + <% unless user.is_admin? %>
  8 + <p><%= recaptcha_tags :ajax => true, :display => {:theme => 'clean'} %> </p>
  9 + <% end %>
8 10
9 <%= submit_button(:send, _('Report profile'), :style => 'float: left; cursor: pointer;', :id => 'report-abuse-submit-button', :onclick => "jQuery('#form-submit-loading').show()") %> 11 <%= submit_button(:send, _('Report profile'), :style => 'float: left; cursor: pointer;', :id => 'report-abuse-submit-button', :onclick => "jQuery('#form-submit-loading').show()") %>
10 <%= button(:cancel, _('Cancel'), {}, :style => 'float: left; padding-top: 0px; padding-bottom: 0px;', :onclick => 'jQuery.colorbox.close(); return false;')%> 12 <%= button(:cancel, _('Cancel'), {}, :style => 'float: left; padding-top: 0px; padding-bottom: 0px;', :onclick => 'jQuery.colorbox.close(); return false;')%>
app/views/profile/send_mail.html.erb
@@ -4,11 +4,13 @@ @@ -4,11 +4,13 @@
4 4
5 <%= error_messages_for :mailing %> 5 <%= error_messages_for :mailing %>
6 6
7 -<%= render :file => 'shared/tiny_mce' %>  
8 -  
9 <%= form_for :mailing, :url => {:action => 'send_mail'}, :html => {:id => 'mailing-form'} do |f| %> 7 <%= form_for :mailing, :url => {:action => 'send_mail'}, :html => {:id => 'mailing-form'} do |f| %>
  8 +
10 <%= labelled_form_field(_('Subject:'), f.text_field(:subject)) %> 9 <%= labelled_form_field(_('Subject:'), f.text_field(:subject)) %>
  10 +
  11 + <%= render :file => 'shared/tiny_mce' %>
11 <%= labelled_form_field(_('Body:'), f.text_area(:body, :class => 'mceEditor')) %> 12 <%= labelled_form_field(_('Body:'), f.text_area(:body, :class => 'mceEditor')) %>
  13 +
12 <%= submit_button(:send, _('Send')) %> 14 <%= submit_button(:send, _('Send')) %>
13 <%= button :cancel, _('Cancel e-mail'), :back %> 15 <%= button :cancel, _('Cancel e-mail'), :back %>
14 <% end %> 16 <% end %>
app/views/search/_full_blog.html.erb
@@ -7,12 +7,12 @@ @@ -7,12 +7,12 @@
7 <tr class="search-blog-items"> 7 <tr class="search-blog-items">
8 <td class="search-field-label"><%= _("Last posts") %></td> 8 <td class="search-field-label"><%= _("Last posts") %></td>
9 9
10 - <% r = blog.children.find(:all, :order => :updated_at, :conditions => ['type != ?', 'RssFeed']).last(3) %>  
11 - <td class="<%= "search-field-none" if r.empty? %>">  
12 - <% r.each do |a| %>  
13 - <%= link_to a.title, a.view_url, :class => 'search-blog-sample-item '+icon_for_article(a) %> 10 + <% last_posts = blog.last_posts %>
  11 + <td class="<%= "search-field-none" if last_posts.empty? %>">
  12 + <% last_posts.each do |post| %>
  13 + <%= link_to post.title, post.view_url, :class => 'search-blog-sample-item '+icon_for_article(post) %>
14 <% end %> 14 <% end %>
15 - <%= _('None') if r.empty? %> 15 + <%= _('None') if last_posts.empty? %>
16 </td> 16 </td>
17 </tr> 17 </tr>
18 18
app/views/shared/logged_in/xmpp_chat.html.erb
@@ -6,6 +6,6 @@ @@ -6,6 +6,6 @@
6 </div> 6 </div>
7 </div> 7 </div>
8 <a href='#' id='chat-online-users-title' onclick='return false;'> 8 <a href='#' id='chat-online-users-title' onclick='return false;'>
9 - <div class='header'><i class='icon-chat'></i><span><%= _("Friends in chat <span class='amount_of_friends'>(%{amount})</span>") %></span></div> 9 + <div class='header'><i class='icon-chat'></i><span><%= _("Friends in chat (<span class='amount_of_friends'>%{amount}</span>)") %></span></div>
10 </a> 10 </a>
11 </div> 11 </div>
app/views/shared/tiny_mce.html.erb
  1 +<% extend MacrosHelper %>
1 <%= javascript_include_tag 'tinymce/jscripts/tiny_mce/tiny_mce.js' %> 2 <%= javascript_include_tag 'tinymce/jscripts/tiny_mce/tiny_mce.js' %>
  3 +<%= include_macro_js_files %>
2 <script type="text/javascript"> 4 <script type="text/javascript">
3 - var myplugins = "searchreplace,print,table,contextmenu"; 5 + var myplugins = "searchreplace,print,table,contextmenu,-macrosPlugin";
4 var first_line, second_line; 6 var first_line, second_line;
5 var mode = '<%= mode ||= false %>' 7 var mode = '<%= mode ||= false %>'
6 <% if mode %> 8 <% if mode %>
@@ -8,36 +10,80 @@ @@ -8,36 +10,80 @@
8 second_line = "" 10 second_line = ""
9 <% else %> 11 <% else %>
10 first_line = "print,separator,copy,paste,separator,undo,redo,separator,search,replace,separator,forecolor,fontsizeselect,formatselect" 12 first_line = "print,separator,copy,paste,separator,undo,redo,separator,search,replace,separator,forecolor,fontsizeselect,formatselect"
11 - second_line = "bold,italic,underline,strikethrough,separator,bullist,numlist,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,link,unlink,image,table,separator,cleanup,code" 13 + second_line = "bold,italic,underline,strikethrough,separator,bullist,numlist,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,link,unlink,image,table,separator,cleanup,code,macros"
  14 + <% macros_with_buttons.each do |macro| %>
  15 + second_line += ',<%=macro.identifier %>'
  16 + <% end %>
12 <% end %> 17 <% end %>
13 18
14 if (tinymce.isIE) { 19 if (tinymce.isIE) {
15 // the paste plugin is only useful in Internet Explorer 20 // the paste plugin is only useful in Internet Explorer
16 myplugins = "paste," + myplugins; 21 myplugins = "paste," + myplugins;
17 } 22 }
  23 +
  24 +tinymce.create('tinymce.plugins.MacrosPlugin', {
  25 + createControl: function(n, cm) {
  26 + switch (n) {
  27 + case 'macros':
  28 + <% unless macros_in_menu.empty? %>
  29 + var c = cm.createMenuButton('macros', {
  30 + title : 'Macros',
  31 + image : '/designs/icons/tango/Tango/16x16/emblems/emblem-system.png',
  32 + icons : false
  33 + });
  34 +
  35 + <% macros_in_menu.each do |macro| %>
  36 + c.onRenderMenu.add(function(c, m) {
  37 + m.add({
  38 + title: <%= macro_title(macro).to_json %>,
  39 + onclick: <%= generate_macro_config_dialog(macro) %>
  40 + });
  41 + });
  42 + <% end %>
  43 +
  44 + // Return the new menu button instance
  45 + return c;
  46 + <% end %>
  47 + }
  48 + return null;
  49 + }
  50 +});
  51 +
  52 +// Register plugin with a short name
  53 +tinymce.PluginManager.add('macrosPlugin', tinymce.plugins.MacrosPlugin);
  54 +
18 tinyMCE.init({ 55 tinyMCE.init({
19 - mode : "textareas",  
20 - editor_selector : "mceEditor",  
21 - theme : "advanced",  
22 - relative_urls : false,  
23 - remove_script_host : false,  
24 - document_base_url : <%= environment.top_url.to_json %>,  
25 - plugins: myplugins,  
26 - theme_advanced_toolbar_location : "top",  
27 - theme_advanced_layout_manager: 'SimpleLayout',  
28 - theme_advanced_buttons1 : first_line,  
29 - theme_advanced_buttons2 : second_line,  
30 - theme_advanced_buttons3 : "",  
31 - theme_advanced_blockformats :"p,address,pre,h2,h3,h4,h5,h6",  
32 - paste_auto_cleanup_on_paste : true,  
33 - paste_insert_word_content_callback : "convertWord",  
34 - paste_use_dialog: false,  
35 - apply_source_formatting : true,  
36 - extended_valid_elements : "applet[style|archive|codebase|code|height|width],comment,iframe[src|style|allowtransparency|frameborder|width|height|scrolling],embed[title|src|type|height|width]",  
37 - content_css: '/stylesheets/tinymce.css',  
38 - language: <%= tinymce_language.inspect %>,  
39 - entity_encoding: 'raw'  
40 - }); 56 + mode : "textareas",
  57 + editor_selector : "mceEditor",
  58 + theme : "advanced",
  59 + relative_urls : false,
  60 + remove_script_host : false,
  61 + document_base_url : <%= environment.top_url.to_json %>,
  62 + plugins: myplugins,
  63 + theme_advanced_toolbar_location : "top",
  64 + theme_advanced_layout_manager: 'SimpleLayout',
  65 + theme_advanced_buttons1 : first_line,
  66 + theme_advanced_buttons2 : second_line,
  67 + theme_advanced_buttons3 : "",
  68 + theme_advanced_blockformats :"p,address,pre,h2,h3,h4,h5,h6",
  69 + paste_auto_cleanup_on_paste : true,
  70 + paste_insert_word_content_callback : "convertWord",
  71 + paste_use_dialog: false,
  72 + apply_source_formatting : true,
  73 + extended_valid_elements : "applet[style|archive|codebase|code|height|width],comment,iframe[src|style|allowtransparency|frameborder|width|height|scrolling],embed[title|src|type|height|width]",
  74 + content_css: '/stylesheets/tinymce.css,<%= macro_css_files %>',
  75 + language: <%= tinymce_language.inspect %>,
  76 + entity_encoding: 'raw',
  77 + setup : function(ed) {
  78 + <% macros_with_buttons.each do |macro| %>
  79 + ed.addButton('<%= macro.identifier %>', {
  80 + title: <%= macro_title(macro).to_json %>,
  81 + onclick: <%= generate_macro_config_dialog(macro) %>,
  82 + image : '<%= macro.configuration[:icon_path]%>'
  83 + });
  84 + <% end %>
  85 + }
  86 +});
41 87
42 function convertWord(type, content) { 88 function convertWord(type, content) {
43 switch (type) { 89 switch (type) {
app/views/spam/index.html.erb
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 <div id='article'> 8 <div id='article'>
9 <div class="comments" id="comments_list"> 9 <div class="comments" id="comments_list">
10 <ul class="article-comments-list"> 10 <ul class="article-comments-list">
11 - <%= render :partial => 'content_viewer/comment', :collection => @spam %> 11 + <%= render :partial => 'comment/comment', :collection => @spam %>
12 </ul> 12 </ul>
13 </div> 13 </div>
14 </div> 14 </div>
app/views/tasks/_approve_comment_accept_details.rhtml 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +<p>
  2 + <b><%= _('Title: ') %></b>
  3 + <%= task.comment.title %>
  4 +</p>
  5 +<p>
  6 + <%= task.comment.body %>
  7 +</p>
app/views/themes/index.html.erb
@@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
12 "/designs/templates/#{template.id}/thumbnail.png", 12 "/designs/templates/#{template.id}/thumbnail.png",
13 :alt => _('The "%s" template')) + 13 :alt => _('The "%s" template')) +
14 '<div class="opt-info">'.html_safe + 14 '<div class="opt-info">'.html_safe +
15 - content_tag('strong', template.id, :class => 'name') + 15 + content_tag('strong', template.name, :class => 'name') +
16 ' <br/> '.html_safe 16 ' <br/> '.html_safe
17 17
18 if @current_template == template.id # selected 18 if @current_template == template.id # selected
app/views/trusted_sites/edit.rhtml 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +<h2> <%= _("Editing trusted site") %> </h2>
  2 +
  3 +<% form_tag :action => :update do %>
  4 +
  5 + <%= text_field_tag :site, @site %>
  6 + <%= hidden_field_tag :orig_site, @site %>
  7 +
  8 + <% button_bar do %>
  9 + <%= submit_button('save', _('Save changes'), :cancel => {:action => 'index'} ) %>
  10 + <% end %>
  11 +<% end %>
  12 +
  13 +<script>
  14 + jQuery(function() { jQuery('input#site').focus(); } );
  15 +</script>
app/views/trusted_sites/index.rhtml 0 → 100644
@@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
  1 +<h1><%= _('Manage trusted sites') %></h1>
  2 +
  3 +<p>
  4 +<%= _('Here you can manage the list of trusted sites of your environment. A trusted site is a site that you consider safe enough to incorporate their content through <em>iframes</em>.') %>
  5 +</p>
  6 +
  7 +<table>
  8 + <tr>
  9 + <th><%= _('Site') %></th>
  10 + <th><%= _('Actions') %></th>
  11 + </tr>
  12 + <% @sites.each do |site| %>
  13 + <tr>
  14 + <td>
  15 + <%= link_to site, :action => 'show', :site => site %>
  16 + </td>
  17 + <td style='white-space: nowrap;'>
  18 + <%= button_without_text :edit, _('Edit'), :action => 'edit', :site => site %>
  19 + <%= button_without_text :remove, _('Remove'), {:action => :destroy, :site => site}, :method => :delete, :confirm => _('Are you sure you want to remove this site from the list of trusted sites?') %>
  20 + </td>
  21 + </tr>
  22 + <% end %>
  23 +</table>
  24 +
  25 +<% button_bar do %>
  26 + <%= button :add, _('Add a trusted site'), :action => 'new' %>
  27 + <%= button :back, _('Back to admin panel'), :controller => 'admin_panel' %>
  28 +<% end %>
app/views/trusted_sites/new.rhtml 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +<h2> <%= _("Add a new trusted site") %> </h2>
  2 +
  3 +<% form_tag :action => :create do %>
  4 +
  5 + <%= text_field_tag :site, @site %>
  6 +
  7 + <% button_bar do %>
  8 + <%= submit_button('save', _('Add trusted site'), :cancel => {:action => 'index'} ) %>
  9 + <% end %>
  10 +<% end %>
  11 +
  12 +<script>
  13 + jQuery(function() { jQuery('input#site').focus(); } );
  14 +</script>
config/routes.rb
@@ -76,6 +76,9 @@ Noosfero::Application.routes.draw do @@ -76,6 +76,9 @@ Noosfero::Application.routes.draw do
76 # profile search 76 # profile search
77 match 'profile/:profile/search', :controller => 'profile_search', :action => 'index', :profile => /#{Noosfero.identifier_format}/ 77 match 'profile/:profile/search', :controller => 'profile_search', :action => 'index', :profile => /#{Noosfero.identifier_format}/
78 78
  79 + # comments
  80 + map.comment 'profile/:profile/comment/:action/:id', :controller => 'comment', :profile => /#{Noosfero.identifier_format}/
  81 +
79 # public profile information 82 # public profile information
80 match 'profile/:profile(/:action(/:id))', :controller => 'profile', :action => 'index', :id => /[^\/]*/, :profile => /#{Noosfero.identifier_format}/ 83 match 'profile/:profile(/:action(/:id))', :controller => 'profile', :action => 'index', :id => /[^\/]*/, :profile => /#{Noosfero.identifier_format}/
81 84
@@ -121,8 +124,6 @@ Noosfero::Application.routes.draw do @@ -121,8 +124,6 @@ Noosfero::Application.routes.draw do
121 # cache stuff - hack 124 # cache stuff - hack
122 match 'public/:action/:id', :controller => 'public' 125 match 'public/:action/:id', :controller => 'public'
123 126
124 - match ':profile/edit_comment/:id/*page', :controller => 'content_viewer', :action => 'edit_comment', :profile => /#{Noosfero.identifier_format}/  
125 -  
126 # match requests for profiles that don't have a custom domain 127 # match requests for profiles that don't have a custom domain
127 match ':profile(/*page)', :controller => 'content_viewer', :action => 'view_page', :profile => /#{Noosfero.identifier_format}/, :constraints => EnvironmentDomainConstraint.new 128 match ':profile(/*page)', :controller => 'content_viewer', :action => 'view_page', :profile => /#{Noosfero.identifier_format}/, :constraints => EnvironmentDomainConstraint.new
128 129
db/migrate/20130605135210_change_article_published_at_from_date_to_datetime.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class ChangeArticlePublishedAtFromDateToDatetime < ActiveRecord::Migration
  2 + def self.up
  3 + change_column :articles, :published_at, :datetime
  4 + end
  5 +
  6 + def self.down
  7 + change_column :articles, :published_at, :date
  8 + end
  9 +end
db/migrate/20130606110602_change_article_versions_published_at_from_date_to_datetime.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class ChangeArticleVersionsPublishedAtFromDateToDatetime < ActiveRecord::Migration
  2 + def self.up
  3 + change_column :article_versions, :published_at, :datetime
  4 + end
  5 +
  6 + def self.down
  7 + change_column :article_versions, :published_at, :date
  8 + end
  9 +end
db/migrate/20130711213046_add_manage_environment_trusted_sites_to_admin_role.rb 0 → 100644
@@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
  1 +class AddManageEnvironmentTrustedSitesToAdminRole < ActiveRecord::Migration
  2 + def self.up
  3 + Environment.all.map(&:id).each do |id|
  4 + role = Environment::Roles.admin(id)
  5 + role.permissions << 'manage_environment_trusted_sites'
  6 + role.save!
  7 + end
  8 + end
  9 +
  10 + def self.down
  11 + Environment.all.map(&:id).each do |id|
  12 + role = Environment::Roles.admin(id)
  13 + role.permissions -= ['manage_environment_trusted_sites']
  14 + role.save!
  15 + end
  16 + end
  17 +end
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 # 9 #
10 # It's strongly recommended to check this file into your version control system. 10 # It's strongly recommended to check this file into your version control system.
11 11
12 -ActiveRecord::Schema.define(:version => 20130429214630) do 12 +ActiveRecord::Schema.define(:version => 20130606110602) do
13 13
14 create_table "abuse_reports", :force => true do |t| 14 create_table "abuse_reports", :force => true do |t|
15 t.integer "reporter_id" 15 t.integer "reporter_id"
@@ -76,7 +76,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do @@ -76,7 +76,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do
76 t.text "setting" 76 t.text "setting"
77 t.boolean "notify_comments", :default => false 77 t.boolean "notify_comments", :default => false
78 t.integer "hits", :default => 0 78 t.integer "hits", :default => 0
79 - t.date "published_at" 79 + t.datetime "published_at"
80 t.string "source" 80 t.string "source"
81 t.boolean "highlighted", :default => false 81 t.boolean "highlighted", :default => false
82 t.string "external_link" 82 t.string "external_link"
@@ -119,7 +119,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do @@ -119,7 +119,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do
119 t.text "setting" 119 t.text "setting"
120 t.boolean "notify_comments", :default => true 120 t.boolean "notify_comments", :default => true
121 t.integer "hits", :default => 0 121 t.integer "hits", :default => 0
122 - t.date "published_at" 122 + t.datetime "published_at"
123 t.string "source" 123 t.string "source"
124 t.boolean "highlighted", :default => false 124 t.boolean "highlighted", :default => false
125 t.string "external_link" 125 t.string "external_link"
@@ -221,6 +221,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do @@ -221,6 +221,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do
221 t.string "source_type" 221 t.string "source_type"
222 t.string "user_agent" 222 t.string "user_agent"
223 t.string "referrer" 223 t.string "referrer"
  224 + t.integer "group_id"
224 end 225 end
225 226
226 add_index "comments", ["source_id", "spam"], :name => "index_comments_on_source_id_and_spam" 227 add_index "comments", ["source_id", "spam"], :name => "index_comments_on_source_id_and_spam"
@@ -264,11 +265,11 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do @@ -264,11 +265,11 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do
264 t.text "design_data" 265 t.text "design_data"
265 t.text "custom_header" 266 t.text "custom_header"
266 t.text "custom_footer" 267 t.text "custom_footer"
267 - t.string "theme", :default => "default", :null => false 268 + t.string "theme", :default => "default", :null => false
268 t.text "terms_of_use_acceptance_text" 269 t.text "terms_of_use_acceptance_text"
269 t.datetime "created_at" 270 t.datetime "created_at"
270 t.datetime "updated_at" 271 t.datetime "updated_at"
271 - t.integer "reports_lower_bound", :default => 0, :null => false 272 + t.integer "reports_lower_bound", :default => 0, :null => false
272 t.string "redirection_after_login", :default => "keep_on_same_page" 273 t.string "redirection_after_login", :default => "keep_on_same_page"
273 t.text "signup_welcome_text" 274 t.text "signup_welcome_text"
274 t.string "languages" 275 t.string "languages"
@@ -428,7 +429,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do @@ -428,7 +429,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do
428 t.string "type" 429 t.string "type"
429 t.string "identifier" 430 t.string "identifier"
430 t.integer "environment_id" 431 t.integer "environment_id"
431 - t.boolean "active", :default => true 432 + t.boolean "active", :default => true
432 t.string "address" 433 t.string "address"
433 t.string "contact_phone" 434 t.string "contact_phone"
434 t.integer "home_page_id" 435 t.integer "home_page_id"
@@ -439,21 +440,21 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do @@ -439,21 +440,21 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do
439 t.float "lat" 440 t.float "lat"
440 t.float "lng" 441 t.float "lng"
441 t.integer "geocode_precision" 442 t.integer "geocode_precision"
442 - t.boolean "enabled", :default => true  
443 - t.string "nickname", :limit => 16 443 + t.boolean "enabled", :default => true
  444 + t.string "nickname", :limit => 16
444 t.text "custom_header" 445 t.text "custom_header"
445 t.text "custom_footer" 446 t.text "custom_footer"
446 t.string "theme" 447 t.string "theme"
447 - t.boolean "public_profile", :default => true 448 + t.boolean "public_profile", :default => true
448 t.date "birth_date" 449 t.date "birth_date"
449 t.integer "preferred_domain_id" 450 t.integer "preferred_domain_id"
450 t.datetime "updated_at" 451 t.datetime "updated_at"
451 - t.boolean "visible", :default => true 452 + t.boolean "visible", :default => true
452 t.integer "image_id" 453 t.integer "image_id"
453 - t.boolean "validated", :default => true 454 + t.boolean "validated", :default => true
454 t.string "cnpj" 455 t.string "cnpj"
455 t.string "national_region_code" 456 t.string "national_region_code"
456 - t.boolean "is_template", :default => false 457 + t.boolean "is_template", :default => false
457 t.integer "template_id" 458 t.integer "template_id"
458 t.string "redirection_after_login" 459 t.string "redirection_after_login"
459 end 460 end
debian/changelog
  1 +noosfero (0.44.0) unstable; urgency=low
  2 +
  3 + * New features release
  4 +
  5 + -- Rodrigo Souto <rodrigo@colivre.coop.br> Wed, 17 Jul 2013 02:19:44 -0300
  6 +
  7 +noosfero (0.43.1) unstable; urgency=low
  8 +
  9 + * Bugfix release
  10 +
  11 + -- Rodrigo Souto <rodrigo@colivre.coop.br> Wed, 17 Jul 2013 01:20:46 -0300
  12 +
  13 +noosfero (0.43.0) unstable; urgency=low
  14 +
  15 + * Solr search converted to a plugin
  16 +
  17 + -- Rodrigo Souto <rodrigo@ravioli> Tue, 18 Jun 2013 11:14:56 -0300
  18 +
1 noosfero (0.43.0~rc20130529152434) squeeze-test; urgency=low 19 noosfero (0.43.0~rc20130529152434) squeeze-test; urgency=low
2 20
3 * RC of 0.43.0 with solr as a plugin 21 * RC of 0.43.0 with solr as a plugin
debian/control
@@ -10,7 +10,6 @@ Build-Depends: @@ -10,7 +10,6 @@ Build-Depends:
10 ruby-sqlite3, 10 ruby-sqlite3,
11 rake, 11 rake,
12 rails3 (>= 3.2.6-1~), 12 rails3 (>= 3.2.6-1~),
13 - openjdk-6-jre,  
14 ruby-will-paginate, 13 ruby-will-paginate,
15 tango-icon-theme, 14 tango-icon-theme,
16 rcov 15 rcov
debian/noosfero-check-dbconfig 0 → 100755
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +#!/usr/bin/ruby
  2 +
  3 +require 'yaml'
  4 +
  5 +DBCONFIG = ARGV.first || '/etc/database/database.yml'
  6 +
  7 +$dbconfig = {}
  8 +
  9 +checks = [
  10 + lambda { File.exists?(DBCONFIG) },
  11 + lambda { $dbconfig = YAML.load_file(DBCONFIG) },
  12 + lambda { $dbconfig['production'] },
  13 + lambda { $dbconfig['production']['adapter'] },
  14 + lambda { $dbconfig['production']['database'] },
  15 +]
  16 +
  17 +if checks.all?(&:call)
  18 + exit 0
  19 +else
  20 + exit 1
  21 +end
debian/noosfero.install
@@ -38,3 +38,4 @@ locale usr/share/noosfero @@ -38,3 +38,4 @@ locale usr/share/noosfero
38 doc/noosfero usr/share/noosfero/doc 38 doc/noosfero usr/share/noosfero/doc
39 39
40 debian/noosfero-console usr/sbin 40 debian/noosfero-console usr/sbin
  41 +debian/noosfero-check-dbconfig usr/sbin
etc/init.d/noosfero
@@ -44,6 +44,13 @@ if [ -z &quot;$NOOSFERO_DIR&quot; ] || [ -z &quot;$NOOSFERO_USER&quot; ]; then @@ -44,6 +44,13 @@ if [ -z &quot;$NOOSFERO_DIR&quot; ] || [ -z &quot;$NOOSFERO_USER&quot; ]; then
44 exit 0 44 exit 0
45 fi 45 fi
46 46
  47 +if test -x /usr/sbin/noosfero-check-dbconfig ; then
  48 + if ! noosfero-check-dbconfig; then
  49 + echo "Noosfero database access not configured, service disabled."
  50 + exit 0
  51 + fi
  52 +fi
  53 +
47 ###################### 54 ######################
48 55
49 main_script() { 56 main_script() {
features/comment.feature
@@ -17,14 +17,6 @@ Feature: comment @@ -17,14 +17,6 @@ Feature: comment
17 And feature "captcha_for_logged_users" is disabled on environment 17 And feature "captcha_for_logged_users" is disabled on environment
18 And I am logged in as "booking" 18 And I am logged in as "booking"
19 19
20 - Scenario: not post a comment without javascript  
21 - Given I am on /booking/article-to-comment  
22 - And I follow "Post a comment"  
23 - And I fill in "Title" with "Hey ho, let's go!"  
24 - And I fill in "Enter your comment" with "Hey ho, let's go!"  
25 - When I press "Post comment"  
26 - Then I should not see "Hey ho, let's go"  
27 -  
28 # This test requires some way to overcome the captcha with unauthenticated 20 # This test requires some way to overcome the captcha with unauthenticated
29 # user. 21 # user.
30 @selenium-fixme 22 @selenium-fixme
@@ -79,14 +71,14 @@ Feature: comment @@ -79,14 +71,14 @@ Feature: comment
79 71
80 @selenium 72 @selenium
81 Scenario: render comment form and go to bottom 73 Scenario: render comment form and go to bottom
82 - Given I am on /booking/article-with-comment 74 + Given I am on /booking/article-to-comment
83 When I follow "Post a comment" 75 When I follow "Post a comment"
84 Then I should see "Enter your comment" 76 Then I should see "Enter your comment"
85 - And I should be on /booking/article-with-comment 77 + And I should be on /booking/article-to-comment
86 78
87 @selenium 79 @selenium
88 Scenario: keep comments field filled while trying to do a comment 80 Scenario: keep comments field filled while trying to do a comment
89 - Given I am on /booking/article-with-comment 81 + Given I am on /booking/article-to-comment
90 And I follow "Post a comment" 82 And I follow "Post a comment"
91 And I fill in "Title" with "Joey Ramone" 83 And I fill in "Title" with "Joey Ramone"
92 When I press "Post comment" 84 When I press "Post comment"
features/comment_reply.feature
@@ -15,15 +15,9 @@ Feature: comment @@ -15,15 +15,9 @@ Feature: comment
15 | article to comment | booking | root comment | this comment is not a reply | 15 | article to comment | booking | root comment | this comment is not a reply |
16 | another article | booking | some comment | this is my very own comment | 16 | another article | booking | some comment | this is my very own comment |
17 17
18 - Scenario: not post a comment without javascript  
19 - Given I am on /booking/article-to-comment  
20 - When I follow "Reply" within ".comment-balloon"  
21 - Then I should not see "Enter your comment" within "div.comment-balloon"  
22 -  
23 Scenario: not show any reply form by default 18 Scenario: not show any reply form by default
24 When I go to /booking/article-to-comment 19 When I go to /booking/article-to-comment
25 Then I should not see "Enter your comment" within "div.comment-balloon" 20 Then I should not see "Enter your comment" within "div.comment-balloon"
26 - And I should see "Reply" within "div.comment-balloon"  
27 21
28 @selenium-fixme 22 @selenium-fixme
29 Scenario: show error messages when make a blank comment reply 23 Scenario: show error messages when make a blank comment reply
@@ -35,18 +29,13 @@ Feature: comment @@ -35,18 +29,13 @@ Feature: comment
35 And I should see "Body can't be blank" within "div.comment_reply" 29 And I should see "Body can't be blank" within "div.comment_reply"
36 30
37 @selenium 31 @selenium
38 - Scenario: not show any reply form by default  
39 - When I go to /booking/article-to-comment  
40 - Then I should not see "Enter your comment" within "div.comment-balloon"  
41 - And I should see "Reply" within "div.comment-balloon"  
42 -  
43 - @selenium  
44 Scenario: render reply form 32 Scenario: render reply form
45 Given I am on /booking/article-to-comment 33 Given I am on /booking/article-to-comment
46 When I follow "Reply" within ".comment-balloon" 34 When I follow "Reply" within ".comment-balloon"
47 Then I should see "Enter your comment" within "div.comment_reply.opened" 35 Then I should see "Enter your comment" within "div.comment_reply.opened"
48 36
49 - @selenium 37 + # The text is hidden but the detector gets it anyway
  38 + @selenium-fixme
50 Scenario: cancel comment reply 39 Scenario: cancel comment reply
51 Given I am on /booking/article-to-comment 40 Given I am on /booking/article-to-comment
52 When I follow "Reply" within ".comment-balloon" 41 When I follow "Reply" within ".comment-balloon"
features/search.feature
@@ -39,7 +39,7 @@ Feature: search @@ -39,7 +39,7 @@ Feature: search
39 | login | name | 39 | login | name |
40 | joaosilva | Joao Silva | 40 | joaosilva | Joao Silva |
41 And the following articles 41 And the following articles
42 - | owner | name | 42 + | owner | name |
43 | joaosilva | article #1 | 43 | joaosilva | article #1 |
44 | joaosilva | article #2 | 44 | joaosilva | article #2 |
45 | joaosilva | article #3 | 45 | joaosilva | article #3 |
@@ -52,8 +52,6 @@ Feature: search @@ -52,8 +52,6 @@ Feature: search
52 When I go to the search page 52 When I go to the search page
53 And I fill in "search-input" with "article" 53 And I fill in "search-input" with "article"
54 And I press "Search" 54 And I press "Search"
55 - Then I should see "article #8" within ".search-results-articles"  
56 - And I should not see "article #9" within ".search-results-articles"  
57 And I should see "see all (9)" 55 And I should see "see all (9)"
58 When I follow "see all (9)" 56 When I follow "see all (9)"
59 Then I should be on the search articles page 57 Then I should be on the search articles page
lib/acts_as_having_boxes.rb
@@ -29,7 +29,7 @@ module ActsAsHavingBoxes @@ -29,7 +29,7 @@ module ActsAsHavingBoxes
29 # returns 3 unless the class table has a boxes_limit column. In that case 29 # returns 3 unless the class table has a boxes_limit column. In that case
30 # return the value of the column. 30 # return the value of the column.
31 def boxes_limit 31 def boxes_limit
32 - LayoutTemplate.find(layout_template).number_of_boxes || 3 32 + @boxes_limit ||= LayoutTemplate.find(layout_template).number_of_boxes || 3
33 end 33 end
34 34
35 end 35 end
lib/noosfero.rb
@@ -4,7 +4,7 @@ require &#39;fast_gettext&#39; @@ -4,7 +4,7 @@ require &#39;fast_gettext&#39;
4 4
5 module Noosfero 5 module Noosfero
6 PROJECT = 'noosfero' 6 PROJECT = 'noosfero'
7 - VERSION = '0.43.0~rc20130529152434' 7 + VERSION = '0.44.0'
8 8
9 def self.pattern_for_controllers_in_directory(dir) 9 def self.pattern_for_controllers_in_directory(dir)
10 disjunction = controllers_in_directory(dir).join('|') 10 disjunction = controllers_in_directory(dir).join('|')
lib/noosfero/plugin.rb
@@ -4,6 +4,10 @@ class Noosfero::Plugin @@ -4,6 +4,10 @@ class Noosfero::Plugin
4 4
5 attr_accessor :context 5 attr_accessor :context
6 6
  7 + def initialize(context=nil)
  8 + self.context = context
  9 + end
  10 +
7 class << self 11 class << self
8 12
9 attr_writer :should_load 13 attr_writer :should_load
@@ -151,6 +155,12 @@ class Noosfero::Plugin @@ -151,6 +155,12 @@ class Noosfero::Plugin
151 blocks || [] 155 blocks || []
152 end 156 end
153 157
  158 + def macros
  159 + self.class.constants.map do |constant_name|
  160 + self.class.const_get(constant_name)
  161 + end.select {|klass| klass < Noosfero::Plugin::Macro}
  162 + end
  163 +
154 # Here the developer may specify the events to which the plugins can 164 # Here the developer may specify the events to which the plugins can
155 # register and must return true or false. The default value must be false. 165 # register and must return true or false. The default value must be false.
156 166
@@ -253,8 +263,8 @@ class Noosfero::Plugin @@ -253,8 +263,8 @@ class Noosfero::Plugin
253 263
254 # -> Parse and possibly make changes of content (article, block, etc) during HTML rendering 264 # -> Parse and possibly make changes of content (article, block, etc) during HTML rendering
255 # returns = content as string after parser and changes 265 # returns = content as string after parser and changes
256 - def parse_content(raw_content)  
257 - raw_content 266 + def parse_content(html, source)
  267 + [html, source]
258 end 268 end
259 269
260 # -> Adds links to the admin panel 270 # -> Adds links to the admin panel
@@ -290,6 +300,18 @@ class Noosfero::Plugin @@ -290,6 +300,18 @@ class Noosfero::Plugin
290 def filter_comment(comment) 300 def filter_comment(comment)
291 end 301 end
292 302
  303 + # Define custom logic to filter loaded comments.
  304 + #
  305 + # Example:
  306 + #
  307 + # def unavailable_comments(scope)
  308 + # scope.without_spams
  309 + # end
  310 + #
  311 + def unavailable_comments(scope)
  312 + scope
  313 + end
  314 +
293 # This method is called by the CommentHandler background job before sending 315 # This method is called by the CommentHandler background job before sending
294 # the notification email. If the comment is marked as spam (i.e. by calling 316 # the notification email. If the comment is marked as spam (i.e. by calling
295 # <tt>comment.spam!</tt>), then the notification email will *not* be sent. 317 # <tt>comment.spam!</tt>), then the notification email will *not* be sent.
@@ -331,6 +353,30 @@ class Noosfero::Plugin @@ -331,6 +353,30 @@ class Noosfero::Plugin
331 def comment_marked_as_ham(comment) 353 def comment_marked_as_ham(comment)
332 end 354 end
333 355
  356 + # Adds extra actions for comments
  357 + # returns = list of hashes or lambda block that creates a list of hashes
  358 + # example:
  359 + #
  360 + # def comment_actions(comment)
  361 + # [{:link => link_to_function(...)}]
  362 + # end
  363 + #
  364 + def comment_actions(comment)
  365 + nil
  366 + end
  367 +
  368 + # This method is called when the user click on comment actions menu.
  369 + # returns = list or lambda block that return ids of enabled menu items for comments
  370 + # example:
  371 + #
  372 + # def check_comment_actions(comment)
  373 + # ['#action1', '#action2']
  374 + # end
  375 + #
  376 + def check_comment_actions(comment)
  377 + []
  378 + end
  379 +
334 # -> Adds fields to the signup form 380 # -> Adds fields to the signup form
335 # returns = lambda block that creates html code 381 # returns = lambda block that creates html code
336 def signup_extra_contents 382 def signup_extra_contents
@@ -405,6 +451,12 @@ class Noosfero::Plugin @@ -405,6 +451,12 @@ class Noosfero::Plugin
405 nil 451 nil
406 end 452 end
407 453
  454 + # -> Adds adicional content to comment form
  455 + # returns = lambda block that creates html code
  456 + def comment_form_extra_contents(args)
  457 + nil
  458 + end
  459 +
408 # -> Finds objects by their contents 460 # -> Finds objects by their contents
409 # returns = {:results => [a, b, c, ...], ...} 461 # returns = {:results => [a, b, c, ...], ...}
410 # P.S.: The plugin might add other informations on the return hash for its 462 # P.S.: The plugin might add other informations on the return hash for its
lib/noosfero/plugin/macro.rb 0 → 100644
@@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
  1 +class Noosfero::Plugin::Macro
  2 +
  3 + attr_accessor :context
  4 +
  5 + class << self
  6 + # Options
  7 + #
  8 + # [:icon_path] Determines the path to icon to be used in the button on
  9 + # tinymce
  10 + # [:title] Former name of the macro
  11 + # [:skip_dialog] Skip configuration dialog on tinymce
  12 + # [:js_files] Javascripts that should be included on tinymce
  13 + # [:css_files Css files that should be included on tinymce
  14 + # [:generator] Javascript code that will be loaded when the macro button
  15 + # is clicked on tinymce
  16 + # [:params] Hash of macro fields that the user might configure
  17 + #
  18 + def configuration
  19 + {}
  20 + end
  21 +
  22 + def plugin
  23 + name.split('::')[0...-1].join('::').constantize
  24 + end
  25 +
  26 + def identifier
  27 + name.underscore
  28 + end
  29 + end
  30 +
  31 + def initialize(context=nil)
  32 + self.context = context
  33 + end
  34 +
  35 + def attributes(macro)
  36 + macro.attributes.to_hash.
  37 + select {|key, value| key[0..10] == 'data-macro-'}.
  38 + inject({}){|result, a| result.merge({a[0][11..-1] => a[1]})}.
  39 + with_indifferent_access
  40 + end
  41 +
  42 + def convert(macro, source)
  43 + macro_name = macro['data-macro']
  44 + attrs = attributes(macro)
  45 +
  46 + begin
  47 + content = parse(attrs, macro.inner_html, source)
  48 + macro['class'] = "parsed-macro #{macro_name}"
  49 + rescue Exception => exception
  50 + content = _("Unsupported macro %s!") % macro_name
  51 + macro['class'] = "failed-macro #{macro_name}"
  52 + end
  53 +
  54 + attrs.each {|key, value| macro.remove_attribute("data-macro-#{key}")}
  55 + content
  56 + end
  57 +
  58 + # This is the method the macros should override
  59 + def parse(attrs, inner_html, source)
  60 + raise
  61 + end
  62 +
  63 +end
lib/noosfero/plugin/manager.rb
@@ -23,7 +23,7 @@ class Noosfero::Plugin::Manager @@ -23,7 +23,7 @@ class Noosfero::Plugin::Manager
23 dispatch_without_flatten(event, *args).flatten 23 dispatch_without_flatten(event, *args).flatten
24 end 24 end
25 25
26 - def dispatch_plugins(event, *args) 26 + def fetch_plugins(event, *args)
27 map { |plugin| plugin.class if plugin.send(event, *args) }.compact.flatten 27 map { |plugin| plugin.class if plugin.send(event, *args) }.compact.flatten
28 end 28 end
29 29
@@ -33,7 +33,7 @@ class Noosfero::Plugin::Manager @@ -33,7 +33,7 @@ class Noosfero::Plugin::Manager
33 33
34 alias :dispatch_scopes :dispatch_without_flatten 34 alias :dispatch_scopes :dispatch_without_flatten
35 35
36 - def first(event, *args) 36 + def dispatch_first(event, *args)
37 result = nil 37 result = nil
38 each do |plugin| 38 each do |plugin|
39 result = plugin.send(event, *args) 39 result = plugin.send(event, *args)
@@ -42,7 +42,7 @@ class Noosfero::Plugin::Manager @@ -42,7 +42,7 @@ class Noosfero::Plugin::Manager
42 result 42 result
43 end 43 end
44 44
45 - def first_plugin(event, *args) 45 + def fetch_first_plugin(event, *args)
46 result = nil 46 result = nil
47 each do |plugin| 47 each do |plugin|
48 if plugin.send(event, *args) 48 if plugin.send(event, *args)
@@ -53,11 +53,38 @@ class Noosfero::Plugin::Manager @@ -53,11 +53,38 @@ class Noosfero::Plugin::Manager
53 result 53 result
54 end 54 end
55 55
  56 + def pipeline(event, *args)
  57 + each do |plugin|
  58 + result = plugin.send(event, *args)
  59 + result = result.kind_of?(Array) ? result : [result]
  60 + raise ArgumentError, "Pipeline broken by #{plugin.class.name} on #{event} with #{result.length} arguments instead of #{args.length}." if result.length != args.length
  61 + args = result
  62 + end
  63 + args.length < 2 ? args.first : args
  64 + end
  65 +
  66 + def filter(property, data)
  67 + inject(data) {|data, plugin| data = plugin.send(property, data)}
  68 + end
  69 +
  70 + def parse_macro(macro_name, macro, source = nil)
  71 + macro_instance = enabled_macros[macro_name] || default_macro
  72 + macro_instance.convert(macro, source)
  73 + end
  74 +
56 def enabled_plugins 75 def enabled_plugins
57 @enabled_plugins ||= (Noosfero::Plugin.all & environment.enabled_plugins).map do |plugin| 76 @enabled_plugins ||= (Noosfero::Plugin.all & environment.enabled_plugins).map do |plugin|
58 - p = plugin.constantize.new  
59 - p.context = context  
60 - p 77 + plugin.constantize.new(context)
  78 + end
  79 + end
  80 +
  81 + def default_macro
  82 + @default_macro ||= Noosfero::Plugin::Macro.new(context)
  83 + end
  84 +
  85 + def enabled_macros
  86 + @enabled_macros ||= dispatch(:macros).inject({}) do |memo, macro|
  87 + memo.merge!(macro.identifier => macro.new(context))
61 end 88 end
62 end 89 end
63 90
plugins/comment_group/controllers/profile/comment_group_plugin_profile_controller.rb 0 → 100644
@@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
  1 +class CommentGroupPluginProfileController < ProfileController
  2 + append_view_path File.join(File.dirname(__FILE__) + '/../views')
  3 +
  4 + def view_comments
  5 + article_id = params[:article_id]
  6 + group_id = params[:group_id]
  7 +
  8 + article = profile.articles.find(article_id)
  9 + comments = article.group_comments.without_spam.in_group(group_id)
  10 + render :update do |page|
  11 + page.replace_html "comments_list_group_#{group_id}", :partial => 'comment/comment.rhtml', :collection => comments
  12 + page.replace_html "comment-count-#{group_id}", comments.count
  13 + end
  14 + end
  15 +
  16 +end
plugins/comment_group/controllers/public/comment_group_plugin_public_controller.rb 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +class CommentGroupPluginPublicController < PublicController
  2 + append_view_path File.join(File.dirname(__FILE__) + '/../views')
  3 +
  4 + def comment_group
  5 + render :json => { :group_id => Comment.find(params[:id]).group_id }
  6 + end
  7 +
  8 +end
plugins/comment_group/db/migrate/20121220201629_add_group_id_to_comment.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class AddGroupIdToComment < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :comments, :group_id, :integer
  4 + end
  5 +
  6 + def self.down
  7 + remove_column :comments, :group_id
  8 + end
  9 +end
plugins/comment_group/lib/comment_group_plugin.rb 0 → 100644
@@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
  1 +require_dependency 'comment_group_plugin/ext/article'
  2 +require_dependency 'comment_group_plugin/ext/comment'
  3 +
  4 +class CommentGroupPlugin < Noosfero::Plugin
  5 +
  6 + def self.plugin_name
  7 + "Comment Group"
  8 + end
  9 +
  10 + def self.plugin_description
  11 + _("A plugin that display comment groups.")
  12 + end
  13 +
  14 + def unavailable_comments(scope)
  15 + scope.without_group
  16 + end
  17 +
  18 + def comment_form_extra_contents(args)
  19 + comment = args[:comment]
  20 + group_id = comment.group_id || args[:group_id]
  21 + lambda {
  22 + hidden_field_tag('comment[group_id]', group_id) if group_id
  23 + }
  24 + end
  25 +
  26 + def js_files
  27 + 'comment_group_macro.js'
  28 + end
  29 +
  30 +end
  31 +
  32 +require_dependency 'comment_group_plugin/macros/allow_comment'
plugins/comment_group/lib/comment_group_plugin/ext/article.rb 0 → 100644
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +require_dependency 'article'
  2 +
  3 +class Article
  4 +
  5 + #FIXME make this test
  6 + has_many :group_comments, :class_name => 'Comment', :foreign_key => 'source_id', :dependent => :destroy, :order => 'created_at asc', :conditions => [ 'group_id IS NOT NULL']
  7 +
  8 + #FIXME make this test
  9 + validate :not_empty_group_comments_removed
  10 +
  11 + #FIXME make this test
  12 + def not_empty_group_comments_removed
  13 + if body
  14 + groups_with_comments = group_comments.collect {|comment| comment.group_id}.uniq
  15 + groups = Hpricot(body.to_s).search('.macro').collect{|element| element['data-macro-group_id'].to_i}
  16 + errors.add_to_base(N_('Not empty group comment cannot be removed')) unless (groups_with_comments-groups).empty?
  17 + end
  18 + end
  19 +
  20 +end
  21 +
plugins/comment_group/lib/comment_group_plugin/ext/comment.rb 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +require_dependency 'comment'
  2 +
  3 +class Comment
  4 +
  5 + named_scope :without_group, :conditions => {:group_id => nil }
  6 +
  7 + named_scope :in_group, lambda { |group_id| {
  8 + :conditions => ['group_id = ?', group_id]
  9 + }
  10 + }
  11 +
  12 +end
plugins/comment_group/lib/comment_group_plugin/macros/allow_comment.rb 0 → 100644
@@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
  1 +#FIXME See a better way to generalize this parameter.
  2 +ActionView::Base.sanitized_allowed_attributes += ['data-macro', 'data-macro-group_id']
  3 +
  4 +class CommentGroupPlugin::AllowComment < Noosfero::Plugin::Macro
  5 + def self.configuration
  6 + { :params => [],
  7 + :skip_dialog => true,
  8 + :generator => 'makeCommentable();',
  9 + :js_files => 'comment_group.js',
  10 + :icon_path => '/designs/icons/tango/Tango/16x16/emblems/emblem-system.png',
  11 + :css_files => 'comment_group.css' }
  12 + end
  13 +
  14 + #FIXME Make this test
  15 + def parse(params, inner_html, source)
  16 + group_id = params[:group_id].to_i
  17 + article = source
  18 + count = article.group_comments.without_spam.in_group(group_id).count
  19 +
  20 + lambda {render :partial => 'plugins/comment_group/views/comment_group.rhtml', :locals => {:group_id => group_id, :article_id => article.id, :inner_html => inner_html, :count => count, :profile_identifier => article.profile.identifier }}
  21 + end
  22 +end
plugins/comment_group/public/comment_group.css 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +div.article_comments {
  2 + background-color:#dddddd;
  3 + border-style: solid;
  4 + border-color:#bbbbbb;
  5 + border-width:2px;
  6 +}
plugins/comment_group/public/comment_group.js 0 → 100644
@@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
  1 +function getNextGroupId() {
  2 + max = -1;
  3 + groups = jQuery('#article_body_ifr').contents().find('.article_comments');
  4 + groups.each(function(key, value) {
  5 + value = jQuery(value).attr('data-macro-group_id');
  6 + if(value>max) max = parseInt(value);
  7 + });
  8 + return max+1;
  9 +}
  10 +
  11 +function makeCommentable() {
  12 + tinyMCE.activeEditor.focus();
  13 + start = jQuery(tinyMCE.activeEditor.selection.getStart()).closest('p');
  14 + end = jQuery(tinyMCE.activeEditor.selection.getEnd()).closest('p');
  15 +
  16 + //text = start.parent().children();
  17 + text = jQuery('#article_body_ifr').contents().find('*');
  18 + selection = text.slice(text.index(start), text.index(end)+1);
  19 +
  20 + hasTag = false;
  21 + selection.each(function(key, value) {
  22 + commentTag = jQuery(value).closest('.article_comments');
  23 + if(commentTag.length) {
  24 + commentTag.children().unwrap('<div class=\"article_comments\"/>');
  25 + hasTag = true;
  26 + }
  27 + });
  28 +
  29 + if(!hasTag) {
  30 + tags = start.siblings().add(start);
  31 + tags = tags.slice(tags.index(start), tags.index(end)>=0?tags.index(end)+1:tags.index(start)+1);
  32 + tags.wrapAll('<div class=\"macro article_comments\" data-macro=\"comment_group_plugin/allow_comment\" data-macro-group_id=\"'+getNextGroupId()+'\"/>');
  33 +
  34 + contents = jQuery('#article_body_ifr').contents();
  35 + lastP = contents.find('p.article_comments_last_paragraph');
  36 + if(lastP.text().trim().length > 0) {
  37 + lastP.removeClass('article_comments_last_paragraph');
  38 + } else {
  39 + lastP.remove();
  40 + }
  41 + lastDiv = contents.find('div.article_comments').last();
  42 + if(lastDiv.next().length==0) {
  43 + lastDiv.after("<p class='article_comments_last_paragraph'>&nbsp;</p>");
  44 + }
  45 + }
  46 +}
  47 +
plugins/comment_group/public/comment_group_macro.js 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +var comment_group_anchor;
  2 +jQuery(document).ready(function($) {
  3 + var anchor = window.location.hash;
  4 + if(anchor.length==0) return;
  5 +
  6 + var val = anchor.split('-'); //anchor format = #comment-\d+
  7 + if(val.length!=2 || val[0]!='#comment') return;
  8 + if($('div[data-macro=comment_group_plugin/allow_comment]').length==0) return; //comment_group_plugin/allow_comment div must exists
  9 + var comment_id = val[1];
  10 + if(!/^\d+$/.test(comment_id)) return; //test for integer
  11 +
  12 + comment_group_anchor = anchor;
  13 + var url = '/plugin/comment_group/public/comment_group/'+comment_id;
  14 + $.getJSON(url, function(data) {
  15 + if(data.group_id!=null) {
  16 + var button = $('div.comment_group_'+ data.group_id + ' a');
  17 + button.click();
  18 + $.scrollTo(button);
  19 + }
  20 + });
  21 +});
  22 +
  23 +function toggleGroup(group) {
  24 + var div = jQuery('div.comments_list_toggle_group_'+group);
  25 + var visible = div.is(':visible');
  26 + if(!visible)
  27 + jQuery('div.comment-group-loading-'+group).addClass('comment-button-loading');
  28 +
  29 + div.toggle('fast');
  30 + return visible;
  31 +}
  32 +
  33 +function loadCompleted(group) {
  34 + jQuery('div.comment-group-loading-'+group).removeClass('comment-button-loading')
  35 + if(comment_group_anchor) {
  36 + jQuery.scrollTo(jQuery(comment_group_anchor));
  37 + comment_group_anchor = null;
  38 + }
  39 +}
plugins/comment_group/public/images/comments.gif 0 → 100644

1.66 KB

plugins/comment_group/test/functional/comment_group_plugin_profile_controller_test.rb 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../../controllers/profile/comment_group_plugin_profile_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class CommentGroupPluginProfileController; def rescue_action(e) raise e end; end
  6 +
  7 +class CommentGroupPluginProfileControllerTest < ActionController::TestCase
  8 +
  9 + def setup
  10 + @controller = CommentGroupPluginProfileController.new
  11 + @request = ActionController::TestRequest.new
  12 + @response = ActionController::TestResponse.new
  13 +
  14 + @profile = create_user('testuser').person
  15 + @article = profile.articles.build(:name => 'test')
  16 + @article.save!
  17 + end
  18 + attr_reader :article
  19 + attr_reader :profile
  20 +
  21 + should 'be able to show group comments' do
  22 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'a comment', :body => 'lalala', :group_id => 0)
  23 + xhr :get, :view_comments, :profile => @profile.identifier, :article_id => article.id, :group_id => 0
  24 + assert_template 'comment/_comment.rhtml'
  25 + assert_match /comments_list_group_0/, @response.body
  26 + assert_match /\"comment-count-0\", \"1\"/, @response.body
  27 + end
  28 +
  29 + should 'do not show global comments' do
  30 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'global comment', :body => 'global', :group_id => nil)
  31 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'a comment', :body => 'lalala', :group_id => 0)
  32 + xhr :get, :view_comments, :profile => @profile.identifier, :article_id => article.id, :group_id => 0
  33 + assert_template 'comment/_comment.rhtml'
  34 + assert_match /comments_list_group_0/, @response.body
  35 + assert_match /\"comment-count-0\", \"1\"/, @response.body
  36 + end
  37 +
  38 +end
plugins/comment_group/test/functional/comment_group_plugin_public_controller_test.rb 0 → 100644
@@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../../controllers/public/comment_group_plugin_public_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class CommentGroupPluginPublicController; def rescue_action(e) raise e end; end
  6 +
  7 +class CommentGroupPluginPublicControllerTest < ActionController::TestCase
  8 +
  9 + def setup
  10 + @controller = CommentGroupPluginPublicController.new
  11 + @request = ActionController::TestRequest.new
  12 + @response = ActionController::TestResponse.new
  13 +
  14 + @profile = create_user('testuser').person
  15 + @article = profile.articles.build(:name => 'test')
  16 + @article.save!
  17 + end
  18 + attr_reader :article
  19 + attr_reader :profile
  20 +
  21 + should 'be able to return group_id for a comment' do
  22 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'a comment', :body => 'lalala', :group_id => 0)
  23 + xhr :get, :comment_group, :id => comment.id
  24 + assert_match /\{\"group_id\":0\}/, @response.body
  25 + end
  26 +
  27 + should 'return group_id=null for a global comment' do
  28 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'a comment', :body => 'lalala' )
  29 + xhr :get, :comment_group, :id => comment.id
  30 + assert_match /\{\"group_id\":null\}/, @response.body
  31 + end
  32 +
  33 +end
plugins/comment_group/test/unit/comment_group_plugin_test.rb 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +
  3 +class CommentGroupPluginTest < ActiveSupport::TestCase
  4 +
  5 + include Noosfero::Plugin::HotSpot
  6 +
  7 + def setup
  8 + @environment = Environment.default
  9 + end
  10 +
  11 + attr_reader :environment
  12 +
  13 + should 'filter_comments returns all the comments wihout group of an article passed as parameter' do
  14 + article = fast_create(Article)
  15 + c1 = fast_create(Comment, :source_id => article.id, :group_id => 1)
  16 + c2 = fast_create(Comment, :source_id => article.id)
  17 + c3 = fast_create(Comment, :source_id => article.id)
  18 +
  19 + plugin = CommentGroupPlugin.new
  20 + assert_equal [], [c2, c3] - plugin.filter_comments(article.comments)
  21 + assert_equal [], plugin.filter_comments(article.comments) - [c2, c3]
  22 + end
  23 +
  24 +end
plugins/comment_group/views/_comment_group.rhtml 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +<div class="comments">
  2 + <div class="comment_group_<%= group_id %>" style="float: left">
  3 + <div style="float: left">
  4 + <%= link_to_remote(image_tag("/plugins/comment_group/images/comments.gif"),
  5 + :url => { :profile => profile_identifier, :controller => 'comment_group_plugin_profile', :action => 'view_comments', :group_id => group_id, :article_id => article_id},
  6 + :loaded => visual_effect(:highlight, "comments_list_group_#{group_id}"),
  7 + :method => :post,
  8 + :condition => "!toggleGroup(#{group_id})",
  9 + :complete => "loadCompleted(#{group_id})")%>
  10 + </div>
  11 + <!-- FIXME: css file -->
  12 + <div id="comments_group_count_<%= group_id %>" style="float: right; vertical-align: middle; padding-left: 3px; padding-right: 5px; color: #5AC1FC"><span id="comment-count-<%= group_id %>" class='comment-count'><%= count %></span></div>
  13 +
  14 + </div>
  15 +
  16 + <div>
  17 + <%= inner_html %>
  18 + </div>
  19 +
  20 + <div class="comment-group-loading-<%= group_id %>"/>
  21 +
  22 + <div class="comments_list_toggle_group_<%= group_id %>" style="display:none">
  23 + <div class="article-comments-list" id="comments_list_group_<%= group_id %>"></div>
  24 + <div id="page-comment-form-<%= group_id %>" class='post_comment_box closed'><%= render :partial => 'comment/comment_form', :locals => {:comment => Comment.new, :display_link => true, :cancel_triggers_hide => true, :group_id => group_id}%></div>
  25 + </div>
  26 +</div>
plugins/custom_forms/controllers/custom_forms_plugin_myprofile_controller.rb
@@ -70,7 +70,8 @@ class CustomFormsPluginMyprofileController &lt; MyProfileController @@ -70,7 +70,8 @@ class CustomFormsPluginMyprofileController &lt; MyProfileController
70 private 70 private
71 71
72 def new_fields(params) 72 def new_fields(params)
73 - result = params[:fields].map {|id, hash| hash.has_key?(:real_id) ? nil : hash}.compact 73 + keys = params[:fields].keys.sort{|a, b| a.to_i <=> b.to_i}
  74 + result = keys.map { |id| (hash = params[:fields][id]).has_key?(:real_id) ? nil : hash}.compact
74 result.delete_if {|field| field[:name].blank?} 75 result.delete_if {|field| field[:name].blank?}
75 result 76 result
76 end 77 end
plugins/custom_forms/controllers/custom_forms_plugin_profile_controller.rb
@@ -27,6 +27,7 @@ class CustomFormsPluginProfileController &lt; ProfileController @@ -27,6 +27,7 @@ class CustomFormsPluginProfileController &lt; ProfileController
27 failed_answers.each do |answer| 27 failed_answers.each do |answer|
28 @submission.errors.add(answer.field.name.to_sym, answer.errors[answer.field.slug.to_sym]) 28 @submission.errors.add(answer.field.name.to_sym, answer.errors[answer.field.slug.to_sym])
29 end 29 end
  30 + raise 'Submission failed: answers not valid'
30 end 31 end
31 session[:notice] = _('Submission saved') 32 session[:notice] = _('Submission saved')
32 redirect_to :action => 'show' 33 redirect_to :action => 'show'
plugins/custom_forms/db/migrate/20130702120732_add_position_to_custom_forms_plugin_fields.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +class AddPositionToCustomFormsPluginFields < ActiveRecord::Migration
  2 + def self.up
  3 + change_table :custom_forms_plugin_fields do |t|
  4 + t.integer :position, :default => 0
  5 + end
  6 + end
  7 +
  8 + def self.down
  9 + change_table :custom_forms_plugin_fields do |t|
  10 + t.remove :position
  11 + end
  12 + end
  13 +end
plugins/custom_forms/lib/custom_forms_plugin/field.rb
@@ -12,5 +12,11 @@ class CustomFormsPlugin::Field &lt; ActiveRecord::Base @@ -12,5 +12,11 @@ class CustomFormsPlugin::Field &lt; ActiveRecord::Base
12 before_validation do |field| 12 before_validation do |field|
13 field.slug = field.name.to_slug if field.name.present? 13 field.slug = field.name.to_slug if field.name.present?
14 end 14 end
  15 +
  16 + before_create do |field|
  17 + if field.form.fields.exists?
  18 + field.position = field.form.fields.order('position').last.position + 1
  19 + end
  20 + end
15 end 21 end
16 22
plugins/custom_forms/lib/custom_forms_plugin/form.rb
1 class CustomFormsPlugin::Form < Noosfero::Plugin::ActiveRecord 1 class CustomFormsPlugin::Form < Noosfero::Plugin::ActiveRecord
2 belongs_to :profile 2 belongs_to :profile
3 3
4 - has_many :fields, :class_name => 'CustomFormsPlugin::Field', :dependent => :destroy 4 + has_many :fields, :class_name => 'CustomFormsPlugin::Field', :dependent => :destroy, :order => 'position'
5 has_many :submissions, :class_name => 'CustomFormsPlugin::Submission' 5 has_many :submissions, :class_name => 'CustomFormsPlugin::Submission'
6 6
7 serialize :access 7 serialize :access
plugins/custom_forms/lib/custom_forms_plugin/helper.rb
@@ -103,14 +103,17 @@ module CustomFormsPlugin::Helper @@ -103,14 +103,17 @@ module CustomFormsPlugin::Helper
103 103
104 def build_answers(submission, form) 104 def build_answers(submission, form)
105 answers = [] 105 answers = []
106 - submission.each do |slug, value|  
107 - field = form.fields.select {|field| field.slug==slug}.first  
108 - if value.kind_of?(String)  
109 - final_value = value  
110 - elsif value.kind_of?(Array)  
111 - final_value = value.join(',')  
112 - elsif value.kind_of?(Hash)  
113 - final_value = value.map {|option, present| present == '1' ? option : nil}.compact.join(',') 106 + form.fields.each do |field|
  107 + final_value = ''
  108 + if submission.has_key?(field.slug)
  109 + value = submission[field.slug]
  110 + if value.kind_of?(String)
  111 + final_value = value
  112 + elsif value.kind_of?(Array)
  113 + final_value = value.join(',')
  114 + elsif value.kind_of?(Hash)
  115 + final_value = value.map {|option, present| present == '1' ? option : nil}.compact.join(',')
  116 + end
114 end 117 end
115 answers << CustomFormsPlugin::Answer.new(:field => field, :value => final_value) 118 answers << CustomFormsPlugin::Answer.new(:field => field, :value => final_value)
116 end 119 end
plugins/custom_forms/public/field.js
@@ -8,7 +8,6 @@ jQuery(&#39;.icon-edit&#39;).live(&#39;click&#39;, function() { @@ -8,7 +8,6 @@ jQuery(&#39;.icon-edit&#39;).live(&#39;click&#39;, function() {
8 id = jQuery(elem).attr('field_id'); 8 id = jQuery(elem).attr('field_id');
9 type = jQuery('#fields_'+id+'_type').val().split('_')[0]; 9 type = jQuery('#fields_'+id+'_type').val().split('_')[0];
10 selector = '#edit-'+type+'-'+id 10 selector = '#edit-'+type+'-'+id
11 - jQuery(selector).show();  
12 return selector 11 return selector
13 } 12 }
14 }); 13 });
plugins/custom_forms/public/style.css
@@ -28,3 +28,6 @@ @@ -28,3 +28,6 @@
28 .edit-information { 28 .edit-information {
29 display: none; 29 display: none;
30 } 30 }
  31 +#colorbox .edit-information {
  32 + display: block;
  33 +}
plugins/custom_forms/test/functional/custom_forms_plugin_myprofile_controller_test.rb
@@ -40,7 +40,7 @@ class CustomFormsPluginMyprofileControllerTest &lt; ActionController::TestCase @@ -40,7 +40,7 @@ class CustomFormsPluginMyprofileControllerTest &lt; ActionController::TestCase
40 should 'create a form' do 40 should 'create a form' do
41 format = '%Y-%m-%d %H:%M' 41 format = '%Y-%m-%d %H:%M'
42 begining = Time.now.strftime(format) 42 begining = Time.now.strftime(format)
43 - ending = (Time.now + 1.day).strftime('%Y-%m-%d %H:%M') 43 + ending = (Time.now + 1.day).strftime(format)
44 assert_difference CustomFormsPlugin::Form, :count, 1 do 44 assert_difference CustomFormsPlugin::Form, :count, 1 do
45 post :create, :profile => profile.identifier, 45 post :create, :profile => profile.identifier,
46 :form => { 46 :form => {
@@ -90,12 +90,42 @@ class CustomFormsPluginMyprofileControllerTest &lt; ActionController::TestCase @@ -90,12 +90,42 @@ class CustomFormsPluginMyprofileControllerTest &lt; ActionController::TestCase
90 assert f2.kind_of?(CustomFormsPlugin::SelectField) 90 assert f2.kind_of?(CustomFormsPlugin::SelectField)
91 end 91 end
92 92
  93 + should 'create fields in the order they are sent' do
  94 + format = '%Y-%m-%d %H:%M'
  95 + num_fields = 10
  96 + begining = Time.now.strftime(format)
  97 + ending = (Time.now + 1.day).strftime(format)
  98 + fields = {}
  99 + num_fields.times do |i|
  100 + fields[i.to_s] = {
  101 + :name => i.to_s,
  102 + :default_value => '',
  103 + :type => 'text_field'
  104 + }
  105 + end
  106 + assert_difference CustomFormsPlugin::Form, :count, 1 do
  107 + post :create, :profile => profile.identifier,
  108 + :form => {
  109 + :name => 'My Form',
  110 + :access => 'logged',
  111 + :begining => begining,
  112 + :ending => ending,
  113 + :description => 'Cool form'},
  114 + :fields => fields
  115 + end
  116 + form = CustomFormsPlugin::Form.find_by_name('My Form')
  117 + assert_equal num_fields, form.fields.count
  118 + form.fields.find_each do |f|
  119 + assert_equal f.position, f.name.to_i
  120 + end
  121 + end
  122 +
93 should 'edit a form' do 123 should 'edit a form' do
94 form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software') 124 form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software')
95 field = CustomFormsPlugin::TextField.create!(:form => form, :name => 'License') 125 field = CustomFormsPlugin::TextField.create!(:form => form, :name => 'License')
96 format = '%Y-%m-%d %H:%M' 126 format = '%Y-%m-%d %H:%M'
97 begining = Time.now.strftime(format) 127 begining = Time.now.strftime(format)
98 - ending = (Time.now + 1.day).strftime('%Y-%m-%d %H:%M') 128 + ending = (Time.now + 1.day).strftime(format)
99 129
100 post :edit, :profile => profile.identifier, :id => form.id, 130 post :edit, :profile => profile.identifier, :id => form.id,
101 :form => {:name => 'My Form', :access => 'logged', :begining => begining, :ending => ending, :description => 'Cool form'}, 131 :form => {:name => 'My Form', :access => 'logged', :begining => begining, :ending => ending, :description => 'Cool form'},
plugins/custom_forms/test/functional/custom_forms_plugin_profile_controller_test.rb 0 → 100644
@@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../../controllers/custom_forms_plugin_profile_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class CustomFormsPluginProfileController; def rescue_action(e) raise e end; end
  6 +
  7 +class CustomFormsPluginProfileControllerTest < ActionController::TestCase
  8 + def setup
  9 + @controller = CustomFormsPluginProfileController.new
  10 + @request = ActionController::TestRequest.new
  11 + @response = ActionController::TestResponse.new
  12 + @profile = create_user('profile').person
  13 + login_as(@profile.identifier)
  14 + environment = Environment.default
  15 + environment.enable_plugin(CustomFormsPlugin)
  16 + end
  17 +
  18 + attr_reader :profile
  19 +
  20 + should 'save submission if fields are ok' do
  21 + form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software')
  22 + field1 = CustomFormsPlugin::TextField.create(:name => 'Name', :form => form, :mandatory => true)
  23 + field2 = CustomFormsPlugin::TextField.create(:name => 'License', :form => form)
  24 +
  25 + assert_difference CustomFormsPlugin::Submission, :count, 1 do
  26 + post :show, :profile => profile.identifier, :id => form.id, :submission => {field1.name.to_slug => 'Noosfero', field2.name.to_slug => 'GPL'}
  27 + end
  28 + assert !session[:notice].include?('not saved')
  29 + assert_redirected_to :action => 'show'
  30 + end
  31 +end
plugins/custom_forms/test/unit/custom_forms_plugin/field_test.rb
@@ -71,5 +71,15 @@ class CustomFormsPlugin::FieldTest &lt; ActiveSupport::TestCase @@ -71,5 +71,15 @@ class CustomFormsPlugin::FieldTest &lt; ActiveSupport::TestCase
71 end 71 end
72 assert_equal form.fields, [license_field] 72 assert_equal form.fields, [license_field]
73 end 73 end
  74 +
  75 + should 'give positions by creation order' do
  76 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  77 + field_0 = CustomFormsPlugin::Field.create!(:name => 'License', :form => form)
  78 + field_1 = CustomFormsPlugin::Field.create!(:name => 'URL', :form => form)
  79 + field_2 = CustomFormsPlugin::Field.create!(:name => 'Wiki', :form => form)
  80 + assert_equal 0, field_0.position
  81 + assert_equal 1, field_1.position
  82 + assert_equal 2, field_2.position
  83 + end
74 end 84 end
75 85