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.

AUTHORS
... ... @@ -110,6 +110,7 @@ Diego Martinez <diegoamc90@gmail.com>
110 110 Diego Martinez <diego@diego-K55A.(none)>
111 111 Diego + Renan <renanteruoc@gmail.com>
112 112 Fernanda Lopes <nanda.listas+psl@gmail.com>
  113 +Francisco Marcelo de Araujo Lima Junior <79350259591@serpro-1457614.(none)>
113 114 Grazieno Pellegrino <grazieno@gmail.com>
114 115 Isaac Canan <isaac@intelletto.com.br>
115 116 Italo Valcy <italo@dcc.ufba.br>
... ... @@ -153,6 +154,7 @@ Leandro Nunes dos Santos &lt;leandronunes@gmail.com&gt;
153 154 Leandro Nunes dos Santos <leandro.santos@serpro.gov.br>
154 155 LinguÁgil 2010 <linguagil.bahia@gmail.com>
155 156 Lucas Melo <lucas@colivre.coop.br>
  157 +Lucas Melo <lucaspradomelo@gmail.com>
156 158 Luis David Aguilar Carlos <ludwig9003@gmail.com>
157 159 Martín Olivera <molivera@solar.org.ar>
158 160 Moises Machado <moises@colivre.coop.br>
... ...
INSTALL.chat
... ... @@ -6,15 +6,13 @@ To configure XMPP/BOSH in Noosfero you need:
6 6 * SystemTimer - http://ph7spot.com/musings/system-timer
7 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 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 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 20  
23 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 25 2. Ejabberd configuration
29 26  
... ... @@ -108,7 +105,7 @@ Unused modules can be disabled, for example:
108 105 * web_admin
109 106 * mod_pubsub
110 107 * mod_irc
111   - * mod_offine
  108 + * mod_offline
112 109 * mod_admin_extra
113 110 * mod_register
114 111  
... ... @@ -132,7 +129,7 @@ This will create a new schema inside the noosfero database, called &#39;ejabberd&#39;.
132 129  
133 130 Note 'noosfero' user should have permission to create Postgresql schemas. Also,
134 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 135 4. ODBC configuration
... ... @@ -168,9 +165,12 @@ Debug = 0
168 165 CommLog = 1
169 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 176 5. Enabling kernel polling and SMP in /etc/default/ejabberd
... ...
RELEASING
... ... @@ -19,11 +19,12 @@ To prepare a release of noosfero, you must follow the steps below:
19 19  
20 20 * Finish all requirements and bugs assigned to the to-be-released version
21 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 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 28 * Test that the tarball and deb package are ok
28 29 * Go to the version's wiki topic and edit it to reflect the new reality
29 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 32 sha1 of the package (with sha1sum and paste the SHA1 hash as comment in the
32 33 attachment form)
33 34 * Download the attached and verify the MD5 hash
34   -* Push the new version tag (with git push --tags)
35 35 * Update an eventual demonstration version that you run.
36 36 * Write an announcement e-mail to the relevant mailing lists pointing to the
37 37 release notes, and maybe to the demonstration version.
... ...
app/controllers/admin/trusted_sites_controller.rb 0 → 100644
... ... @@ -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 before_filter :setup_multitenancy
7 7 before_filter :detect_stuff_by_domain
8 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 24 include ApplicationHelper
11 25 layout :get_layout
... ... @@ -82,11 +96,10 @@ class ApplicationController &lt; ActionController::Base
82 96 false
83 97 end
84 98  
85   -
86 99 def user
87 100 current_user.person if logged_in?
88 101 end
89   -
  102 +
90 103 alias :current_person :user
91 104  
92 105 # TODO: move this logic somewhere else (Domain class?)
... ... @@ -104,6 +117,12 @@ class ApplicationController &lt; ActionController::Base
104 117 else
105 118 @environment = @domain.environment
106 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 126 end
108 127 end
109 128  
... ... @@ -156,7 +175,7 @@ class ApplicationController &lt; ActionController::Base
156 175 def find_by_contents(asset, scope, query, paginate_options={:page => 1}, options={})
157 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 179 fallback_find_by_contents(asset, scope, query, paginate_options, options)
161 180 end
162 181  
... ...
app/controllers/my_profile/cms_controller.rb
... ... @@ -182,7 +182,14 @@ class CmsController &lt; MyProfileController
182 182 if request.post?
183 183 @article.destroy
184 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 193 end
187 194 end
188 195  
... ...
app/controllers/public/account_controller.rb
... ... @@ -26,7 +26,8 @@ class AccountController &lt; ApplicationController
26 26  
27 27 # action to perform login to the application
28 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 31 return unless request.post?
31 32  
32 33 self.current_user = plugins_alternative_authentication
... ... @@ -125,7 +126,7 @@ class AccountController &lt; ApplicationController
125 126 def change_password
126 127 if request.post?
127 128 @user = current_user
128   - begin
  129 + begin
129 130 @user.change_password!(params[:current_password],
130 131 params[:new_password],
131 132 params[:new_password_confirmation])
... ... @@ -218,7 +219,7 @@ class AccountController &lt; ApplicationController
218 219 @question = @enterprise.question
219 220 return unless check_answer
220 221 return unless check_acceptance_of_terms
221   -
  222 +
222 223 activation = load_enterprise_activation
223 224 if activation && user
224 225 activation.requestor = user
... ... @@ -355,7 +356,9 @@ class AccountController &lt; ApplicationController
355 356 end
356 357  
357 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 362 case user.preferred_login_redirection
360 363 when 'keep_on_same_page'
361 364 redirect_back_or_default(user.admin_url)
... ...
app/controllers/public/catalog_controller.rb
... ... @@ -5,15 +5,14 @@ class CatalogController &lt; PublicController
5 5 before_filter :check_enterprise_and_environment
6 6  
7 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 10 end
12 11  
13 12 protected
14 13  
15 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 16 redirect_to :controller => 'profile', :profile => profile.identifier, :action => 'index'
18 17 end
19 18 end
... ...
app/controllers/public/comment_controller.rb 0 → 100644
... ... @@ -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 2  
3 3 needs_profile
4 4  
5   - before_filter :comment_author, :only => :edit_comment
6   -
7 5 helper ProfileHelper
8 6 helper TagsHelper
9 7  
... ... @@ -70,24 +68,8 @@ class ContentViewerController &lt; ApplicationController
70 68  
71 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 74 if @page.has_posts?
93 75 posts = if params[:year] and params[:month]
... ... @@ -117,89 +99,18 @@ class ContentViewerController &lt; ApplicationController
117 99 end
118 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 107 if params[:slideshow]
124 108 render :action => 'slideshow', :layout => 'slideshow'
125 109 end
126 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 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 114 def per_page
204 115 12
205 116 end
... ... @@ -223,13 +134,9 @@ class ContentViewerController &lt; ApplicationController
223 134 end
224 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 139 end
  140 + helper_method :pass_without_comment_captcha?
234 141  
235 142 end
... ...
app/controllers/public/profile_controller.rb
... ... @@ -278,7 +278,7 @@ class ProfileController &lt; PublicController
278 278 end
279 279  
280 280 def register_report
281   - if !verify_recaptcha
  281 + unless user.is_admin? || verify_recaptcha
282 282 render :text => {
283 283 :ok => false,
284 284 :error => {
... ...
app/helpers/application_helper.rb
... ... @@ -32,6 +32,8 @@ module ApplicationHelper
32 32  
33 33 include AccountHelper
34 34  
  35 + include CommentHelper
  36 +
35 37 include BlogHelper
36 38  
37 39 include ContentViewerHelper
... ... @@ -956,10 +958,7 @@ module ApplicationHelper
956 958 options.merge!(:page => params[:npage])
957 959 content = article.to_html(options)
958 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 962 end
964 963  
965 964 # Please, use link_to by default!
... ... @@ -1357,10 +1356,7 @@ module ApplicationHelper
1357 1356 options[:on_ready] ||= 'null'
1358 1357  
1359 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 1360 .tokenInput('#{url_for(search_action)}', {
1365 1361 minChars: #{options[:min_chars].to_json},
1366 1362 prePopulate: #{options[:pre_populate].to_json},
... ... @@ -1376,16 +1372,15 @@ module ApplicationHelper
1376 1372 onAdd: #{options[:on_add]},
1377 1373 onDelete: #{options[:on_delete]},
1378 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 1378 if options[:avoid_enter]
1383   - result += "jQuery('#token-input-#{element_id}')
  1379 + result += javascript_tag("jQuery('#token-input-#{element_id}')
1384 1380 .live('keydown', function(event){
1385 1381 if(event.keyCode == '13') return false;
1386   - });"
  1382 + });")
1387 1383 end
1388   - result += "</script>"
1389 1384 result
1390 1385 end
1391 1386  
... ... @@ -1396,12 +1391,12 @@ module ApplicationHelper
1396 1391 end
1397 1392  
1398 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 1395 expirable_content_reference content, action, text, url, options
1401 1396 end
1402 1397  
1403 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 1400 expirable_content_reference content, action, text, url, options
1406 1401 end
1407 1402  
... ... @@ -1440,6 +1435,29 @@ module ApplicationHelper
1440 1435 @no_design_blocks = true
1441 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 1461 def default_folder_for_image_upload(profile)
1444 1462 default_folder = profile.folders.find_by_type('Gallery')
1445 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 35 'div',
36 36 check_box(:article, :notify_comments) +
37 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 47 (article.can_display_hits? ?
... ...
app/helpers/boxes_helper.rb
... ... @@ -99,8 +99,8 @@ module BoxesHelper
99 99 unless block.visible?
100 100 options[:title] = _("This block is invisible. Your visitors will not see it.")
101 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 104 end
105 105 box_decorator.block_target(block.box, block) +
106 106 content_tag('div',
... ...
app/helpers/catalog_helper.rb
... ... @@ -3,9 +3,18 @@ module CatalogHelper
3 3 include DisplayHelper
4 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 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 18 current_level = content_tag('strong', category.name)
10 19 all_items = [start] + ancestors + [current_level]
11 20 content_tag('div', all_items.join(' &rarr; '), :id => 'breadcrumb')
... ... @@ -15,7 +24,7 @@ module CatalogHelper
15 24 count = profile.products.from_category(category).count
16 25 name = truncate(category.name, :length => 22 - count.to_s.size)
17 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 28 content_tag('li', "#{link} (#{count})") if count > 0
20 29 end
21 30  
... ...
app/helpers/comment_helper.rb 0 → 100644
... ... @@ -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 3 include BlogHelper
4 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 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 14 end
14 15  
15 16 def article_title(article, args = {})
... ...
app/helpers/forms_helper.rb
... ... @@ -244,7 +244,7 @@ module FormsHelper
244 244 yearSuffix: #{datepicker_options[:year_suffix].to_json}
245 245 })
246 246 </script>
247   - "
  247 + ".html_safe
248 248 result
249 249 end
250 250  
... ...
app/helpers/forum_helper.rb
... ... @@ -29,9 +29,9 @@ module ForumHelper
29 29 css_add << 'not-published' if !art.published?
30 30 css_add << position
31 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 35 :class => 'forum-post ' + css_add.join(' '),
36 36 :id => "post-#{art.id}"
37 37 )
... ...
app/helpers/layout_helper.rb
... ... @@ -84,5 +84,11 @@ module LayoutHelper
84 84 theme_path + '/style.css'
85 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 93 end
88 94  
... ...
app/helpers/macros_helper.rb 0 → 100644
... ... @@ -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 @@
  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 67 settings_items :display_hits, :type => :boolean, :default => true
68 68 settings_items :author_name, :type => :string, :default => ""
69 69 settings_items :allow_members_to_edit, :type => :boolean, :default => false
  70 + settings_items :moderate_comments, :type => :boolean, :default => false
70 71 settings_items :followers, :type => Array, :default => []
71 72  
72 73 belongs_to :reference_article, :class_name => "Article", :foreign_key => 'reference_article_id'
... ... @@ -329,6 +330,14 @@ class Article &lt; ActiveRecord::Base
329 330 @view_url ||= image? ? url.merge(:view => true) : url
330 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 341 def allow_children?
333 342 true
334 343 end
... ... @@ -491,6 +500,14 @@ class Article &lt; ActiveRecord::Base
491 500 allow_post_content?(user) || user && allow_members_to_edit && user.is_member_of?(profile)
492 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 511 def accept_category?(cat)
495 512 !cat.is_a?(ProductCategory)
496 513 end
... ... @@ -597,7 +614,7 @@ class Article &lt; ActiveRecord::Base
597 614 end
598 615  
599 616 def lead
600   - abstract.blank? ? first_paragraph : abstract
  617 + abstract.blank? ? first_paragraph.html_safe : abstract.html_safe
601 618 end
602 619  
603 620 def short_lead
... ...
app/models/blog.rb
1 1 class Blog < Folder
2 2  
3 3 acts_as_having_posts
  4 + include PostsLimit
4 5  
5 6 #FIXME This should be used until there is a migration to fix all blogs that
6 7 # already have folders inside them
... ... @@ -81,4 +82,8 @@ class Blog &lt; Folder
81 82 posts.empty?
82 83 end
83 84  
  85 + def last_posts(limit=3)
  86 + posts.where("type != 'RssFeed'").order(:updated_at).limit(limit)
  87 + end
  88 +
84 89 end
... ...
app/models/blog_archives_block.rb
... ... @@ -21,18 +21,22 @@ class BlogArchivesBlock &lt; Block
21 21 end
22 22  
23 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 27 end
26 28  
27 29 def content(args={})
28 30 owner_blog = self.blog
29 31 return nil unless owner_blog
30 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 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 40 end
37 41 results << "</ul>"
38 42 end
... ...
app/models/comment.rb
... ... @@ -17,6 +17,7 @@ class Comment &lt; ActiveRecord::Base
17 17 belongs_to :reply_of, :class_name => 'Comment', :foreign_key => 'reply_of_id'
18 18  
19 19 scope :without_spam, :conditions => ['spam IS NULL OR spam = ?', false]
  20 + scope :without_reply, :conditions => ['reply_of_id IS NULL']
20 21 scope :spam, :conditions => ['spam = ?', true]
21 22  
22 23 # unauthenticated authors:
... ... @@ -34,7 +35,9 @@ class Comment &lt; ActiveRecord::Base
34 35  
35 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 42 def action_tracker_target
40 43 self.article.profile
... ... @@ -150,21 +153,6 @@ class Comment &lt; ActiveRecord::Base
150 153 @replies = comments_list
151 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 156 include ApplicationHelper
169 157 def reported_version(options = {})
170 158 comment = self
... ... @@ -248,4 +236,22 @@ class Comment &lt; ActiveRecord::Base
248 236 plugins.dispatch(:comment_marked_as_ham, self)
249 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 257 end
... ...
app/models/enterprise_homepage.rb
... ... @@ -20,7 +20,8 @@ class EnterpriseHomepage &lt; Article
20 20 enterprise_homepage = self
21 21 lambda do
22 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 25 render :partial => 'content_viewer/enterprise_homepage', :object => enterprise_homepage
25 26 end
26 27 end
... ...
app/models/environment.rb
... ... @@ -28,6 +28,7 @@ class Environment &lt; ActiveRecord::Base
28 28 'manage_environment_users' => N_('Manage environment users'),
29 29 'manage_environment_templates' => N_('Manage environment templates'),
30 30 'manage_environment_licenses' => N_('Manage environment licenses'),
  31 + 'manage_environment_trusted_sites' => N_('Manage environment trusted sites')
31 32 }
32 33  
33 34 module Roles
... ... @@ -270,6 +271,13 @@ class Environment &lt; ActiveRecord::Base
270 271  
271 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 281 def news_amount_by_folder=(amount)
274 282 settings[:news_amount_by_folder] = amount.to_i
275 283 end
... ...
app/models/event.rb
... ... @@ -16,7 +16,6 @@ class Event &lt; Article
16 16 maybe_add_http(self.setting[:link])
17 17 end
18 18  
19   - xss_terminate :only => [ :link ], :on => 'validation'
20 19 xss_terminate :only => [ :body, :link, :address ], :with => 'white_list', :on => 'validation'
21 20  
22 21 def initialize(*args)
... ...
app/models/forum.rb
1 1 class Forum < Folder
2 2  
3 3 acts_as_having_posts :order => 'updated_at DESC'
  4 + include PostsLimit
4 5  
5 6 def self.type_name
6 7 _('Forum')
... ...
app/models/layout_template.rb
... ... @@ -15,6 +15,10 @@ class LayoutTemplate
15 15 @id = id
16 16 end
17 17  
  18 + def name
  19 + @config['name']
  20 + end
  21 +
18 22 def title
19 23 @config['title']
20 24 end
... ...
app/models/posts_limit.rb 0 → 100644
... ... @@ -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 74 end
75 75  
76 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 78 end
79 79  
80 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 312 'email_domain' => self.enable_email ? self.email_domain : nil,
313 313 'friends_list' => friends_list,
314 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 318 end
318 319  
... ...
app/views/admin_panel/index.html.erb
... ... @@ -9,6 +9,7 @@
9 9 <tr><td><%= link_to _('Sideboxes'), :controller => 'environment_design'%></td></tr>
10 10 <tr><td><%= link_to _('Homepage'), :action => 'set_portal_community' %></td></tr>
11 11 <tr><td><%= link_to _('Licenses'), :controller =>'licenses' %></td></tr>
  12 + <tr><td><%= link_to _('Trusted sites'), :controller =>'trusted_sites' %></td></tr>
12 13 </table>
13 14  
14 15 <h2><%= _('Profiles') %></h2>
... ...
app/views/catalog/index.html.erb
... ... @@ -3,26 +3,28 @@
3 3  
4 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 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 28 <% @products.each do |product| %>
27 29 <% extra_content = @plugins.dispatch(:catalog_item_extras, product).collect { |content| instance_eval(&content) } %>
28 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 56  
57 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 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 10  
11 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 @@
  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 @@
  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 19 <%= labelled_form_field _('City and state'), location_fields %>
20 20 <% end %>
21 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 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 28 <%= submit_button(:send, _('Send'), :onclick => "$('confirm').value = 'true'") %>
... ...
app/views/content_viewer/edit_comment.html.erb
... ... @@ -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 8  
9 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 17 <div id="article-toolbar"></div>
12 18  
13 19 <script type="text/javascript">
... ... @@ -60,7 +66,6 @@
60 66 addthis_options = '<%= escape_javascript( NOOSFERO_CONF['addthis_options'] ) %>';
61 67 </script>
62 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 69 </div>
65 70 <% end %>
66 71  
... ... @@ -85,20 +90,21 @@
85 90  
86 91 <% if @page.accept_comments? || @comments_count > 0 %>
87 92 <h3 <%= 'class="no-comments-yet"' if @comments_count == 0 %>>
88   - <%= number_of_comments(@page) %>
  93 + <%= display_number_of_comments(@comments_count) %>
89 94 </h3>
90 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 99 <% end %>
95 100  
96 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 104 </ul>
99 105  
100 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 108 <% end %>
103 109 </div><!-- end class="comments" -->
104 110  
... ...
app/views/layouts/application-ng.html.erb
... ... @@ -17,6 +17,10 @@
17 17 content.respond_to?(:call) ? content.call : content
18 18 end.join("\n")
19 19 %>
  20 +
  21 + <script type='text/javascript'>
  22 + DEFAULT_LOADING_MESSAGE = <%="'#{ _('loading...') }'" %>;
  23 + </script>
20 24 </head>
21 25 <body class="<%= body_classes %>">
22 26 <a href="#content" id="link-go-content"><span><%= _("Go to the content") %></span></a>
... ... @@ -56,5 +60,6 @@
56 60 </div><!-- end id="theme-footer" -->
57 61 <%= noosfero_layout_features %>
58 62 <%= theme_javascript_ng %>
  63 + <%= addthis_javascript %>
59 64 </body>
60 65 </html>
... ...
app/views/profile/report_abuse.html.erb
... ... @@ -4,7 +4,9 @@
4 4 <%= hidden_field_tag(:content_type, params[:content_type]) %>
5 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 11 <%= submit_button(:send, _('Report profile'), :style => 'float: left; cursor: pointer;', :id => 'report-abuse-submit-button', :onclick => "jQuery('#form-submit-loading').show()") %>
10 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 4  
5 5 <%= error_messages_for :mailing %>
6 6  
7   -<%= render :file => 'shared/tiny_mce' %>
8   -
9 7 <%= form_for :mailing, :url => {:action => 'send_mail'}, :html => {:id => 'mailing-form'} do |f| %>
  8 +
10 9 <%= labelled_form_field(_('Subject:'), f.text_field(:subject)) %>
  10 +
  11 + <%= render :file => 'shared/tiny_mce' %>
11 12 <%= labelled_form_field(_('Body:'), f.text_area(:body, :class => 'mceEditor')) %>
  13 +
12 14 <%= submit_button(:send, _('Send')) %>
13 15 <%= button :cancel, _('Cancel e-mail'), :back %>
14 16 <% end %>
... ...
app/views/search/_full_blog.html.erb
... ... @@ -7,12 +7,12 @@
7 7 <tr class="search-blog-items">
8 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 14 <% end %>
15   - <%= _('None') if r.empty? %>
  15 + <%= _('None') if last_posts.empty? %>
16 16 </td>
17 17 </tr>
18 18  
... ...
app/views/shared/logged_in/xmpp_chat.html.erb
... ... @@ -6,6 +6,6 @@
6 6 </div>
7 7 </div>
8 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 10 </a>
11 11 </div>
... ...
app/views/shared/tiny_mce.html.erb
  1 +<% extend MacrosHelper %>
1 2 <%= javascript_include_tag 'tinymce/jscripts/tiny_mce/tiny_mce.js' %>
  3 +<%= include_macro_js_files %>
2 4 <script type="text/javascript">
3   - var myplugins = "searchreplace,print,table,contextmenu";
  5 + var myplugins = "searchreplace,print,table,contextmenu,-macrosPlugin";
4 6 var first_line, second_line;
5 7 var mode = '<%= mode ||= false %>'
6 8 <% if mode %>
... ... @@ -8,36 +10,80 @@
8 10 second_line = ""
9 11 <% else %>
10 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 17 <% end %>
13 18  
14 19 if (tinymce.isIE) {
15 20 // the paste plugin is only useful in Internet Explorer
16 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 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 88 function convertWord(type, content) {
43 89 switch (type) {
... ...
app/views/spam/index.html.erb
... ... @@ -8,7 +8,7 @@
8 8 <div id='article'>
9 9 <div class="comments" id="comments_list">
10 10 <ul class="article-comments-list">
11   - <%= render :partial => 'content_viewer/comment', :collection => @spam %>
  11 + <%= render :partial => 'comment/comment', :collection => @spam %>
12 12 </ul>
13 13 </div>
14 14 </div>
... ...
app/views/tasks/_approve_comment_accept_details.rhtml 0 → 100644
... ... @@ -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 12 "/designs/templates/#{template.id}/thumbnail.png",
13 13 :alt => _('The "%s" template')) +
14 14 '<div class="opt-info">'.html_safe +
15   - content_tag('strong', template.id, :class => 'name') +
  15 + content_tag('strong', template.name, :class => 'name') +
16 16 ' <br/> '.html_safe
17 17  
18 18 if @current_template == template.id # selected
... ...
app/views/trusted_sites/edit.rhtml 0 → 100644
... ... @@ -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 @@
  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 @@
  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 76 # profile search
77 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 82 # public profile information
80 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 124 # cache stuff - hack
122 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 127 # match requests for profiles that don't have a custom domain
127 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 @@
  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 @@
  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 @@
  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
... ...
db/schema.rb
... ... @@ -9,7 +9,7 @@
9 9 #
10 10 # It's strongly recommended to check this file into your version control system.
11 11  
12   -ActiveRecord::Schema.define(:version => 20130429214630) do
  12 +ActiveRecord::Schema.define(:version => 20130606110602) do
13 13  
14 14 create_table "abuse_reports", :force => true do |t|
15 15 t.integer "reporter_id"
... ... @@ -76,7 +76,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do
76 76 t.text "setting"
77 77 t.boolean "notify_comments", :default => false
78 78 t.integer "hits", :default => 0
79   - t.date "published_at"
  79 + t.datetime "published_at"
80 80 t.string "source"
81 81 t.boolean "highlighted", :default => false
82 82 t.string "external_link"
... ... @@ -119,7 +119,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do
119 119 t.text "setting"
120 120 t.boolean "notify_comments", :default => true
121 121 t.integer "hits", :default => 0
122   - t.date "published_at"
  122 + t.datetime "published_at"
123 123 t.string "source"
124 124 t.boolean "highlighted", :default => false
125 125 t.string "external_link"
... ... @@ -221,6 +221,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do
221 221 t.string "source_type"
222 222 t.string "user_agent"
223 223 t.string "referrer"
  224 + t.integer "group_id"
224 225 end
225 226  
226 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 265 t.text "design_data"
265 266 t.text "custom_header"
266 267 t.text "custom_footer"
267   - t.string "theme", :default => "default", :null => false
  268 + t.string "theme", :default => "default", :null => false
268 269 t.text "terms_of_use_acceptance_text"
269 270 t.datetime "created_at"
270 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 273 t.string "redirection_after_login", :default => "keep_on_same_page"
273 274 t.text "signup_welcome_text"
274 275 t.string "languages"
... ... @@ -428,7 +429,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do
428 429 t.string "type"
429 430 t.string "identifier"
430 431 t.integer "environment_id"
431   - t.boolean "active", :default => true
  432 + t.boolean "active", :default => true
432 433 t.string "address"
433 434 t.string "contact_phone"
434 435 t.integer "home_page_id"
... ... @@ -439,21 +440,21 @@ ActiveRecord::Schema.define(:version =&gt; 20130429214630) do
439 440 t.float "lat"
440 441 t.float "lng"
441 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 445 t.text "custom_header"
445 446 t.text "custom_footer"
446 447 t.string "theme"
447   - t.boolean "public_profile", :default => true
  448 + t.boolean "public_profile", :default => true
448 449 t.date "birth_date"
449 450 t.integer "preferred_domain_id"
450 451 t.datetime "updated_at"
451   - t.boolean "visible", :default => true
  452 + t.boolean "visible", :default => true
452 453 t.integer "image_id"
453   - t.boolean "validated", :default => true
  454 + t.boolean "validated", :default => true
454 455 t.string "cnpj"
455 456 t.string "national_region_code"
456   - t.boolean "is_template", :default => false
  457 + t.boolean "is_template", :default => false
457 458 t.integer "template_id"
458 459 t.string "redirection_after_login"
459 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 19 noosfero (0.43.0~rc20130529152434) squeeze-test; urgency=low
2 20  
3 21 * RC of 0.43.0 with solr as a plugin
... ...
debian/control
... ... @@ -10,7 +10,6 @@ Build-Depends:
10 10 ruby-sqlite3,
11 11 rake,
12 12 rails3 (>= 3.2.6-1~),
13   - openjdk-6-jre,
14 13 ruby-will-paginate,
15 14 tango-icon-theme,
16 15 rcov
... ...
debian/noosfero-check-dbconfig 0 → 100755
... ... @@ -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 38 doc/noosfero usr/share/noosfero/doc
39 39  
40 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 44 exit 0
45 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 56 main_script() {
... ...
features/comment.feature
... ... @@ -17,14 +17,6 @@ Feature: comment
17 17 And feature "captcha_for_logged_users" is disabled on environment
18 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 20 # This test requires some way to overcome the captcha with unauthenticated
29 21 # user.
30 22 @selenium-fixme
... ... @@ -79,14 +71,14 @@ Feature: comment
79 71  
80 72 @selenium
81 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 75 When I follow "Post a comment"
84 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 79 @selenium
88 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 82 And I follow "Post a comment"
91 83 And I fill in "Title" with "Joey Ramone"
92 84 When I press "Post comment"
... ...
features/comment_reply.feature
... ... @@ -15,15 +15,9 @@ Feature: comment
15 15 | article to comment | booking | root comment | this comment is not a reply |
16 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 18 Scenario: not show any reply form by default
24 19 When I go to /booking/article-to-comment
25 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 22 @selenium-fixme
29 23 Scenario: show error messages when make a blank comment reply
... ... @@ -35,18 +29,13 @@ Feature: comment
35 29 And I should see "Body can't be blank" within "div.comment_reply"
36 30  
37 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 32 Scenario: render reply form
45 33 Given I am on /booking/article-to-comment
46 34 When I follow "Reply" within ".comment-balloon"
47 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 39 Scenario: cancel comment reply
51 40 Given I am on /booking/article-to-comment
52 41 When I follow "Reply" within ".comment-balloon"
... ...
features/search.feature
... ... @@ -39,7 +39,7 @@ Feature: search
39 39 | login | name |
40 40 | joaosilva | Joao Silva |
41 41 And the following articles
42   - | owner | name |
  42 + | owner | name |
43 43 | joaosilva | article #1 |
44 44 | joaosilva | article #2 |
45 45 | joaosilva | article #3 |
... ... @@ -52,8 +52,6 @@ Feature: search
52 52 When I go to the search page
53 53 And I fill in "search-input" with "article"
54 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 55 And I should see "see all (9)"
58 56 When I follow "see all (9)"
59 57 Then I should be on the search articles page
... ...
lib/acts_as_having_boxes.rb
... ... @@ -29,7 +29,7 @@ module ActsAsHavingBoxes
29 29 # returns 3 unless the class table has a boxes_limit column. In that case
30 30 # return the value of the column.
31 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 33 end
34 34  
35 35 end
... ...
lib/noosfero.rb
... ... @@ -4,7 +4,7 @@ require &#39;fast_gettext&#39;
4 4  
5 5 module Noosfero
6 6 PROJECT = 'noosfero'
7   - VERSION = '0.43.0~rc20130529152434'
  7 + VERSION = '0.44.0'
8 8  
9 9 def self.pattern_for_controllers_in_directory(dir)
10 10 disjunction = controllers_in_directory(dir).join('|')
... ...
lib/noosfero/plugin.rb
... ... @@ -4,6 +4,10 @@ class Noosfero::Plugin
4 4  
5 5 attr_accessor :context
6 6  
  7 + def initialize(context=nil)
  8 + self.context = context
  9 + end
  10 +
7 11 class << self
8 12  
9 13 attr_writer :should_load
... ... @@ -151,6 +155,12 @@ class Noosfero::Plugin
151 155 blocks || []
152 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 164 # Here the developer may specify the events to which the plugins can
155 165 # register and must return true or false. The default value must be false.
156 166  
... ... @@ -253,8 +263,8 @@ class Noosfero::Plugin
253 263  
254 264 # -> Parse and possibly make changes of content (article, block, etc) during HTML rendering
255 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 268 end
259 269  
260 270 # -> Adds links to the admin panel
... ... @@ -290,6 +300,18 @@ class Noosfero::Plugin
290 300 def filter_comment(comment)
291 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 315 # This method is called by the CommentHandler background job before sending
294 316 # the notification email. If the comment is marked as spam (i.e. by calling
295 317 # <tt>comment.spam!</tt>), then the notification email will *not* be sent.
... ... @@ -331,6 +353,30 @@ class Noosfero::Plugin
331 353 def comment_marked_as_ham(comment)
332 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 380 # -> Adds fields to the signup form
335 381 # returns = lambda block that creates html code
336 382 def signup_extra_contents
... ... @@ -405,6 +451,12 @@ class Noosfero::Plugin
405 451 nil
406 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 460 # -> Finds objects by their contents
409 461 # returns = {:results => [a, b, c, ...], ...}
410 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 @@
  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 23 dispatch_without_flatten(event, *args).flatten
24 24 end
25 25  
26   - def dispatch_plugins(event, *args)
  26 + def fetch_plugins(event, *args)
27 27 map { |plugin| plugin.class if plugin.send(event, *args) }.compact.flatten
28 28 end
29 29  
... ... @@ -33,7 +33,7 @@ class Noosfero::Plugin::Manager
33 33  
34 34 alias :dispatch_scopes :dispatch_without_flatten
35 35  
36   - def first(event, *args)
  36 + def dispatch_first(event, *args)
37 37 result = nil
38 38 each do |plugin|
39 39 result = plugin.send(event, *args)
... ... @@ -42,7 +42,7 @@ class Noosfero::Plugin::Manager
42 42 result
43 43 end
44 44  
45   - def first_plugin(event, *args)
  45 + def fetch_first_plugin(event, *args)
46 46 result = nil
47 47 each do |plugin|
48 48 if plugin.send(event, *args)
... ... @@ -53,11 +53,38 @@ class Noosfero::Plugin::Manager
53 53 result
54 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 75 def enabled_plugins
57 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 88 end
62 89 end
63 90  
... ...
plugins/comment_group/controllers/profile/comment_group_plugin_profile_controller.rb 0 → 100644
... ... @@ -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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 70 private
71 71  
72 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 75 result.delete_if {|field| field[:name].blank?}
75 76 result
76 77 end
... ...
plugins/custom_forms/controllers/custom_forms_plugin_profile_controller.rb
... ... @@ -27,6 +27,7 @@ class CustomFormsPluginProfileController &lt; ProfileController
27 27 failed_answers.each do |answer|
28 28 @submission.errors.add(answer.field.name.to_sym, answer.errors[answer.field.slug.to_sym])
29 29 end
  30 + raise 'Submission failed: answers not valid'
30 31 end
31 32 session[:notice] = _('Submission saved')
32 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 @@
  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 12 before_validation do |field|
13 13 field.slug = field.name.to_slug if field.name.present?
14 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 21 end
16 22  
... ...
plugins/custom_forms/lib/custom_forms_plugin/form.rb
1 1 class CustomFormsPlugin::Form < Noosfero::Plugin::ActiveRecord
2 2 belongs_to :profile
3 3  
4   - has_many :fields, :class_name => 'CustomFormsPlugin::Field', :dependent => :destroy
  4 + has_many :fields, :class_name => 'CustomFormsPlugin::Field', :dependent => :destroy, :order => 'position'
5 5 has_many :submissions, :class_name => 'CustomFormsPlugin::Submission'
6 6  
7 7 serialize :access
... ...
plugins/custom_forms/lib/custom_forms_plugin/helper.rb
... ... @@ -103,14 +103,17 @@ module CustomFormsPlugin::Helper
103 103  
104 104 def build_answers(submission, form)
105 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 117 end
115 118 answers << CustomFormsPlugin::Answer.new(:field => field, :value => final_value)
116 119 end
... ...
plugins/custom_forms/public/field.js
... ... @@ -8,7 +8,6 @@ jQuery(&#39;.icon-edit&#39;).live(&#39;click&#39;, function() {
8 8 id = jQuery(elem).attr('field_id');
9 9 type = jQuery('#fields_'+id+'_type').val().split('_')[0];
10 10 selector = '#edit-'+type+'-'+id
11   - jQuery(selector).show();
12 11 return selector
13 12 }
14 13 });
... ...
plugins/custom_forms/public/style.css
... ... @@ -28,3 +28,6 @@
28 28 .edit-information {
29 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 40 should 'create a form' do
41 41 format = '%Y-%m-%d %H:%M'
42 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 44 assert_difference CustomFormsPlugin::Form, :count, 1 do
45 45 post :create, :profile => profile.identifier,
46 46 :form => {
... ... @@ -90,12 +90,42 @@ class CustomFormsPluginMyprofileControllerTest &lt; ActionController::TestCase
90 90 assert f2.kind_of?(CustomFormsPlugin::SelectField)
91 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 123 should 'edit a form' do
94 124 form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software')
95 125 field = CustomFormsPlugin::TextField.create!(:form => form, :name => 'License')
96 126 format = '%Y-%m-%d %H:%M'
97 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 130 post :edit, :profile => profile.identifier, :id => form.id,
101 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 @@
  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 71 end
72 72 assert_equal form.fields, [license_field]
73 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 84 end
75 85  
... ...