Commit 5cb9df6dfe3276be2d7b63585e7050bd2025283c

Authored by Victor Costa
2 parents d2f53367 538bb500

Merge branch 'master' into rails3_chat

Conflicts:
	public/javascripts/application.js
Showing 180 changed files with 4971 additions and 4208 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 180 files displayed.

@@ -40,6 +40,7 @@ Alessandro Palmeira + João M. M. Silva <alessandro.palmeira@gmail.com> @@ -40,6 +40,7 @@ Alessandro Palmeira + João M. M. Silva <alessandro.palmeira@gmail.com>
40 Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com> 40 Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com>
41 Alessandro Palmeira + Paulo Meirelles + João M. M. da Silva <alessandro.palmeira@gmail.com> 41 Alessandro Palmeira + Paulo Meirelles + João M. M. da Silva <alessandro.palmeira@gmail.com>
42 Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com> 42 Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com>
  43 +analosnak <analosnak@gmail.com>
43 Ana Losnak <analosnak@gmail.com> 44 Ana Losnak <analosnak@gmail.com>
44 Andre Bernardes <andrebsguedes@gmail.com> 45 Andre Bernardes <andrebsguedes@gmail.com>
45 Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br> 46 Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br>
@@ -81,7 +82,6 @@ Carlos Morais + Diego Araújo &lt;diegoamc90@gmail.com&gt; @@ -81,7 +82,6 @@ Carlos Morais + Diego Araújo &lt;diegoamc90@gmail.com&gt;
81 Carlos Morais + Eduardo Morais <carlos88morais@gmail.com> 82 Carlos Morais + Eduardo Morais <carlos88morais@gmail.com>
82 Carlos Morais + Paulo Meirelles <carlos88morais@gmail.com> 83 Carlos Morais + Paulo Meirelles <carlos88morais@gmail.com>
83 Carlos Morais + Pedro Leal <carlos88morais@gmail.com> 84 Carlos Morais + Pedro Leal <carlos88morais@gmail.com>
84 -Daniela Feitosa <dani@dohko.(none)>  
85 Daniel Alves + Diego Araújo <danpaulalves@gmail.com> 85 Daniel Alves + Diego Araújo <danpaulalves@gmail.com>
86 Daniel Alves + Diego Araújo <diegoamc90@gmail.com> 86 Daniel Alves + Diego Araújo <diegoamc90@gmail.com>
87 Daniel Alves + Diego Araújo + Guilherme Rojas <danpaulalves@gmail.com> 87 Daniel Alves + Diego Araújo + Guilherme Rojas <danpaulalves@gmail.com>
@@ -119,7 +119,6 @@ Diego Araújo + Renan Teruo &lt;diegoamc90@gmail.com&gt; @@ -119,7 +119,6 @@ Diego Araújo + Renan Teruo &lt;diegoamc90@gmail.com&gt;
119 Diego Araujo + Rodrigo Souto + Rafael Manzo <rr.manzo@gmail.com> 119 Diego Araujo + Rodrigo Souto + Rafael Manzo <rr.manzo@gmail.com>
120 Diego + Jefferson <diegoamc90@gmail.com> 120 Diego + Jefferson <diegoamc90@gmail.com>
121 Diego Martinez <diegoamc90@gmail.com> 121 Diego Martinez <diegoamc90@gmail.com>
122 -Diego Martinez <diego@diego-K55A.(none)>  
123 Diego + Renan <renanteruoc@gmail.com> 122 Diego + Renan <renanteruoc@gmail.com>
124 Eduardo Tourinho Edington <eduardo.edington@serpro.gov.br> 123 Eduardo Tourinho Edington <eduardo.edington@serpro.gov.br>
125 Evandro Jr <evandrojr@gmail.com> 124 Evandro Jr <evandrojr@gmail.com>
@@ -195,9 +194,11 @@ Luis David Aguilar Carlos &lt;ludwig9003@gmail.com&gt; @@ -195,9 +194,11 @@ Luis David Aguilar Carlos &lt;ludwig9003@gmail.com&gt;
195 Luiz Fernando de Freitas Matos <luiz@luizff.matos@gmail.com> 194 Luiz Fernando de Freitas Matos <luiz@luizff.matos@gmail.com>
196 Marcos Ramos <ms.ramos@outlook.com> 195 Marcos Ramos <ms.ramos@outlook.com>
197 Martín Olivera <molivera@solar.org.ar> 196 Martín Olivera <molivera@solar.org.ar>
  197 +Michal Čihař <michal@cihar.com>
198 Moises Machado <moises@colivre.coop.br> 198 Moises Machado <moises@colivre.coop.br>
199 Naíla Alves <naila@colivre.coop.br> 199 Naíla Alves <naila@colivre.coop.br>
200 Nanda Lopes <nanda.listas+psl@gmail.com> 200 Nanda Lopes <nanda.listas+psl@gmail.com>
  201 +Parley Martins <parleypachecomartins@gmail.com>
201 Paulo Meirelles + Alessandro Palmeira + João M. M. da Silva <paulo@softwarelivre.org> 202 Paulo Meirelles + Alessandro Palmeira + João M. M. da Silva <paulo@softwarelivre.org>
202 Paulo Meirelles + Alessandro Palmeira <paulo@softwarelivre.org> 203 Paulo Meirelles + Alessandro Palmeira <paulo@softwarelivre.org>
203 Paulo Meirelles + Carlos Morais <paulo@softwarelivre.org> 204 Paulo Meirelles + Carlos Morais <paulo@softwarelivre.org>
@@ -220,6 +221,7 @@ Rafael Reggiani Manzo + João M. M. da Silva &lt;rr.manzo@gmail.com&gt; @@ -220,6 +221,7 @@ Rafael Reggiani Manzo + João M. M. da Silva &lt;rr.manzo@gmail.com&gt;
220 Rafael Reggiani Manzo <rr.manzo@gmail.com> 221 Rafael Reggiani Manzo <rr.manzo@gmail.com>
221 Raphaël Rousseau <raph@r4f.org> 222 Raphaël Rousseau <raph@r4f.org>
222 Raquel Lira <raquel.lira@gmail.com> 223 Raquel Lira <raquel.lira@gmail.com>
  224 +Raquel <rcordioli@gmail.com>
223 Renan Teruo + Caio Salgado <renanteruoc@gmail.com> 225 Renan Teruo + Caio Salgado <renanteruoc@gmail.com>
224 Renan Teruoc + Diego Araujo <renanteruoc@gmail.com> 226 Renan Teruoc + Diego Araujo <renanteruoc@gmail.com>
225 Renan Teruo + Diego Araujo <renanteruoc@gmail.com> 227 Renan Teruo + Diego Araujo <renanteruoc@gmail.com>
@@ -227,13 +229,13 @@ Renan Teruo + Diego Araújo &lt;renanteruoc@gmail.com&gt; @@ -227,13 +229,13 @@ Renan Teruo + Diego Araújo &lt;renanteruoc@gmail.com&gt;
227 Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com> 229 Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com>
228 Renan Teruo + Rafael Manzo <renanteruoc@gmail.com> 230 Renan Teruo + Rafael Manzo <renanteruoc@gmail.com>
229 Rodrigo Souto + Ana Losnak + Daniel Bucher + Caio Almeida + Leandro Nunes + Daniela Feitosa + Mariel Zasso <noosfero-br@listas.softwarelivre.org> 231 Rodrigo Souto + Ana Losnak + Daniel Bucher + Caio Almeida + Leandro Nunes + Daniela Feitosa + Mariel Zasso <noosfero-br@listas.softwarelivre.org>
230 -Rodrigo Souto <diguliu@gmail.com>  
231 Rodrigo Souto <rodrigo@colivre.coop.br> 232 Rodrigo Souto <rodrigo@colivre.coop.br>
232 Ronny Kursawe <kursawe.ronny@googlemail.com> 233 Ronny Kursawe <kursawe.ronny@googlemail.com>
233 root <root@debian.sdr.serpro> 234 root <root@debian.sdr.serpro>
234 Samuel R. C. Vale <srcvale@holoscopio.com> 235 Samuel R. C. Vale <srcvale@holoscopio.com>
235 Tallys Martins <tallysmartins@gmail.com> 236 Tallys Martins <tallysmartins@gmail.com>
236 tallys <tallys@tallys.(none)> 237 tallys <tallys@tallys.(none)>
  238 +Thiago Zoroastro <thiago.zoroastro@bol.com.br>
237 Valessio Brito <contato@valessiobrito.com.br> 239 Valessio Brito <contato@valessiobrito.com.br>
238 Valessio Brito <contato@valessiobrito.info> 240 Valessio Brito <contato@valessiobrito.info>
239 Valessio Brito <valessio@gmail.com> 241 Valessio Brito <valessio@gmail.com>
DEVELOPMENT.md 0 → 100644
@@ -0,0 +1,124 @@ @@ -0,0 +1,124 @@
  1 +# Noosfero Development Policy
  2 +
  3 +## Developer Roles
  4 +
  5 +* *Developers* are everyone that is contributing code to Noosfero.
  6 +* *Committers* are the people with direct commit access to the Noosfero source
  7 + code. They are responsible for reviewing contributions from other developers
  8 + and integrating them in the Noosfero code base. They are the members of the
  9 + [Noosfero group on Gitlab](https://gitlab.com/groups/noosfero/members).
  10 +* *Release managers* are the people that are managing the release of a new
  11 + Noosfero version and/or the maintainance work of an existing Noosfero stable
  12 + branch. See MAINTAINANCE.md for details on the maintaince policy.
  13 +
  14 +## Development process
  15 +
  16 +* Every new feature or non-trivial bugfix should be reviewed by at least one
  17 + committer. This must be the case even if the original author is a committer.
  18 +
  19 + * In the case the original author is a committer, he/she should feel free to
  20 + commit directly if after 1 week nobody has provided any kind of feedback.
  21 +
  22 + * Developers who are not committers should feel free to ping committers if
  23 + they do not get feedback on their contributions after 1 week.
  24 +
  25 + * On GitLab, one can just add a comment to the merge request; one can also
  26 + @-mention specific committers or other developers who have expertise on
  27 + the area of the contribution.
  28 +
  29 + * Committers should follow the activity of the project, and try to help
  30 + reviewing contributions from others as much as possible.
  31 +
  32 + * On GitLab one can get emails for all activity on a project by setting the
  33 + [notification settings](https://gitlab.com/profile/notifications) to
  34 + "watch".
  35 +
  36 + * Anyone can help by reviewing contributions. Committers are the only ones
  37 + who can give the final approval to a contribution, but everyone is welcome
  38 + to help with code review, testing, etc.
  39 +
  40 + * See note above about setting up notification on GitLab.
  41 +
  42 +* Committers should feel free to push trivial (or urgent) changes directly.
  43 + There are no strict rule on what makes a change trivial or urgent; committers
  44 + are expected to exercise good judgement on a case by case basis.
  45 +
  46 + * Usually changes to the database are not trivial.
  47 +
  48 +* In the case of unsolvable conflict between commiters regarding any change to
  49 + the code, the current release manager(s) will have the final say in the
  50 + matter.
  51 +
  52 +* Release managers are responsible for stablishing a release schedule, and
  53 + about deciding when and what to release.
  54 +
  55 + * Release managers should announce release schedules to the project mailing
  56 + lists in advance.
  57 +
  58 + * The release schedule may include a period of feature freeze, during which
  59 + no new features or any other changes that are not pre-approved by the
  60 + release manager must be committed to the repository.
  61 +
  62 + * Committers must respect the release schedule and feature freezes.
  63 +
  64 +## Maintainance process
  65 +
  66 +### Not all feature releases will be maintained as a stable release
  67 +
  68 +We will be choosing specific release series to be maintained as stable
  69 +releases.
  70 +
  71 +This means that a given release is not guaranteed to be maintained as a stable
  72 +release, but does *not* mean it won't be. Any committer (or anyone, really) can
  73 +decide to maintain a given release as stable and seek help from others to do
  74 +so.
  75 +
  76 +### No merges from stable branches to master
  77 +
  78 +*All* changes must be submitted against the master branch first, and when
  79 +applicable, backported to the desired stable releases. Exceptions to this rules
  80 +are bug fixes that only apply to a given stable branch and not to master.
  81 +
  82 +In the past we had non-trivial changes accepted into stable releases while
  83 +master was way ahead (e.g. during the rails3 migration period), that made the
  84 +merge back into master very painful. By eliminating the need to do these
  85 +merges, we save time for the people responsible for the release, and eliminate
  86 +the possibility of human errors or oversights causing changes to be accepted
  87 +into stable that will be a problem to merge back into master.
  88 +
  89 +By getting all fixes in master first, we improve the chances that a future
  90 +release will not present regressions against bugs that should already be fixed,
  91 +but the fixes got lost in a big, complicated merge (and those won't exist
  92 +anymore, at least not from stable branches to master).
  93 +
  94 +After a fix gets into master, backporting changes into a stable release branch
  95 +is the responsibility of whoever is maintaing that branch, and those interested
  96 +in it. The stable branch release manager(s) are entitled the final say on any
  97 +matters related to that branch.
  98 +
  99 +## Apendix A: how to become a committer
  100 +
  101 +Every developer that wants to be a committer should create [an issue on
  102 +Gitlab](https://gitlab.com/noosfero/noosfero/issues) requesting to be added as
  103 +a committer. This request must include information about the requestor's
  104 +previous contributions to the project.
  105 +
  106 +If 2 or more commiters consider second the request, the requestor is accepted
  107 +as new commiter and added to the Noosfero group.
  108 +
  109 +The existing committers are free to choose whatever criteria they want to
  110 +second the request, but they must be sure that the new committer is a
  111 +responsible developer and knows what she/he is doing. They must be aware that
  112 +seconding these requests means seconding the actions of the new committer: if
  113 +the new committer screw up, her/his seconds screwed up.
  114 +
  115 +## Apendix B: how to become a release manager
  116 +
  117 +A new release manager for the development version of Noosfero (i.e. the one
  118 +that includes new features, a.k.a. the master branch) is apointed by the
  119 +current release manager, and must be a committer first.
  120 +
  121 +Release managers for stable branches are self-appointed, i.e. whoever takes the
  122 +work takes the role. In case of a conflict (e.g. 2+ different people want to do
  123 +the work but can't agree on working together), the development release manager
  124 +decides.
@@ -41,8 +41,9 @@ group :cucumber do @@ -41,8 +41,9 @@ group :cucumber do
41 gem 'selenium-webdriver', '~> 2.39.0' 41 gem 'selenium-webdriver', '~> 2.39.0'
42 end 42 end
43 43
44 -# include plugin gemfiles  
45 -Dir.glob(File.join('config', 'plugins', '*')).each do |plugin|  
46 - plugin_gemfile = File.join(plugin, 'Gemfile')  
47 - eval File.read(plugin_gemfile) if File.exists?(plugin_gemfile) 44 +# include gemfiles from enabled plugins
  45 +# plugins in baseplugins/ are not included on purpose. They should not have any
  46 +# dependencies.
  47 +Dir.glob('config/plugins/*/Gemfile').each do |gemfile|
  48 + eval File.read(gemfile)
48 end 49 end
app/controllers/my_profile/profile_editor_controller.rb
@@ -16,14 +16,16 @@ class ProfileEditorController &lt; MyProfileController @@ -16,14 +16,16 @@ class ProfileEditorController &lt; MyProfileController
16 if request.post? 16 if request.post?
17 params[:profile_data][:fields_privacy] ||= {} if profile.person? && params[:profile_data].is_a?(Hash) 17 params[:profile_data][:fields_privacy] ||= {} if profile.person? && params[:profile_data].is_a?(Hash)
18 Profile.transaction do 18 Profile.transaction do
19 - Image.transaction do  
20 - if @profile_data.update_attributes(params[:profile_data])  
21 - redirect_to :action => 'index', :profile => profile.identifier  
22 - else  
23 - profile.identifier = params[:profile] if profile.identifier.blank? 19 + Image.transaction do
  20 + begin
  21 + @plugins.dispatch(:profile_editor_transaction_extras)
  22 + @profile_data.update_attributes!(params[:profile_data])
  23 + redirect_to :action => 'index', :profile => profile.identifier
  24 + rescue Exception => ex
  25 + profile.identifier = params[:profile] if profile.identifier.blank?
  26 + end
24 end 27 end
25 end 28 end
26 - end  
27 end 29 end
28 end 30 end
29 31
app/controllers/public/account_controller.rb
@@ -193,7 +193,7 @@ class AccountController &lt; ApplicationController @@ -193,7 +193,7 @@ class AccountController &lt; ApplicationController
193 else 193 else
194 @change_password.errors[:base] << _('Could not find any user with %s equal to "%s".') % [fields_label, params[:value]] 194 @change_password.errors[:base] << _('Could not find any user with %s equal to "%s".') % [fields_label, params[:value]]
195 end 195 end
196 - rescue ActiveRecord::RecordInvald 196 + rescue ActiveRecord::RecordInvalid
197 @change_password.errors[:base] << _('Could not perform password recovery for the user.') 197 @change_password.errors[:base] << _('Could not perform password recovery for the user.')
198 end 198 end
199 end 199 end
app/controllers/public/chat_controller.rb
@@ -19,7 +19,7 @@ class ChatController &lt; PublicController @@ -19,7 +19,7 @@ class ChatController &lt; PublicController
19 def avatar 19 def avatar
20 profile = environment.profiles.find_by_identifier(params[:id]) 20 profile = environment.profiles.find_by_identifier(params[:id])
21 filename, mimetype = profile_icon(profile, :minor, true) 21 filename, mimetype = profile_icon(profile, :minor, true)
22 - if filename =~ /^https?:/ 22 + if filename =~ /^(https?:)?\/\//
23 redirect_to filename 23 redirect_to filename
24 else 24 else
25 data = File.read(File.join(Rails.root, 'public', filename)) 25 data = File.read(File.join(Rails.root, 'public', filename))
app/controllers/public/profile_controller.rb
@@ -65,13 +65,13 @@ class ProfileController &lt; PublicController @@ -65,13 +65,13 @@ class ProfileController &lt; PublicController
65 65
66 def friends 66 def friends
67 if is_cache_expired?(profile.friends_cache_key(params)) 67 if is_cache_expired?(profile.friends_cache_key(params))
68 - @friends = profile.friends.includes(relations_to_include).paginate(:per_page => per_page, :page => params[:npage]) 68 + @friends = profile.friends.includes(relations_to_include).paginate(:per_page => per_page, :page => params[:npage], :total_entries => profile.friends.count)
69 end 69 end
70 end 70 end
71 71
72 def members 72 def members
73 if is_cache_expired?(profile.members_cache_key(params)) 73 if is_cache_expired?(profile.members_cache_key(params))
74 - @members = profile.members_by_name.includes(relations_to_include).paginate(:per_page => members_per_page, :page => params[:npage]) 74 + @members = profile.members_by_name.includes(relations_to_include).paginate(:per_page => members_per_page, :page => params[:npage], :total_entries => profile.members.count)
75 end 75 end
76 end 76 end
77 77
app/helpers/application_helper.rb
@@ -433,19 +433,19 @@ module ApplicationHelper @@ -433,19 +433,19 @@ module ApplicationHelper
433 end 433 end
434 434
435 def theme_site_title 435 def theme_site_title
436 - theme_include('site_title') 436 + @theme_site_title ||= theme_include 'site_title'
437 end 437 end
438 438
439 def theme_header 439 def theme_header
440 - theme_include('header') 440 + @theme_header ||= theme_include 'header'
441 end 441 end
442 442
443 def theme_footer 443 def theme_footer
444 - theme_include('footer') 444 + @theme_footer ||= theme_include 'footer'
445 end 445 end
446 446
447 def theme_extra_navigation 447 def theme_extra_navigation
448 - theme_include('navigation') 448 + @theme_extra_navigation ||= theme_include 'navigation'
449 end 449 end
450 450
451 def is_testing_theme 451 def is_testing_theme
@@ -674,13 +674,14 @@ module ApplicationHelper @@ -674,13 +674,14 @@ module ApplicationHelper
674 html.join "\n" 674 html.join "\n"
675 end 675 end
676 676
  677 + def theme_javascript_src
  678 + script = File.join theme_path, 'theme.js'
  679 + script if File.exists? File.join(Rails.root, 'public', script)
  680 + end
  681 +
677 def theme_javascript_ng 682 def theme_javascript_ng
678 - script = File.join(theme_path, 'theme.js')  
679 - if File.exists?(File.join(Rails.root, 'public', script))  
680 - javascript_include_tag script  
681 - else  
682 - nil  
683 - end 683 + script = theme_javascript_src
  684 + javascript_include_tag script if script
684 end 685 end
685 686
686 def file_field_or_thumbnail(label, image, i) 687 def file_field_or_thumbnail(label, image, i)
@@ -907,13 +908,15 @@ module ApplicationHelper @@ -907,13 +908,15 @@ module ApplicationHelper
907 end 908 end
908 909
909 def page_title 910 def page_title
910 - (@page ? @page.title + ' - ' : '') +  
911 - (@topic ? @topic.title + ' - ' : '') +  
912 - (@section ? @section.title + ' - ' : '') +  
913 - (@toc ? _('Online Manual') + ' - ' : '') +  
914 - (controller.controller_name == 'chat' ? _('Chat') + ' - ' : '') +  
915 - (profile ? profile.short_name : environment.name) +  
916 - (@category ? " - #{@category.full_name}" : '') 911 + CGI.escapeHTML(
  912 + (@page ? @page.title + ' - ' : '') +
  913 + (@topic ? @topic.title + ' - ' : '') +
  914 + (@section ? @section.title + ' - ' : '') +
  915 + (@toc ? _('Online Manual') + ' - ' : '') +
  916 + (controller.controller_name == 'chat' ? _('Chat') + ' - ' : '') +
  917 + (profile ? profile.short_name : environment.name) +
  918 + (@category ? " - #{@category.full_name}" : '')
  919 + )
917 end 920 end
918 921
919 # DEPRECATED. Do not use this. 922 # DEPRECATED. Do not use this.
@@ -1285,11 +1288,13 @@ module ApplicationHelper @@ -1285,11 +1288,13 @@ module ApplicationHelper
1285 end 1288 end
1286 1289
1287 def delete_article_message(article) 1290 def delete_article_message(article)
1288 - if article.folder?  
1289 - _("Are you sure that you want to remove the folder \"%s\"? Note that all the items inside it will also be removed!") % article.name  
1290 - else  
1291 - _("Are you sure that you want to remove the item \"%s\"?") % article.name  
1292 - end 1291 + CGI.escapeHTML(
  1292 + if article.folder?
  1293 + _("Are you sure that you want to remove the folder \"%s\"? Note that all the items inside it will also be removed!") % article.name
  1294 + else
  1295 + _("Are you sure that you want to remove the item \"%s\"?") % article.name
  1296 + end
  1297 + )
1293 end 1298 end
1294 1299
1295 def expirable_link_to(expired, content, url, options = {}) 1300 def expirable_link_to(expired, content, url, options = {})
@@ -1377,7 +1382,7 @@ module ApplicationHelper @@ -1377,7 +1382,7 @@ module ApplicationHelper
1377 # are old things that do not support it we are keeping this hot spot. 1382 # are old things that do not support it we are keeping this hot spot.
1378 html = @plugins.pipeline(:parse_content, html, source).first 1383 html = @plugins.pipeline(:parse_content, html, source).first
1379 end 1384 end
1380 - html 1385 + html && html.html_safe
1381 end 1386 end
1382 1387
1383 def convert_macro(html, source) 1388 def convert_macro(html, source)
app/helpers/content_viewer_helper.rb
@@ -10,7 +10,7 @@ module ContentViewerHelper @@ -10,7 +10,7 @@ module ContentViewerHelper
10 end 10 end
11 11
12 def number_of_comments(article) 12 def number_of_comments(article)
13 - display_number_of_comments(article.comments_count - article.spam_comments_count) 13 + display_number_of_comments(article.comments_count - article.spam_comments_count.to_i)
14 end 14 end
15 15
16 def article_title(article, args = {}) 16 def article_title(article, args = {})
app/helpers/layout_helper.rb
@@ -17,6 +17,8 @@ module LayoutHelper @@ -17,6 +17,8 @@ module LayoutHelper
17 unless plugins_javascripts.empty? 17 unless plugins_javascripts.empty?
18 output += javascript_include_tag plugins_javascripts, :cache => "cache/plugins-#{Digest::MD5.hexdigest plugins_javascripts.to_s}" 18 output += javascript_include_tag plugins_javascripts, :cache => "cache/plugins-#{Digest::MD5.hexdigest plugins_javascripts.to_s}"
19 end 19 end
  20 + output += theme_javascript_ng.to_s
  21 +
20 output 22 output
21 end 23 end
22 24
@@ -85,6 +87,10 @@ module LayoutHelper @@ -85,6 +87,10 @@ module LayoutHelper
85 theme_path + '/style.css' 87 theme_path + '/style.css'
86 end 88 end
87 89
  90 + def layout_template
  91 + if profile then profile.layout_template else environment.layout_template end
  92 + end
  93 +
88 def addthis_javascript 94 def addthis_javascript
89 if NOOSFERO_CONF['addthis_enabled'] 95 if NOOSFERO_CONF['addthis_enabled']
90 '<script src="https://s7.addthis.com/js/152/addthis_widget.js"></script>' 96 '<script src="https://s7.addthis.com/js/152/addthis_widget.js"></script>'
@@ -92,7 +98,7 @@ module LayoutHelper @@ -92,7 +98,7 @@ module LayoutHelper
92 end 98 end
93 99
94 def meta_description_tag(article=nil) 100 def meta_description_tag(article=nil)
95 - article ? truncate(strip_tags(article.body.to_s), :length => 200) : environment.name 101 + article ? CGI.escapeHTML(truncate(strip_tags(article.body.to_s), :length => 200)) : environment.name
96 end 102 end
97 end 103 end
98 104
app/helpers/role_helper.rb
1 module RoleHelper 1 module RoleHelper
  2 +
  3 + def role_available_permissions(role)
  4 + role.kind == "Environment" ? ['Environment', 'Profile'] : [role.kind]
  5 + end
  6 +
2 end 7 end
app/helpers/sweeper_helper.rb
@@ -56,12 +56,12 @@ module SweeperHelper @@ -56,12 +56,12 @@ module SweeperHelper
56 if profile 56 if profile
57 profile.blocks.each {|block| 57 profile.blocks.each {|block|
58 conditions = block.class.expire_on 58 conditions = block.class.expire_on
59 - blocks_to_expire << block unless (conditions[:profile] & causes).empty? 59 + blocks_to_expire << block unless (conditions[:profile] & causes).blank?
60 } 60 }
61 end 61 end
62 environment.blocks.each {|block| 62 environment.blocks.each {|block|
63 conditions = block.class.expire_on 63 conditions = block.class.expire_on
64 - blocks_to_expire << block unless (conditions[:environment] & causes).empty? 64 + blocks_to_expire << block unless (conditions[:environment] & causes).blank?
65 } 65 }
66 66
67 blocks_to_expire.uniq! 67 blocks_to_expire.uniq!
app/models/person.rb
@@ -21,6 +21,12 @@ class Person &lt; Profile @@ -21,6 +21,12 @@ class Person &lt; Profile
21 { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] } 21 { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] }
22 } 22 }
23 23
  24 + scope :by_role, lambda { |roles|
  25 + roles = [roles] unless roles.kind_of?(Array)
  26 + { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.role_id IN (?)',
  27 +roles] }
  28 + }
  29 +
24 def has_permission_with_plugins?(permission, profile) 30 def has_permission_with_plugins?(permission, profile)
25 permissions = [has_permission_without_plugins?(permission, profile)] 31 permissions = [has_permission_without_plugins?(permission, profile)]
26 permissions += plugins.map do |plugin| 32 permissions += plugins.map do |plugin|
app/models/profile.rb
@@ -97,7 +97,7 @@ class Profile &lt; ActiveRecord::Base @@ -97,7 +97,7 @@ class Profile &lt; ActiveRecord::Base
97 end 97 end
98 98
99 def members_by_name 99 def members_by_name
100 - members.order(:name) 100 + members.order('profiles.name')
101 end 101 end
102 102
103 class << self 103 class << self
@@ -108,8 +108,8 @@ class Profile &lt; ActiveRecord::Base @@ -108,8 +108,8 @@ class Profile &lt; ActiveRecord::Base
108 alias_method_chain :count, :distinct 108 alias_method_chain :count, :distinct
109 end 109 end
110 110
111 - def members_by_role(role)  
112 - Person.members_of(self).all(:conditions => ['role_assignments.role_id = ?', role.id]) 111 + def members_by_role(roles)
  112 + Person.members_of(self).by_role(roles)
113 end 113 end
114 114
115 acts_as_having_boxes 115 acts_as_having_boxes
app/models/task.rb
@@ -285,8 +285,9 @@ class Task &lt; ActiveRecord::Base @@ -285,8 +285,9 @@ class Task &lt; ActiveRecord::Base
285 # If 285 # If
286 def send_notification(action) 286 def send_notification(action)
287 if sends_email? 287 if sends_email?
288 - if self.requestor  
289 - TaskMailer.generic_message("task_#{action}", self) 288 + if self.requestor && !self.requestor.notification_emails.empty?
  289 + message = TaskMailer.generic_message("task_#{action}", self)
  290 + message.deliver if message
290 end 291 end
291 end 292 end
292 end 293 end
app/models/user.rb
@@ -201,6 +201,10 @@ class User &lt; ActiveRecord::Base @@ -201,6 +201,10 @@ class User &lt; ActiveRecord::Base
201 Digest::MD5.hexdigest(password) 201 Digest::MD5.hexdigest(password)
202 end 202 end
203 203
  204 + add_encryption_method :salted_md5 do |password, salt|
  205 + Digest::MD5.hexdigest(password+salt)
  206 + end
  207 +
204 add_encryption_method :clear do |password, salt| 208 add_encryption_method :clear do |password, salt|
205 password 209 password
206 end 210 end
@@ -350,6 +354,7 @@ class User &lt; ActiveRecord::Base @@ -350,6 +354,7 @@ class User &lt; ActiveRecord::Base
350 end 354 end
351 355
352 def delay_activation_check 356 def delay_activation_check
  357 + return if person.is_template?
353 Delayed::Job.enqueue(UserActivationJob.new(self.id), {:priority => 0, :run_at => 72.hours.from_now}) 358 Delayed::Job.enqueue(UserActivationJob.new(self.id), {:priority => 0, :run_at => 72.hours.from_now})
354 end 359 end
355 end 360 end
app/sweepers/role_assignment_sweeper.rb
@@ -13,7 +13,7 @@ class RoleAssignmentSweeper &lt; ActiveRecord::Observer @@ -13,7 +13,7 @@ class RoleAssignmentSweeper &lt; ActiveRecord::Observer
13 protected 13 protected
14 14
15 def expire_caches(role_assignment) 15 def expire_caches(role_assignment)
16 - expire_cache(role_assignment.accessor) 16 + expire_cache(role_assignment.accessor) if role_assignment.accessor.kind_of?(Profile)
17 expire_cache(role_assignment.resource) if role_assignment.resource.kind_of?(Profile) 17 expire_cache(role_assignment.resource) if role_assignment.resource.kind_of?(Profile)
18 end 18 end
19 19
app/views/file_presenter/_generic.html.erb
1 <span class="download-link"> 1 <span class="download-link">
2 <span>Download</span> 2 <span>Download</span>
3 - <strong><%= link_to generic.filename, generic.public_filename %></strong> 3 + <strong><%= link_to generic.filename, [Noosfero.root, generic.public_filename].join %></strong>
4 </span> 4 </span>
5 5
6 <div class="uploaded-file-description <%= 'empty' if generic.abstract.blank? %>"> 6 <div class="uploaded-file-description <%= 'empty' if generic.abstract.blank? %>">
app/views/file_presenter/_image.html.erb
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 28
29 <%# image_tag(article.public_filename(:display), :class => article.css_class_name, :style => 'max-width: 100%') %> 29 <%# image_tag(article.public_filename(:display), :class => article.css_class_name, :style => 'max-width: 100%') %>
30 30
31 -<img src="<%=image.public_filename(:display)%>" class="<%=image.css_class_name%>"> 31 +<img src="<%= [Noosfero.root, image.public_filename(:display)].join %>" class="<%=image.css_class_name%>">
32 32
33 <div class="uploaded-file-description <%= 'empty' if image.abstract.blank? %>"> 33 <div class="uploaded-file-description <%= 'empty' if image.abstract.blank? %>">
34 <%= image.abstract %> 34 <%= image.abstract %>
app/views/home/index.html.erb
@@ -61,9 +61,6 @@ @@ -61,9 +61,6 @@
61 <%= submit_button(:search, _('Search')) %> 61 <%= submit_button(:search, _('Search')) %>
62 </div> 62 </div>
63 63
64 - <div>  
65 - <%= lightbox_link_to _('More options'), :controller => 'search', :action => 'popup' %>  
66 - </div>  
67 <% end %> 64 <% end %>
68 </div> 65 </div>
69 <% end %> 66 <% end %>
app/views/layouts/_content.html.erb 0 → 100644
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
  1 +<div id="content-inner">
  2 + <%= insert_boxes(yield) %>
  3 + <br style='clear: both'/>
  4 +</div><!-- end id="content-inner" -->
app/views/layouts/application-ng.html.erb
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 <meta property="og:url" content="<%= @page ? url_for(@page.url) : @environment.top_url %>"> 17 <meta property="og:url" content="<%= @page ? url_for(@page.url) : @environment.top_url %>">
18 <meta property="og:title" content="<%= h page_title %>"> 18 <meta property="og:title" content="<%= h page_title %>">
19 <meta property="og:site_name" content="<%= profile ? profile.name : @environment.name %>"> 19 <meta property="og:site_name" content="<%= profile ? profile.name : @environment.name %>">
20 - <meta property="og:description" content="<%= @page ? truncate(strip_tags(@page.body.to_s), :length => 200) : @environment.name %>"> 20 + <meta property="og:description" content="<%= meta_description_tag(@page) %>">
21 21
22 <!-- site root --> 22 <!-- site root -->
23 <meta property="noosfero:root" content="<%= Noosfero.root %>"/> 23 <meta property="noosfero:root" content="<%= Noosfero.root %>"/>
@@ -72,10 +72,7 @@ @@ -72,10 +72,7 @@
72 <div id="navigation-end"></div> 72 <div id="navigation-end"></div>
73 </div><!-- end id="navigation" --> 73 </div><!-- end id="navigation" -->
74 <div id="content"> 74 <div id="content">
75 - <div id="content-inner">  
76 - <%= insert_boxes(yield) %>  
77 - <br style='clear: both'/>  
78 - </div><!-- end id="content-inner" --> 75 + <%= render 'layouts/content' %>
79 </div><!-- end id="content" --> 76 </div><!-- end id="content" -->
80 </div><!-- end id="wrap-2" --> 77 </div><!-- end id="wrap-2" -->
81 </div><!-- end id="wrap-1" --> 78 </div><!-- end id="wrap-1" -->
@@ -84,7 +81,6 @@ @@ -84,7 +81,6 @@
84 <%= theme_footer %> 81 <%= theme_footer %>
85 </div><!-- end id="theme-footer" --> 82 </div><!-- end id="theme-footer" -->
86 <%= noosfero_layout_features %> 83 <%= noosfero_layout_features %>
87 - <%= theme_javascript_ng %>  
88 <%= addthis_javascript %> 84 <%= addthis_javascript %>
89 <%= 85 <%=
90 @plugins.dispatch(:body_ending).map do |content| 86 @plugins.dispatch(:body_ending).map do |content|
app/views/profile/_profile_comment_form.html.erb
@@ -10,8 +10,8 @@ @@ -10,8 +10,8 @@
10 :rows => 1, 10 :rows => 1,
11 :class => 'submit-with-keypress', 11 :class => 'submit-with-keypress',
12 :title => _('Leave your comment'), 12 :title => _('Leave your comment'),
13 - :onfocus => ('if(this.value==this.title){this.value="";this.style.color="#000"};this.style.backgroundImage="url(' + profile_icon(current_person, :icon, false) + ')"' if logged_in?),  
14 - :onblur => ('if(this.value==""){this.value=this.title;this.style.color="#ccc"};this.style.backgroundImage="none"' if logged_in?), 13 + :onfocus => ("if(this.value==this.title){this.value='';this.style.color='#000'};this.style.backgroundImage='url(" + profile_icon(current_person, :icon, false) + ")'" if logged_in?),
  14 + :onblur => ("if(this.value==''){this.value=this.title;this.style.color='#ccc'};this.style.backgroundImage='none'" if logged_in?),
15 :value => _('Leave your comment'), 15 :value => _('Leave your comment'),
16 :style => 'color: #ccc' %> 16 :style => 'color: #ccc' %>
17 <%= hidden_field_tag :source_id, activity.id, :id => "activity_id_#{activity.id}" %> 17 <%= hidden_field_tag :source_id, activity.id, :id => "activity_id_#{activity.id}" %>
app/views/profile/_profile_scrap_reply_form.html.erb
@@ -9,8 +9,8 @@ @@ -9,8 +9,8 @@
9 :rows => 1, 9 :rows => 1,
10 :class => 'submit-with-keypress', 10 :class => 'submit-with-keypress',
11 :title => _('Leave your comment'), 11 :title => _('Leave your comment'),
12 - :onfocus => ('if(this.value==this.title){this.value="";this.style.color="#000"};this.style.backgroundImage="url(' + profile_icon(current_person, :icon, false) + ')"' if logged_in?),  
13 - :onblur => ('if(this.value==""){this.value=this.title;this.style.color="#ccc"};this.style.backgroundImage="none"' if logged_in?), 12 + :onfocus => ("if(this.value==this.title){this.value='';this.style.color='#000'};this.style.backgroundImage='url(" + profile_icon(current_person, :icon, false) + ")'" if logged_in?),
  13 + :onblur => ("if(this.value==''){this.value=this.title;this.style.color='#ccc'};this.style.backgroundImage='none'" if logged_in?),
14 :value => _('Leave your comment') %> 14 :value => _('Leave your comment') %>
15 <%= hidden_field_tag 'scrap[scrap_id]', scrap.id %> 15 <%= hidden_field_tag 'scrap[scrap_id]', scrap.id %>
16 <%= hidden_field_tag 'receiver_id', scrap.sender.id %> 16 <%= hidden_field_tag 'receiver_id', scrap.sender.id %>
app/views/profile_editor/_person_form.html.erb
@@ -27,6 +27,10 @@ @@ -27,6 +27,10 @@
27 <%= optional_field(@person, 'district', labelled_form_field(_('District'), text_field(:profile_data, :district, :rel => _('District')))) %> 27 <%= optional_field(@person, 'district', labelled_form_field(_('District'), text_field(:profile_data, :district, :rel => _('District')))) %>
28 <%= optional_field(@person, 'image', labelled_form_field(_('Image'), file_field(:file, :image, :rel => _('Image')))) %> 28 <%= optional_field(@person, 'image', labelled_form_field(_('Image'), file_field(:file, :image, :rel => _('Image')))) %>
29 29
  30 +<% @plugins.dispatch(:extra_optional_fields).each do |field| %>
  31 + <%= optional_field(@person, field[:name], labelled_form_field(field[:label], text_field(field[:object_name], field[:method], :rel => field[:label], :value => field[:value]))) %>
  32 +<% end %>
  33 +
30 <% optional_field(@person, 'schooling') do %> 34 <% optional_field(@person, 'schooling') do %>
31 <div class="formfieldline"> 35 <div class="formfieldline">
32 <label class='formlabel' for='profile_data_schooling'><%= _('Schooling') %></label> 36 <label class='formlabel' for='profile_data_schooling'><%= _('Schooling') %></label>
app/views/role/_form.html.erb
@@ -6,10 +6,14 @@ @@ -6,10 +6,14 @@
6 6
7 <%= required f.text_field(:name) %> 7 <%= required f.text_field(:name) %>
8 8
9 - <p><%= _('Permissions:') %><p>  
10 - <% permissions.keys.each do |p| %>  
11 - <%= check_box_tag("role[permissions][]", p, role.has_permission?(p), { :id => p }) %>  
12 - <%= content_tag(:label, permission_name(p), { :for => p }) %><br/> 9 + <% permissions.each do |key| %>
  10 + <div class="permissions <%= key.downcase %>">
  11 + <h4><%= _('%s Permissions:' % key) %></h4>
  12 + <% ActiveRecord::Base::PERMISSIONS[key].keys.each do |p| %>
  13 + <%= check_box_tag("role[permissions][]", p, role.has_permission?(p), { :id => p }) %>
  14 + <%= content_tag(:label, permission_name(p), { :for => p }) %><br/>
  15 + <% end %>
  16 + </div>
13 <% end %> 17 <% end %>
14 18
15 <% button_bar do %> 19 <% button_bar do %>
app/views/role/edit.html.erb
1 <h2> <%= _("Editing #{@role.name}") %> </h2> 1 <h2> <%= _("Editing #{@role.name}") %> </h2>
2 2
3 -<%= render :partial => 'form', :locals => { :mode => :edit, :role => @role, :permissions => ActiveRecord::Base::PERMISSIONS[@role.kind] } %> 3 +<%= render :partial => 'form', :locals => { :mode => :edit, :role => @role, :permissions => role_available_permissions(@role) } %>
app/views/role/new.html.erb
1 <h2> <%= _("Create a new role") %> </h2> 1 <h2> <%= _("Create a new role") %> </h2>
2 2
3 -<%= render :partial => 'form', :locals => { :mode => :create, :role => @role, :permissions => ActiveRecord::Base::PERMISSIONS[@role.kind] } %> 3 +<%= render :partial => 'form', :locals => { :mode => :create, :role => @role, :permissions => role_available_permissions(@role) } %>
app/views/task_mailer/generic_message.text.erb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +<%= _('Dear %s,') % @requestor %>
  2 +
  3 +<%= word_wrap(@message) %>
  4 +
  5 +<%= _('Greetings,') %>
  6 +
  7 +--
  8 +<%= _('%s team.') % @environment %>
  9 +<%= @url %>
app/views/task_mailer/task_activated.text.erb
@@ -1 +0,0 @@ @@ -1 +0,0 @@
1 -task_cancelled.text.erb  
2 \ No newline at end of file 0 \ No newline at end of file
app/views/task_mailer/task_cancelled.text.erb
@@ -1,9 +0,0 @@ @@ -1,9 +0,0 @@
1 -<%= _('Dear %s,') % @requestor %>  
2 -  
3 -<%= word_wrap(@message) %>  
4 -  
5 -<%= _('Greetings,') %>  
6 -  
7 ---  
8 -<%= _('%s team.') % @environment %>  
9 -<%= @url %>  
app/views/task_mailer/task_created.text.erb
@@ -1 +0,0 @@ @@ -1 +0,0 @@
1 -task_cancelled.text.erb  
2 \ No newline at end of file 0 \ No newline at end of file
app/views/task_mailer/task_finished.text.erb
@@ -1 +0,0 @@ @@ -1 +0,0 @@
1 -task_cancelled.text.erb  
2 \ No newline at end of file 0 \ No newline at end of file
config/application.rb
@@ -111,9 +111,7 @@ module Noosfero @@ -111,9 +111,7 @@ module Noosfero
111 # Make sure the secret is at least 30 characters and all random, 111 # Make sure the secret is at least 30 characters and all random,
112 # no regular words or you'll be exposed to dictionary attacks. 112 # no regular words or you'll be exposed to dictionary attacks.
113 config.secret_token = noosfero_session_secret 113 config.secret_token = noosfero_session_secret
114 - config.action_dispatch.session = {  
115 - :key => '_noosfero_session',  
116 - } 114 + config.session_store :cookie_store, :key => '_noosfero_session'
117 115
118 config.time_zone = File.read('/etc/timezone').split("\n").first 116 config.time_zone = File.read('/etc/timezone').split("\n").first
119 config.active_record.default_timezone = :local 117 config.active_record.default_timezone = :local
config/noosfero.yml.dist
@@ -5,7 +5,7 @@ development: @@ -5,7 +5,7 @@ development:
5 addthis_pub: your-user-name 5 addthis_pub: your-user-name
6 addthis_logo: http://localhost:3000/images/logo-200x50.png 6 addthis_logo: http://localhost:3000/images/logo-200x50.png
7 addthis_options: favorites, email, digg, delicious, technorati, slashdot, twitter, more 7 addthis_options: favorites, email, digg, delicious, technorati, slashdot, twitter, more
8 - gravatar: wavatar 8 + gravatar: mm
9 googlemaps_initial_zoom: 4 9 googlemaps_initial_zoom: 4
10 exception_recipients: [admin@example.com] 10 exception_recipients: [admin@example.com]
11 max_upload_size: 5MB 11 max_upload_size: 5MB
db/migrate/20140724134600_remove_environment_statistics_block_sooner.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class RemoveEnvironmentStatisticsBlockSooner < ActiveRecord::Migration
  2 + def self.up
  3 + update("UPDATE blocks SET type = 'StatisticsBlock' WHERE type = 'EnvironmentStatisticsBlock'")
  4 + end
  5 +
  6 + def self.down
  7 + say("Nothing to undo (cannot recover the data)")
  8 + end
  9 +end
db/migrate/20140724134601_fix_yaml_encoding.rb
1 class FixYamlEncoding < ActiveRecord::Migration 1 class FixYamlEncoding < ActiveRecord::Migration
2 def self.up 2 def self.up
3 - fix_encoding(Block, 'settings')  
4 - fix_encoding(Product, 'data')  
5 - fix_encoding(Environment, 'settings')  
6 - fix_encoding(Profile, 'data')  
7 - fix_encoding(ActionTracker::Record, 'params')  
8 - fix_encoding(Article, 'setting')  
9 - fix_encoding(Task, 'data') 3 + ActiveRecord::Base.transaction do
  4 + fix_encoding(Environment, 'settings')
  5 + fix_encoding(Profile, 'data')
  6 + fix_encoding(Product, 'data')
  7 + fix_encoding(ActionTracker::Record, 'params')
  8 + fix_encoding(Article, 'setting')
  9 + fix_encoding(Task, 'data')
  10 + fix_encoding(Block, 'settings')
  11 + end
10 end 12 end
11 13
12 def self.down 14 def self.down
@@ -16,15 +18,34 @@ class FixYamlEncoding &lt; ActiveRecord::Migration @@ -16,15 +18,34 @@ class FixYamlEncoding &lt; ActiveRecord::Migration
16 private 18 private
17 19
18 def self.fix_encoding(model, param) 20 def self.fix_encoding(model, param)
19 - result = model.find(:all, :conditions => "#{param} LIKE '%!binary%'") 21 + result = model.all
20 puts "Fixing #{result.count} rows of #{model} (#{param})" 22 puts "Fixing #{result.count} rows of #{model} (#{param})"
21 - result.each {|r| r.update_column(param, deep_fix(r.send(param)).to_yaml)} 23 + result.each do |r|
  24 + begin
  25 + yaml = r.send(param)
  26 + # if deserialization failed then a string is returned
  27 + if yaml.is_a? String
  28 + yaml.gsub! ': `', ': '
  29 + yaml = YAML.load yaml
  30 + end
  31 + r.update_column param, deep_fix(yaml).to_yaml
  32 + rescue => e
  33 + puts "FAILED #{r.inspect}"
  34 + puts e.message
  35 + end
  36 + end
22 end 37 end
23 38
24 def self.deep_fix(hash) 39 def self.deep_fix(hash)
25 hash.each do |value| 40 hash.each do |value|
26 - value.force_encoding('UTF-8') if value.is_a?(String) && !value.frozen? && value.encoding == Encoding::ASCII_8BIT  
27 deep_fix(value) if value.respond_to?(:each) 41 deep_fix(value) if value.respond_to?(:each)
  42 + if value.is_a? String and not value.frozen?
  43 + if value.encoding == Encoding::ASCII_8BIT
  44 + value.force_encoding "utf-8"
  45 + else
  46 + value.encode!("iso-8859-1").force_encoding("utf-8")
  47 + end
  48 + end
28 end 49 end
29 end 50 end
30 51
debian/bundle/config
@@ -1,3 +0,0 @@ @@ -1,3 +0,0 @@
1 ----  
2 -BUNDLE_WITHOUT: test:cucumber  
3 -  
debian/changelog
  1 +noosfero (1.0) wheezy; urgency=low
  2 +
  3 + * Noosfero 1.0 \o/
  4 +
  5 + -- Antonio Terceiro <terceiro@colivre.coop.br> Mon, 22 Dec 2014 10:52:21 -0300
  6 +
  7 +noosfero (1.0~rc4) wheezy-test; urgency=low
  8 +
  9 + * Fourth release candidate
  10 +
  11 + -- Antonio Terceiro <terceiro@colivre.coop.br> Wed, 19 Nov 2014 10:31:16 -0300
  12 +
1 noosfero (1.0~rc3) wheezy-test; urgency=low 13 noosfero (1.0~rc3) wheezy-test; urgency=low
2 14
3 * Third release candidate to Noosfero 1.0 15 * Third release candidate to Noosfero 1.0
4 16
5 - -- Antonio Terceiro <terceiro@debian.org> Fri, 12 Sep 2014 16:20:58 -0300 17 + -- Antonio Terceiro <terceiro@colivre.coop.br> Fri, 12 Sep 2014 16:20:58 -0300
6 18
7 noosfero (1.0~rc2) wheezy-test; urgency=low 19 noosfero (1.0~rc2) wheezy-test; urgency=low
8 20
9 * Second 1.0 release candidate 21 * Second 1.0 release candidate
10 22
11 - -- Antonio Terceiro <terceiro@debian.org> Fri, 12 Sep 2014 13:01:11 -0300 23 + -- Antonio Terceiro <terceiro@colivre.coop.br> Fri, 12 Sep 2014 13:01:11 -0300
12 24
13 noosfero (1.0~rc1) wheezy-test; urgency=low 25 noosfero (1.0~rc1) wheezy-test; urgency=low
14 26
15 * First 1.0 release candidate 27 * First 1.0 release candidate
16 28
17 - -- Rodrigo Souto <vagrant@wheezy-base> Fri, 15 Aug 2014 16:35:35 -0300 29 + -- Rodrigo Souto <rodrigo@colivre.coop.br> Fri, 15 Aug 2014 16:35:35 -0300
18 30
19 noosfero (0.99.0~rc20140618202455) wheezy-test; urgency=low 31 noosfero (0.99.0~rc20140618202455) wheezy-test; urgency=low
20 32
debian/control
@@ -51,6 +51,7 @@ Depends: @@ -51,6 +51,7 @@ Depends:
51 ruby-hpricot, 51 ruby-hpricot,
52 ruby-nokogiri, 52 ruby-nokogiri,
53 ruby-acts-as-taggable-on, 53 ruby-acts-as-taggable-on,
  54 + ruby-progressbar,
54 ruby-prototype-rails, 55 ruby-prototype-rails,
55 ruby-rails-autolink, 56 ruby-rails-autolink,
56 memcached, 57 memcached,
@@ -60,6 +61,11 @@ Depends: @@ -60,6 +61,11 @@ Depends:
60 dbconfig-common, 61 dbconfig-common,
61 adduser, 62 adduser,
62 exim4 | mail-transport-agent, 63 exim4 | mail-transport-agent,
  64 +# to minimize upgrade issues:
  65 + ruby-feedparser (>= 0.7-3~),
  66 + ruby-eventmachine (>= 0.12.10-4~),
  67 + ruby-rack (>= 1.4.5-2~),
  68 + ruby-tzinfo (>= 1.1.0-2~),
63 ${misc:Depends} 69 ${misc:Depends}
64 Recommends: 70 Recommends:
65 postgresql, 71 postgresql,
debian/filter-gemfile 0 → 100755
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +#!/bin/sh
  2 +
  3 +set -e
  4 +
  5 +sed -e '/^group\s*:\(test\|cucumber\)/,/^end/ d' Gemfile
debian/noosfero.install
@@ -7,9 +7,6 @@ util usr/share/noosfero @@ -7,9 +7,6 @@ util usr/share/noosfero
7 Rakefile usr/share/noosfero 7 Rakefile usr/share/noosfero
8 vendor usr/share/noosfero 8 vendor usr/share/noosfero
9 9
10 -Gemfile usr/share/noosfero  
11 -debian/bundle/config usr/share/noosfero/.bundle  
12 -  
13 config/application.rb usr/share/noosfero/config 10 config/application.rb usr/share/noosfero/config
14 config/boot.rb usr/share/noosfero/config 11 config/boot.rb usr/share/noosfero/config
15 config/environment.rb usr/share/noosfero/config 12 config/environment.rb usr/share/noosfero/config
debian/noosfero.links
@@ -15,4 +15,4 @@ var/lib/noosfero-data/public/thumbnails usr/share/noosfero/public/th @@ -15,4 +15,4 @@ var/lib/noosfero-data/public/thumbnails usr/share/noosfero/public/th
15 usr/share/noosfero/public/designs/themes/noosfero usr/share/noosfero/public/designs/themes/default 15 usr/share/noosfero/public/designs/themes/noosfero usr/share/noosfero/public/designs/themes/default
16 usr/share/noosfero/public/designs/icons/tango usr/share/noosfero/public/designs/icons/default 16 usr/share/noosfero/public/designs/icons/tango usr/share/noosfero/public/designs/icons/default
17 usr/share/noosfero/script/noosfero-plugins usr/sbin/noosfero-plugins 17 usr/share/noosfero/script/noosfero-plugins usr/sbin/noosfero-plugins
18 -usr/share/noosfero/Gemfile.lock /dev/null 18 +/dev/null usr/share/noosfero/Gemfile.lock
debian/noosfero.yml
1 # See an example of this file at: 1 # See an example of this file at:
2 # /usr/share/doc/noosfero/examples/ 2 # /usr/share/doc/noosfero/examples/
3 production: 3 production:
4 - gravatar: wavatar 4 + gravatar: mm
@@ -20,6 +20,10 @@ override_dh_link: @@ -20,6 +20,10 @@ override_dh_link:
20 dh_link usr/lib/noosfero/dbinstall usr/share/dbconfig-common/scripts/noosfero/install/$$db; \ 20 dh_link usr/lib/noosfero/dbinstall usr/share/dbconfig-common/scripts/noosfero/install/$$db; \
21 done 21 done
22 22
  23 +override_dh_auto_install:
  24 + dh_auto_install
  25 + debian/filter-gemfile > $(CURDIR)/debian/noosfero/usr/share/noosfero/Gemfile
  26 +
23 override_dh_installinit: 27 override_dh_installinit:
24 dh_installinit -pnoosfero --onlyscripts 28 dh_installinit -pnoosfero --onlyscripts
25 29
etc/noosfero/varnish-noosfero.vcl
@@ -10,6 +10,13 @@ sub vcl_recv { @@ -10,6 +10,13 @@ sub vcl_recv {
10 } 10 }
11 } 11 }
12 12
  13 +sub vcl_deliver {
  14 + # Force clients to aways hit the server again for HTML pages
  15 + if (resp.http.Content-Type ~ "^text/html") {
  16 + set resp.http.Cache-Control = "no-cache";
  17 + }
  18 +}
  19 +
13 sub vcl_error { 20 sub vcl_error {
14 set obj.http.Content-Type = "text/html; charset=utf-8"; 21 set obj.http.Content-Type = "text/html; charset=utf-8";
15 22
features/gallery_navigation.feature
@@ -1,86 +0,0 @@ @@ -1,86 +0,0 @@
1 -Feature: gallery_navigation  
2 - As a noosfero user  
3 - I want to navigate over image gallery  
4 -  
5 - Background:  
6 - Given the following users  
7 - | login |  
8 - | marciopunk |  
9 - And the following galleries  
10 - | owner | name |  
11 - | marciopunk | my-gallery |  
12 - | marciopunk | other-gallery |  
13 - And the following files  
14 - | owner | file | mime | parent |  
15 - | marciopunk | rails.png | image/png | my-gallery |  
16 - | marciopunk | rails.png | image/png | other-gallery |  
17 - | marciopunk | other-pic.jpg | image/jpeg | my-gallery |  
18 -  
19 - Scenario: provide link to go to next image  
20 - Given I am on /marciopunk/my-gallery/other-pic.jpg?view=true  
21 - Then I should see "Next »"  
22 -  
23 - @selenium  
24 - Scenario: view next image when follow next link  
25 - Given I am on /marciopunk/my-gallery/other-pic.jpg?view=true  
26 - When I follow "Next »"  
27 - Then I should see "rails.png" within ".title"  
28 -  
29 - Scenario: not link to next when in last image  
30 - When I am on /marciopunk/my-gallery/rails.png?view=true  
31 - Then I should see "« Previous" within ".gallery-navigation a"  
32 - And I should not see "Next »" within ".gallery-navigation a"  
33 -  
34 - Scenario: provide link to go to previous image  
35 - Given I am on /marciopunk/my-gallery/other-pic.jpg?view=true  
36 - Then I should see "« Previous"  
37 -  
38 - @selenium  
39 - Scenario: view previous image when follow previous link  
40 - Given I am on /marciopunk/my-gallery/rails.png?view=true  
41 - When I follow "« Previous"  
42 - Then I should see "other-pic.jpg" within ".title"  
43 -  
44 - Scenario: not link to previous when in first image  
45 - When I am on /marciopunk/my-gallery/other-pic.jpg?view=true  
46 - Then I should see "Next »" within ".gallery-navigation a"  
47 - And I should not see "« Previous" within ".gallery-navigation a"  
48 -  
49 - Scenario: display number of current and total of images  
50 - Given I am on /marciopunk/my-gallery/other-pic.jpg?view=true  
51 - Then I should see "image 1 of 2" within ".gallery-navigation"  
52 -  
53 - Scenario: increment current number when follow next  
54 - Given I am on /marciopunk/my-gallery/other-pic.jpg?view=true  
55 - Then I should see "image 1 of 2" within ".gallery-navigation"  
56 - When I follow "Next »"  
57 - Then I should see "image 2 of 2" within ".gallery-navigation"  
58 -  
59 - Scenario: decrement current number when follow next  
60 - Given I am on /marciopunk/my-gallery/rails.png?view=true  
61 - Then I should see "image 2 of 2" within ".gallery-navigation"  
62 - When I follow "« Previous"  
63 - Then I should see "image 1 of 2" within ".gallery-navigation"  
64 -  
65 - Scenario: provide button to go back to gallery  
66 - Given I am on /marciopunk/my-gallery/rails.png?view=true  
67 - Then I should see "Go back to my-gallery"  
68 - When I follow "Go back to my-gallery"  
69 - Then I should be on /marciopunk/my-gallery  
70 -  
71 - # Looking for page title is problematic on selenium since it considers the  
72 - # title to be invibible. Checkout some information about this:  
73 - # * https://github.com/jnicklas/capybara/issues/863  
74 - # * https://github.com/jnicklas/capybara/pull/953  
75 - @selenium  
76 - Scenario: image title in window title  
77 - Given I am logged in as "marciopunk"  
78 - When I go to /marciopunk/other-gallery/rails.png?view=true  
79 - Then I should see "rails.png" within any "h1"  
80 -# And the page title should be "rails.png"  
81 - And I follow "Edit"  
82 - And I fill in "Title" with "Rails is cool"  
83 - And I press "Save"  
84 - When I go to /marciopunk/other-gallery/rails.png?view=true  
85 - Then I should see "Rails is cool" within any "h1"  
86 - #Then the page title should be "Rails is cool"  
features/gravatar_support.feature
@@ -20,11 +20,11 @@ Feature: Gravatar Support @@ -20,11 +20,11 @@ Feature: Gravatar Support
20 Scenario: The Aurium's gravatar picture must link to his gravatar profile 20 Scenario: The Aurium's gravatar picture must link to his gravatar profile
21 # because Aurium has his picture registered at garvatar.com. 21 # because Aurium has his picture registered at garvatar.com.
22 When I go to article "My Article" 22 When I go to article "My Article"
23 - Then I should see "Aurium" linking to "http://www.gravatar.com/24a625896a07aa37fdb2352e302e96de" 23 + Then I should see "Aurium" linking to "//www.gravatar.com/24a625896a07aa37fdb2352e302e96de"
24 24
25 @selenium 25 @selenium
26 Scenario: The NoOne's gravatar picture must link to Gravatar homepage 26 Scenario: The NoOne's gravatar picture must link to Gravatar homepage
27 # because NoOne <nobody@colivre.coop.br> has no picture registered. 27 # because NoOne <nobody@colivre.coop.br> has no picture registered.
28 When I go to article "My Article" 28 When I go to article "My Article"
29 - Then I should see "NoOne" linking to "http://www.gravatar.com" 29 + Then I should see "NoOne" linking to "//www.gravatar.com"
30 30
features/signup.feature
@@ -278,29 +278,6 @@ Feature: signup @@ -278,29 +278,6 @@ Feature: signup
278 Then "José da Silva" should be a member of "Free Software" 278 Then "José da Silva" should be a member of "Free Software"
279 279
280 @selenium 280 @selenium
281 - Scenario: join community on direct signup  
282 - Given the following users  
283 - | login | name |  
284 - | mariasilva | Maria Silva |  
285 - And the following communities  
286 - | name | identifier | owner |  
287 - | Free Software | freesoftware | mariasilva |  
288 - And feature "skip_new_user_email_confirmation" is enabled on environment  
289 - And I am on /freesoftware  
290 - When I follow "Join"  
291 - And I follow "New user"  
292 - And I fill in the following within ".no-boxes":  
293 - | e-Mail | josesilva@example.com |  
294 - | Username | josesilva |  
295 - | Password | secret |  
296 - | Password confirmation | secret |  
297 - | Full name | José da Silva |  
298 - And wait for the captcha signup time  
299 - And I press "Create my account"  
300 - Then I should see "Control panel"  
301 - And "José da Silva" should be a member of "Free Software"  
302 -  
303 - @selenium  
304 Scenario: user registration is moderated by admin 281 Scenario: user registration is moderated by admin
305 Given feature "admin_must_approve_new_users" is enabled on environment 282 Given feature "admin_must_approve_new_users" is enabled on environment
306 And feature "skip_new_user_email_confirmation" is disabled on environment 283 And feature "skip_new_user_email_confirmation" is disabled on environment
features/step_definitions/noosfero_steps.rb
@@ -762,3 +762,11 @@ When /^I confirm the &quot;(.*)&quot; dialog$/ do |confirmation| @@ -762,3 +762,11 @@ When /^I confirm the &quot;(.*)&quot; dialog$/ do |confirmation|
762 assert_equal confirmation, a.text 762 assert_equal confirmation, a.text
763 a.accept 763 a.accept
764 end 764 end
  765 +
  766 +Given /^the field (.*) is public for all users$/ do |field|
  767 + Person.all.each do |person|
  768 + person.fields_privacy = Hash.new if person.fields_privacy.nil?
  769 + person.fields_privacy[field] = "public"
  770 + person.save!
  771 + end
  772 +end
765 \ No newline at end of file 773 \ No newline at end of file
lib/log_memory_consumption_job.rb
1 class LogMemoryConsumptionJob < Struct.new(:last_stat) 1 class LogMemoryConsumptionJob < Struct.new(:last_stat)
2 # Number of entries do display 2 # Number of entries do display
3 N = 20 3 N = 20
4 - # In seconds  
5 - PERIOD = 10  
6 4
7 def perform 5 def perform
8 logpath = File.join(Rails.root, 'log', "#{ENV['RAILS_ENV']}_memory_consumption.log") 6 logpath = File.join(Rails.root, 'log', "#{ENV['RAILS_ENV']}_memory_consumption.log")
lib/noosfero.rb
@@ -70,16 +70,6 @@ module Noosfero @@ -70,16 +70,6 @@ module Noosfero
70 end 70 end
71 end 71 end
72 72
73 - def self.term(t)  
74 - self.terminology.get(t)  
75 - end  
76 - def self.terminology  
77 - @terminology ||= Noosfero::Terminology::Default.instance  
78 - end  
79 - def self.terminology=(term)  
80 - @terminology = term  
81 - end  
82 -  
83 def self.url_options 73 def self.url_options
84 case Rails.env 74 case Rails.env
85 when 'development' 75 when 'development'
lib/noosfero/gravatar.rb
1 module Noosfero::Gravatar 1 module Noosfero::Gravatar
2 def gravatar_profile_image_url(email, options = {}) 2 def gravatar_profile_image_url(email, options = {})
3 - "http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(email.to_s)}?" + { 3 + "//www.gravatar.com/avatar/#{Digest::MD5.hexdigest(email.to_s)}?" + {
4 :only_path => false, 4 :only_path => false,
5 }.merge(options).map{|k,v| '%s=%s' % [ k,v ] }.join('&') 5 }.merge(options).map{|k,v| '%s=%s' % [ k,v ] }.join('&')
6 end 6 end
7 7
8 def gravatar_profile_url(email) 8 def gravatar_profile_url(email)
9 - 'http://www.gravatar.com/'+ Digest::MD5.hexdigest(email.to_s) 9 + '//www.gravatar.com/'+ Digest::MD5.hexdigest(email.to_s)
10 end 10 end
11 end 11 end
lib/noosfero/plugin.rb
@@ -88,18 +88,29 @@ class Noosfero::Plugin @@ -88,18 +88,29 @@ class Noosfero::Plugin
88 # This is a generic method that initialize any possible filter defined by a 88 # This is a generic method that initialize any possible filter defined by a
89 # plugin to a specific controller 89 # plugin to a specific controller
90 def load_plugin_filters(plugin) 90 def load_plugin_filters(plugin)
91 - plugin_methods = plugin.instance_methods.select {|m| m.to_s.end_with?('_filters')}  
92 - plugin_methods.each do |plugin_method|  
93 - controller_class = plugin_method.to_s.gsub('_filters', '').camelize.constantize  
94 - filters = plugin.new.send(plugin_method)  
95 - filters = [filters] if !filters.kind_of?(Array)  
96 -  
97 - filters.each do |plugin_filter|  
98 - filter_method = (plugin.name.underscore.gsub('/','_') + '_' + plugin_filter[:method_name]).to_sym  
99 - controller_class.send(plugin_filter[:type], filter_method, (plugin_filter[:options] || {}))  
100 - controller_class.send(:define_method, filter_method) do  
101 - instance_eval(&plugin_filter[:block]) if environment.plugin_enabled?(plugin)  
102 - end 91 + Rails.configuration.to_prepare do
  92 + filters = plugin.new.send 'application_controller_filters' rescue []
  93 + Noosfero::Plugin.add_controller_filters ApplicationController, plugin, filters
  94 +
  95 + plugin_methods = plugin.instance_methods.select {|m| m.to_s.end_with?('_filters')}
  96 + plugin_methods.each do |plugin_method|
  97 + controller_class = plugin_method.to_s.gsub('_filters', '').camelize.constantize
  98 +
  99 + filters = plugin.new.send(plugin_method)
  100 + Noosfero::Plugin.add_controller_filters controller_class, plugin, filters
  101 + end
  102 + end
  103 + end
  104 +
  105 + def add_controller_filters(controller_class, plugin, filters)
  106 + unless filters.is_a?(Array)
  107 + filters = [filters]
  108 + end
  109 + filters.each do |plugin_filter|
  110 + filter_method = (plugin.name.underscore.gsub('/','_') + '_' + plugin_filter[:method_name]).to_sym
  111 + controller_class.send(plugin_filter[:type], filter_method, (plugin_filter[:options] || {}))
  112 + controller_class.send(:define_method, filter_method) do
  113 + instance_exec(&plugin_filter[:block]) if environment.plugin_enabled?(plugin)
103 end 114 end
104 end 115 end
105 end 116 end
@@ -531,6 +542,18 @@ class Noosfero::Plugin @@ -531,6 +542,18 @@ class Noosfero::Plugin
531 nil 542 nil
532 end 543 end
533 544
  545 + # -> Perform extra transactions related to profile in profile editor
  546 + # returns = true in success or raise and exception if it could not update the data
  547 + def profile_editor_transaction_extras
  548 + nil
  549 + end
  550 +
  551 + # -> Return a list of hashs with the needed information to create optional fields
  552 + # returns = a list of hashs as {:name => "string", :label => "string", :object_name => :key, :method => :key}
  553 + def extra_optional_fields
  554 + []
  555 + end
  556 +
534 # -> Adds additional blocks to profiles and environments. 557 # -> Adds additional blocks to profiles and environments.
535 # Your plugin must implements a class method called 'extra_blocks' 558 # Your plugin must implements a class method called 'extra_blocks'
536 # that returns a hash with the following syntax. 559 # that returns a hash with the following syntax.
lib/noosfero/version.rb
1 module Noosfero 1 module Noosfero
2 PROJECT = 'noosfero' 2 PROJECT = 'noosfero'
3 - VERSION = '1.0~rc3' 3 + VERSION = '1.0'
4 end 4 end
lib/tasks/ci.rake
@@ -7,9 +7,18 @@ namespace :ci do @@ -7,9 +7,18 @@ namespace :ci do
7 from = ENV['PREV_HEAD'] || "origin/#{current_branch}" 7 from = ENV['PREV_HEAD'] || "origin/#{current_branch}"
8 to = ENV['HEAD'] || current_branch 8 to = ENV['HEAD'] || current_branch
9 changed_files = `git diff --name-only #{from}..#{to}`.split.select do |f| 9 changed_files = `git diff --name-only #{from}..#{to}`.split.select do |f|
10 - File.exist?(f) 10 + File.exist?(f) && f.split(File::SEPARATOR).first != 'vendor'
11 end 11 end
12 12
  13 + changed_plugin_files = changed_files.select do |f|
  14 + f.split(File::SEPARATOR).first == 'plugins'
  15 + end
  16 + changed_plugins = changed_plugin_files.map do |f|
  17 + f.split(File::SEPARATOR)[1]
  18 + end.uniq
  19 +
  20 + changed_files -= changed_plugin_files
  21 +
13 # explicitly changed tests 22 # explicitly changed tests
14 tests = changed_files.select { |f| f =~ /test\/.*_test\.rb$/ } 23 tests = changed_files.select { |f| f =~ /test\/.*_test\.rb$/ }
15 features = changed_files.select { |f| f =~ /\.feature$/ } 24 features = changed_files.select { |f| f =~ /\.feature$/ }
@@ -26,7 +35,14 @@ namespace :ci do @@ -26,7 +35,14 @@ namespace :ci do
26 35
27 sh 'testrb', '-Itest', *tests unless tests.empty? 36 sh 'testrb', '-Itest', *tests unless tests.empty?
28 sh 'cucumber', *features unless features.empty? 37 sh 'cucumber', *features unless features.empty?
29 - sh 'cucumber', '-p', 'selenium', *features unless features.empty? 38 + sh 'xvfb-run', 'cucumber', '-p', 'selenium', *features unless features.empty?
  39 +
  40 + changed_plugins.each do |plugin|
  41 + task = "test:noosfero_plugins:#{plugin}"
  42 + puts "Running #{task}"
  43 + Rake::Task[task].execute
  44 + end
  45 +
30 end 46 end
31 47
32 end 48 end
lib/tasks/plugins_tests.rake
  1 +@broken_plugins = %w[
  2 + anti_spam
  3 + bsc
  4 + comment_classification
  5 + ldap
  6 + solr
  7 +]
  8 +
1 @all_plugins = Dir.glob('plugins/*').map { |f| File.basename(f) } - ['template'] 9 @all_plugins = Dir.glob('plugins/*').map { |f| File.basename(f) } - ['template']
2 @all_plugins.sort! 10 @all_plugins.sort!
3 @all_tasks = [:units, :functionals, :integration, :cucumber, :selenium] 11 @all_tasks = [:units, :functionals, :integration, :cucumber, :selenium]
@@ -104,7 +112,7 @@ def run_test(name, files) @@ -104,7 +112,7 @@ def run_test(name, files)
104 end 112 end
105 113
106 def run_testrb(files) 114 def run_testrb(files)
107 - sh 'testrb', '-Itest', *files 115 + sh 'testrb', '-I.:test', *files
108 end 116 end
109 117
110 def run_cucumber(profile, files) 118 def run_cucumber(profile, files)
@@ -167,6 +175,7 @@ def test_sequence(plugins, tasks) @@ -167,6 +175,7 @@ def test_sequence(plugins, tasks)
167 end 175 end
168 end 176 end
169 rollback_plugins_state 177 rollback_plugins_state
  178 + yield(failed) if block_given?
170 fail 'There are broken tests to be fixed!' if fail_flag 179 fail 'There are broken tests to be fixed!' if fail_flag
171 end 180 end
172 181
@@ -195,13 +204,39 @@ namespace :test do @@ -195,13 +204,39 @@ namespace :test do
195 @all_tasks.each do |taskname| 204 @all_tasks.each do |taskname|
196 desc "Run #{taskname} tests for all plugins" 205 desc "Run #{taskname} tests for all plugins"
197 task taskname do 206 task taskname do
198 - test_sequence(@all_plugins, taskname) 207 + test_sequence(@all_plugins - @broken_plugins, taskname)
199 end 208 end
200 end 209 end
201 end 210 end
202 211
203 desc "Run all tests for all plugins" 212 desc "Run all tests for all plugins"
204 task :noosfero_plugins do 213 task :noosfero_plugins do
205 - test_sequence(@all_plugins, @all_tasks) 214 + test_sequence(@all_plugins - @broken_plugins, @all_tasks) do |failed|
  215 + plugins_status_report(failed)
  216 + end
206 end 217 end
207 end 218 end
  219 +
  220 +def plugins_status_report(failed)
  221 + w = @all_plugins.map { |s| s.size }.max
  222 +
  223 + puts
  224 + printf ('=' * (w + 21)) + "\n"
  225 + puts 'Plugins status report'
  226 + printf ('=' * (w + 21)) + "\n"
  227 + printf "%-#{w}s %s\n", "Plugin", "Status"
  228 + printf ('-' * w) + ' ' + ('-' * 20) + "\n"
  229 +
  230 + @all_plugins.each do |plugin|
  231 + if @broken_plugins.include?(plugin)
  232 + status = "SKIP"
  233 + elsif !failed[plugin] || failed[plugin].empty?
  234 + status = "PASS"
  235 + else
  236 + status = "FAIL: #{failed[plugin].join(', ')}"
  237 + end
  238 + printf "%-#{w}s %s\n", plugin, status
  239 + end
  240 + printf ('=' * (w + 21)) + "\n"
  241 + puts
  242 +end
lib/tasks/release.rake
@@ -83,7 +83,7 @@ EOF @@ -83,7 +83,7 @@ EOF
83 begin 83 begin
84 File.open("AUTHORS.md", 'w') do |output| 84 File.open("AUTHORS.md", 'w') do |output|
85 output.puts AUTHORS_HEADER 85 output.puts AUTHORS_HEADER
86 - output.puts `git log --pretty=format:'%aN <%aE>' | sort | uniq` 86 + output.puts `git log --no-merges --pretty=format:'%aN <%aE>' | sort | uniq`
87 output.puts AUTHORS_FOOTER 87 output.puts AUTHORS_FOOTER
88 end 88 end
89 commit_changes(['AUTHORS.md'], 'Updating authors file') if !pendencies_on_authors[:ok] 89 commit_changes(['AUTHORS.md'], 'Updating authors file') if !pendencies_on_authors[:ok]
@@ -137,7 +137,17 @@ EOF @@ -137,7 +137,17 @@ EOF
137 new_version += '~rc1' 137 new_version += '~rc1'
138 end 138 end
139 else 139 else
140 - new_version.sub!(/~rc[0-9]+/, '') 140 + if new_version =~ /~rc\d+/
  141 + new_version.sub!(/~rc[0-9]+/, '')
  142 + else
  143 + components = new_version.split('.').map(&:to_i)
  144 + if components.size < 3
  145 + components << 1
  146 + else
  147 + components[-1] += 1
  148 + end
  149 + new_version = components.join('.')
  150 + end
141 end 151 end
142 152
143 puts "Current version: #{$version}" 153 puts "Current version: #{$version}"
lib/unifreire_terminology.rb
@@ -1,44 +0,0 @@ @@ -1,44 +0,0 @@
1 -require 'noosfero/terminology'  
2 -  
3 -class UnifreireTerminology < Noosfero::Terminology::Custom  
4 -  
5 - def initialize  
6 - # NOTE: the hash values must be marked for translation!!  
7 - super({  
8 - 'Enterprises' => N_('Institutions'),  
9 - 'enterprises' => N_('institutions'),  
10 - 'The enterprises where this user works.' => N_('The institution where this user belongs.'),  
11 - 'A block that displays your enterprises' => N_('A block that displays your institutions.'),  
12 - 'All enterprises' => N_('All institutions'),  
13 - 'Disable search for enterprises' => N_('Disable search for institutions'),  
14 - 'One enterprise' => N_('One institution'),  
15 - '%{num} enterprises' => N_('%{num} institutions'),  
16 - 'Favorite Enterprises' => N_('Favorite Institutions'),  
17 - 'This user\'s favorite enterprises.' => N_('This user\'s favorite institutions'),  
18 - 'A block that displays your favorite enterprises' => N_('A block that displays your favorite institutions'),  
19 - 'All favorite enterprises' => N_('All favorite institutions'),  
20 - 'A search for enterprises by products selled and local' => N_('A search for institutions by products selled and local'),  
21 - 'Edit message for disabled enterprises' => N_('Edit message for disabled institutions'),  
22 - 'Add favorite enterprise' => N_('Add favorite institution'),  
23 - 'Validation info is the information the enterprises will see about how your organization processes the enterprises validations it receives: validation methodology, restrictions to the types of enterprises the organization validates etc.' => N_('Validation info is the information the institutions will see about how your organization processes the institutions validations it receives: validation methodology, restrictions to the types of institutions the organization validates etc.'),  
24 - 'Here are all <b>%s</b>\'s enterprises.' => N_('Here are all <b>%s</b>\'s institutions.'),  
25 - 'Here are all <b>%s</b>\'s favorite enterprises.' => N_('Here are all <b>%s</b>\'s favorite institutions.'),  
26 - 'Favorite Enterprises' => N_('Favorite Institutions'),  
27 - 'Enterprises in "%s"' => N_('Institutions in "%s"'),  
28 - 'Register a new Enterprise' => N_('Register a new Institution'),  
29 - 'Events' => N_('Schedule'),  
30 - 'Manage enterprise fields' => N_('Manage institutions fields'),  
31 - "%s's enterprises" => N_("%s's institutions"),  
32 - 'Activate your enterprise' => N_('Activate your institution'),  
33 - 'Enterprise activation code' => N_('Institution activation code'),  
34 - 'Disable activation of enterprises' => N_('Disable activation of institutions'),  
35 - "%s's favorite enterprises" => N_("%s's favorite institutions"),  
36 - 'Disable Enterprise' => N_('Disable Institution'),  
37 - 'Enable Enterprise' => N_('Enable Institution'),  
38 - 'Enterprise Validation' => N_('Institution Validation'),  
39 - 'Enterprise Info and settings' => N_('Institution Info and settings'),  
40 - 'Enterprises are disabled when created' => N_('Institutions are disabled when created'),  
41 - })  
42 - end  
43 -  
44 -end  
lib/user_activation_job.rb
1 class UserActivationJob < Struct.new(:user_id) 1 class UserActivationJob < Struct.new(:user_id)
2 def perform 2 def perform
3 user = User.find(user_id) 3 user = User.find(user_id)
4 - user.destroy unless user.activated? 4 + user.destroy unless user.activated? || user.person.is_template?
5 end 5 end
6 end 6 end
lib/white_list_filter.rb
@@ -9,7 +9,7 @@ module WhiteListFilter @@ -9,7 +9,7 @@ module WhiteListFilter
9 unless iframe =~ /src=['"].*src=['"]/ 9 unless iframe =~ /src=['"].*src=['"]/
10 trusted_sites.each do |trusted_site| 10 trusted_sites.each do |trusted_site|
11 re_dom = trusted_site.gsub('.', '\.') 11 re_dom = trusted_site.gsub('.', '\.')
12 - if iframe =~ /src=["']https?:\/\/(www\.)?#{re_dom}\// 12 + if iframe =~ /src=["'](https?:)?\/\/(www\.)?#{re_dom}\//
13 result = iframe 13 result = iframe
14 end 14 end
15 end 15 end
lib/zen3_terminology.rb
@@ -1,88 +0,0 @@ @@ -1,88 +0,0 @@
1 -require 'noosfero/terminology'  
2 -  
3 -class Zen3Terminology < Noosfero::Terminology::Custom  
4 -  
5 - def initialize  
6 - # NOTE: the hash values must be marked for translation!!  
7 - super({  
8 - 'My Home Page' => N_('My ePortfolio'),  
9 - 'Homepage' => N_('ePortfolio'),  
10 - 'Communities' => N_('Groups'),  
11 - 'communities' => N_('groups'),  
12 - 'A block that displays your communities' => N_('A block that displays your groups'),  
13 - 'A block that displays your friends' => N_('A block that displays your contacts'),  
14 - 'The communities in which the user is a member' => N_('The groups in which the user is a member'),  
15 - 'All communities' => N_('All groups'),  
16 - 'Community' => N_('Group'),  
17 - 'One community' => N_('One group'),  
18 - '%{num} communities' => N_('%{num} groups'),  
19 - 'Disable search for communities' => N_('Disable search for groups'),  
20 - 'Enterprises' => N_('Organizations'),  
21 - 'enterprises' => N_('organizations'),  
22 - 'The enterprises where this user works.' => N_('The organizations where this user works.'),  
23 - 'A block that displays your enterprises' => N_('A block that displays your organizations.'),  
24 - 'All enterprises' => N_('All organizations'),  
25 - 'Disable search for enterprises' => N_('Disable search for organizations'),  
26 - 'One enterprise' => N_('One organization'),  
27 - '%{num} enterprises' => N_('%{num} organizations'),  
28 - 'Favorite Enterprises' => N_('Favorite Organizations'),  
29 - 'This user\'s favorite enterprises.' => N_('This user\'s favorite organizations'),  
30 - 'A block that displays your favorite enterprises' => N_('A block that displays your favorite organizations'),  
31 - 'All favorite enterprises' => N_('All favorite organizations'),  
32 - 'A search for enterprises by products selled and local' => N_('A search for organizations by products selled and local'),  
33 - 'Edit message for disabled enterprises' => N_('Edit message for disabled organizations'),  
34 - 'Add enterprise as favorite' => N_('Add organization as favorite'),  
35 - 'Validation info is the information the enterprises will see about how your organization processes the enterprises validations it receives: validation methodology, restrictions to the types of enterprises the organization validates etc.' => N_('Validation info is the information the organizations will see about how your organization processes the organizations validations it receives: validation methodology, restrictions to the types of organizations the organization validates etc.'),  
36 - 'Here are all <b>%s</b>\'s enterprises.' => N_('Here all all <b>%s</b>\'s organizations.'),  
37 - 'Here are all <b>%s</b>\'s favorite enterprises.' => N_('Here are all <b>%s</b>\'s favorite organizations.'),  
38 - 'Favorite Enterprises' => N_('Favorite Organizations'),  
39 - 'Enterprises in "%s"' => N_('Organizations in "%s"'),  
40 - 'Register a new Enterprise' => N_('Register a new organization'),  
41 - 'One friend' => N_('One contact'),  
42 - '%s friends' => N_('%s contacts'),  
43 - '%s communities' => N_('%s groups'),  
44 - 'Are you sure you want to remove %s from your friends list?' => N_('Are you sure you want to remove %s from your contacts list?'),  
45 - 'Note that %s will still have you as a friend, unless he/she also wants to remove you from his/her friend list.' => N_('Note that %s will still have you as a contact, unless he/she also wants to remove you from his/her contact list.'),  
46 - 'Yes, I want to remove %s from my friend list' => N_('Yes, I want to remove %s from my contact list'),  
47 - 'Adding %s as a friend' => N_('Adding %s as a contact'),  
48 - 'Are you sure you want to add %s as your friend?' => N_('Are you sure you want to add %s as your contact?'),  
49 - 'Note that %s will need to accept being added as your friend.' => N_('Note that %s will need to accept being added as your contact.'),  
50 - 'Classify your new friend %s: ' => N_('Classify your new contact %s: '),  
51 - 'Yes, I want to add %s as my friend' => N_('Yes, I want to add %s as my contact'),  
52 - 'Manage friends' => N_('Manage contacts'),  
53 - 'Add friend' => N_('Add contact'),  
54 - 'Removing friend: %s' => N_('Removing friend: %s'),  
55 - 'Clicking on this button will remove your friend relation with %s.' => N_('Clicking on this button will remove your contact relation with %s.'),  
56 - 'You have no friends yet.' => N_('You have no contacts yet.'),  
57 - '%s\'s friends' => N_('%s\'s contacts'),  
58 - 'Here are all <b>%s</b>\'s friends.' => N_('Here are all <b>%s</b>\'s contacts.'),  
59 - 'Friends' => N_('Contacts'),  
60 - 'Creating new community' => N_('Creating new group'),  
61 - 'Do you want to join this community?' => N_('Do you want to join this group?'),  
62 - 'Activate your enterprise' => N_('Activate your organization'),  
63 - 'Enterprise activation code' => N_('Organization activation code'),  
64 - 'Disable activation of enterprises' => N_('Disable activation of organizations'),  
65 - 'Manage community fields' => N_('Manage group fields'),  
66 - 'Create a new community' => N_('Create a new group'),  
67 - 'Preferred domain name:' => N_('Choose your host community:'),  
68 - 'My communities' => N_('My groups'),  
69 - 'Community Info and settings' => N_('Group Info and Settings'),  
70 - '{#} community' => N_('{#} group'),  
71 - '{#} communities' => N_('{#} groups'),  
72 - '{#} enterprise' => N_('{#} organization'),  
73 - '{#} enterprises' => N_('{#} organizations'),  
74 - '{#} friend' => N_('{#} contact'),  
75 - '{#} friends' => N_('{#} contacts'),  
76 - "%s's favorite enterprises" => N_("%s's favorite organizations"),  
77 - 'Disable Enterprise' => N_('Disable Organization'),  
78 - 'Enable Enterprise' => N_('Enable Organization'),  
79 - 'Enterprise Validation' => N_('Organization Validation'),  
80 - 'Enterprise Info and settings' => N_('Organization Info and settings'),  
81 - 'Choose the communities you want to join and/or create your own.' => N_('Choose the groups you want to join and/or create your own.'),  
82 - 'New community' => N_('New group'),  
83 - "Tags are important to new users, they'll be able to find your new community more easily." => N_("Tags are important to new users, they'll be able to find your new group more easily."),  
84 - 'Enterprises are disabled when created' => N_('Organizations are disabled when created'),  
85 - })  
86 - end  
87 -  
88 -end  
plugins/comment_group/views/comment_group_plugin_profile/view_comments.rjs
@@ -8,5 +8,5 @@ page.replace_html &quot;comment-count-#{@group_id}&quot;, @comments_count @@ -8,5 +8,5 @@ page.replace_html &quot;comment-count-#{@group_id}&quot;, @comments_count
8 if @no_more_pages 8 if @no_more_pages
9 page.replace_html "comments_list_group_#{@group_id}_more", "" 9 page.replace_html "comments_list_group_#{@group_id}_more", ""
10 else 10 else
11 - page.replace_html "comments_list_group_#{@group_id}_more", link_to_remote(_('More'), :url => { :profile => profile.identifier, :controller => 'comment_group_plugin_profile', :action => 'view_comments', :group_id => @group_id, :article_id => @article_id, :group_comment_page => @group_comment_page + 1}, :loaded => visual_effect(:highlight, "comments_list_group_#{@group_id}"), :method => :post, :complete => "loadCompleted(#{@group_id})") 11 + page.replace_html "comments_list_group_#{@group_id}_more", link_to_remote(_('More'), :url => { :profile => profile.identifier, :controller => 'comment_group_plugin_profile', :action => 'view_comments', :group_id => @group_id, :article_id => @article_id, :group_comment_page => @group_comment_page + 1}, :method => :get)
12 end 12 end
plugins/community_block/test/functional/commmunity_block_plugin_profile_controller_test.rb 0 → 100644
@@ -0,0 +1,83 @@ @@ -0,0 +1,83 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +# Re-raise errors caught by the controller.
  4 +class ProfileController
  5 + append_view_path File.join(File.dirname(__FILE__) + '/../../views')
  6 + def rescue_action(e)
  7 + raise e
  8 + end
  9 +end
  10 +
  11 +class ProfileControllerTest < ActionController::TestCase
  12 +
  13 + def setup
  14 + @user = create_user('testinguser').person
  15 + login_as(@user.identifier)
  16 +
  17 + @community = fast_create(Community, :environment_id => Environment.default)
  18 + @community.add_member @user
  19 + @community.add_admin @user
  20 +
  21 + @environment = @community.environment
  22 + @environment.enabled_plugins = ['CommunityBlock']
  23 + @environment.save!
  24 +
  25 + CommunityBlock.delete_all
  26 + @box1 = create(Box, :owner => @community)
  27 + @community.boxes = [@box1]
  28 +
  29 + @block = CommunityBlock.new
  30 + @block.box = @box1
  31 + @block.save!
  32 +
  33 + @community.blocks<<@block
  34 + @community.save!
  35 + end
  36 +
  37 + should 'display community-block' do
  38 + get :index, :profile => @community.identifier
  39 + assert_tag :div, :attributes => {:class => 'community-block-logo'}
  40 + assert_tag :div, :attributes => {:class => 'community-block-info'}
  41 + assert_tag :div, :attributes => {:class => 'community-block-title'}
  42 + assert_tag :div, :attributes => {:class => 'community-block-description'}
  43 + end
  44 +
  45 + should 'display *leave* button when the user is logged in and is a member of the community' do
  46 + get :index, :profile => @community.identifier
  47 + assert_tag :span, :attributes => {:class => 'community-block-button icon-remove'}
  48 + end
  49 +
  50 + should 'display *send email to administrators* button when the user is logged in and is a member of the community' do
  51 + get :index, :profile => @community.identifier
  52 + assert_match /\{&quot;Send an e-mail&quot;:\{&quot;href&quot;:&quot;\/contact\/#{@community.identifier}\/new&quot;\}\}/, @response.body
  53 + end
  54 +
  55 + should 'display *report* button when the user is logged in and is a member of the community' do
  56 + get :index, :profile => @community.identifier
  57 + assert_match /\{&quot;Report abuse&quot;:\{&quot;href&quot;:&quot;\/profile\/#{@community.identifier}\/report_abuse&quot;\}\}/, @response.body
  58 + end
  59 +
  60 + should 'display *join* button when the user is logged in and is not a member of the community' do
  61 + @community.remove_member @user
  62 + get :index, :profile => @community.identifier
  63 + assert_tag :span, :attributes => {:class => 'community-block-button icon-add'}
  64 + end
  65 +
  66 + should 'display *control panel* link option when the user is logged in and is community admin' do
  67 + get :index, :profile => @community.identifier
  68 + assert_match /\{&quot;Control panel&quot;:\{&quot;href&quot;:&quot;\/myprofile\/#{@community.identifier}&quot;\}\}/, @response.body
  69 + end
  70 +
  71 + should 'display *join* button when the user is not logged in' do
  72 + logout
  73 + get :index, :profile => @community.identifier
  74 + assert_tag :span, :attributes => {:class => 'community-block-button icon-add'}
  75 + end
  76 +
  77 + should 'not display *arrow* button when the user is not logged in' do
  78 + logout
  79 + get :index, :profile => @community.identifier
  80 + assert_no_tag :span, :attributes => {:class => 'community-block-button icon-arrow'}
  81 + end
  82 +
  83 +end
plugins/community_block/test/functional/commmunity_block_plugin_profile_design_controller_test.rb
@@ -1,87 +0,0 @@ @@ -1,87 +0,0 @@
1 -require File.dirname(__FILE__) + '/../test_helper'  
2 -  
3 -# Re-raise errors caught by the controller.  
4 -class ProfileController  
5 - append_view_path File.join(File.dirname(__FILE__) + '/../../views')  
6 - def rescue_action(e)  
7 - raise e  
8 - end  
9 -end  
10 -  
11 -class ProfileControllerTest < ActionController::TestCase  
12 -  
13 - def setup  
14 - @controller = ProfileController.new  
15 - @request = ActionController::TestRequest.new  
16 - @response = ActionController::TestResponse.new  
17 -  
18 - @user = create_user('testinguser').person  
19 - login_as(@user.identifier)  
20 -  
21 - @community = fast_create(Community, :environment_id => Environment.default)  
22 - @community.add_member @user  
23 - @community.add_admin @user  
24 -  
25 - @environment = @community.environment  
26 - @environment.enabled_plugins = ['CommunityBlock']  
27 - @environment.save!  
28 -  
29 - CommunityBlock.delete_all  
30 - @box1 = create(Box, :owner => @community)  
31 - @community.boxes = [@box1]  
32 -  
33 - @block = CommunityBlock.new  
34 - @block.box = @box1  
35 - @block.save!  
36 -  
37 - @community.blocks<<@block  
38 - @community.save!  
39 - end  
40 -  
41 - should 'display community-block' do  
42 - get :index, :profile => @community.identifier  
43 - assert_tag :div, :attributes => {:class => 'community-block-logo'}  
44 - assert_tag :div, :attributes => {:class => 'community-block-info'}  
45 - assert_tag :div, :attributes => {:class => 'community-block-title'}  
46 - assert_tag :div, :attributes => {:class => 'community-block-description'}  
47 - end  
48 -  
49 - should 'display *leave* button when the user is logged in and is a member of the community' do  
50 - get :index, :profile => @community.identifier  
51 - assert_tag :span, :attributes => {:class => 'community-block-button icon-remove'}  
52 - end  
53 -  
54 - should 'display *send email to administrators* button when the user is logged in and is a member of the community' do  
55 - get :index, :profile => @community.identifier  
56 - assert_match /\{&quot;Send an e-mail&quot;:\{&quot;href&quot;:&quot;\/contact\/#{@community.identifier}\/new&quot;\}\}/, @response.body  
57 - end  
58 -  
59 - should 'display *report* button when the user is logged in and is a member of the community' do  
60 - get :index, :profile => @community.identifier  
61 - assert_match /\{&quot;Report abuse&quot;:\{&quot;href&quot;:&quot;\/profile\/#{@community.identifier}\/report_abuse&quot;\}\}/, @response.body  
62 - end  
63 -  
64 - should 'display *join* button when the user is logged in and is not a member of the community' do  
65 - @community.remove_member @user  
66 - get :index, :profile => @community.identifier  
67 - assert_tag :span, :attributes => {:class => 'community-block-button icon-add'}  
68 - end  
69 -  
70 - should 'display *control panel* link option when the user is logged in and is community admin' do  
71 - get :index, :profile => @community.identifier  
72 - assert_match /\{&quot;Control panel&quot;:\{&quot;href&quot;:&quot;\/myprofile\/#{@community.identifier}&quot;\}\}/, @response.body  
73 - end  
74 -  
75 - should 'display *join* button when the user is not logged in' do  
76 - logout  
77 - get :index, :profile => @community.identifier  
78 - assert_tag :span, :attributes => {:class => 'community-block-button icon-add'}  
79 - end  
80 -  
81 - should 'not display *arrow* button when the user is not logged in' do  
82 - logout  
83 - get :index, :profile => @community.identifier  
84 - assert_no_tag :span, :attributes => {:class => 'community-block-button icon-arrow'}  
85 - end  
86 -  
87 -end  
plugins/community_block/views/community_block.html.erb
@@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
23 <%= link_to( 23 <%= link_to(
24 content_tag('span','',:class => 'community-block-button icon-arrow'), 24 content_tag('span','',:class => 'community-block-button icon-arrow'),
25 '#', 25 '#',
26 - :onclick => "toggleSubmenu(this,'',#{j links.to_json}); return false;", 26 + :onclick => "toggleSubmenu(this,'',#{CGI::escapeHTML(links.to_json)}); return false;",
27 :class => 'simplemenu-trigger') %> 27 :class => 'simplemenu-trigger') %>
28 28
29 <% end %> 29 <% end %>
@@ -32,11 +32,11 @@ @@ -32,11 +32,11 @@
32 <% if profile.members.include?(user) || profile.already_request_membership?(user) %> 32 <% if profile.members.include?(user) || profile.already_request_membership?(user) %>
33 <%= link_to( 33 <%= link_to(
34 content_tag('span', '', :class => 'community-block-button icon-remove'), 34 content_tag('span', '', :class => 'community-block-button icon-remove'),
35 - profile.leave_url) %> 35 + profile.leave_url, :class => 'join-community') %>
36 <% else %> 36 <% else %>
37 <%= link_to( 37 <%= link_to(
38 content_tag('span', '', :class => 'community-block-button icon-add'), 38 content_tag('span', '', :class => 'community-block-button icon-add'),
39 - profile.join_url) %> 39 + profile.join_url, :class => 'join-community') %>
40 <% end %> 40 <% end %>
41 <% else %> 41 <% else %>
42 <%= link_to( 42 <%= link_to(
plugins/community_track/test/functional/community_track_plugin_content_viewer_controller_test.rb
@@ -101,9 +101,6 @@ class ContentViewerControllerTest &lt; ActionController::TestCase @@ -101,9 +101,6 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
101 should 'render tracks in track list block' do 101 should 'render tracks in track list block' do
102 @block = CommunityTrackPlugin::TrackListBlock.create!(:box => @profile.boxes.last) 102 @block = CommunityTrackPlugin::TrackListBlock.create!(:box => @profile.boxes.last)
103 get :view_page, @step.url 103 get :view_page, @step.url
104 - file = File.open('result.html', 'w+')  
105 - file.write(@response.body)  
106 - file.close  
107 assert_tag :tag => 'div', :attributes => { :class => "item category_#{@track.category_name}" }, :descendant => { :tag => 'div', :attributes => { :class => 'steps' }, :descendant => { :tag => 'span', :attributes => { :class => "step #{@block.status_class(@step)}" } } } 104 assert_tag :tag => 'div', :attributes => { :class => "item category_#{@track.category_name}" }, :descendant => { :tag => 'div', :attributes => { :class => 'steps' }, :descendant => { :tag => 'span', :attributes => { :class => "step #{@block.status_class(@step)}" } } }
108 end 105 end
109 106
plugins/community_track/test/unit/community_track_plugin_test.rb
@@ -6,10 +6,13 @@ class CommunityTrackPluginTest &lt; ActiveSupport::TestCase @@ -6,10 +6,13 @@ class CommunityTrackPluginTest &lt; ActiveSupport::TestCase
6 @plugin = CommunityTrackPlugin.new 6 @plugin = CommunityTrackPlugin.new
7 @profile = fast_create(Community) 7 @profile = fast_create(Community)
8 @params = {} 8 @params = {}
9 - @plugin.stubs(:context).returns(self) 9 + @context = mock
  10 + @context.stubs(:profile).returns(@profile)
  11 + @context.stubs(:params).returns(@params)
  12 + @plugin.stubs(:context).returns(@context)
10 end 13 end
11 14
12 - attr_reader :profile, :params 15 + attr_reader :profile, :params, :context
13 16
14 should 'has name' do 17 should 'has name' do
15 assert CommunityTrackPlugin.plugin_name 18 assert CommunityTrackPlugin.plugin_name
@@ -28,37 +31,37 @@ class CommunityTrackPluginTest &lt; ActiveSupport::TestCase @@ -28,37 +31,37 @@ class CommunityTrackPluginTest &lt; ActiveSupport::TestCase
28 end 31 end
29 32
30 should 'do not return Track as a content type if profile is not a community' do 33 should 'do not return Track as a content type if profile is not a community' do
31 - @profile = Organization.new 34 + context.stubs(:profile).returns(Organization.new)
32 assert_not_includes @plugin.content_types, CommunityTrackPlugin::Track 35 assert_not_includes @plugin.content_types, CommunityTrackPlugin::Track
33 end 36 end
34 37
35 should 'do not return Track as a content type if there is a parent' do 38 should 'do not return Track as a content type if there is a parent' do
36 - parent = fast_create(Blog, :profile_id => @profile.id)  
37 - @params[:parent_id] = parent.id 39 + parent = fast_create(Blog, :profile_id => profile.id)
  40 + params[:parent_id] = parent.id
38 assert_not_includes @plugin.content_types, CommunityTrackPlugin::Track 41 assert_not_includes @plugin.content_types, CommunityTrackPlugin::Track
39 end 42 end
40 43
41 should 'return Step as a content type if parent is a Track' do 44 should 'return Step as a content type if parent is a Track' do
42 - parent = fast_create(CommunityTrackPlugin::Track, :profile_id => @profile.id)  
43 - @params[:parent_id] = parent.id 45 + parent = fast_create(CommunityTrackPlugin::Track, :profile_id => profile.id)
  46 + params[:parent_id] = parent.id
44 assert_includes @plugin.content_types, CommunityTrackPlugin::Step 47 assert_includes @plugin.content_types, CommunityTrackPlugin::Step
45 end 48 end
46 49
47 should 'do not return Step as a content type if parent is not a Track' do 50 should 'do not return Step as a content type if parent is not a Track' do
48 - parent = fast_create(Blog, :profile_id => @profile.id)  
49 - @params[:parent_id] = parent.id 51 + parent = fast_create(Blog, :profile_id => profile.id)
  52 + params[:parent_id] = parent.id
50 assert_not_includes @plugin.content_types, CommunityTrackPlugin::Step 53 assert_not_includes @plugin.content_types, CommunityTrackPlugin::Step
51 end 54 end
52 55
53 should 'return Track and Step as a content type if context has no params' do 56 should 'return Track and Step as a content type if context has no params' do
54 - parent = fast_create(Blog, :profile_id => @profile.id)  
55 - expects(:respond_to?).with(:params).returns(false) 57 + parent = fast_create(Blog, :profile_id => profile.id)
  58 + context.expects(:respond_to?).with(:params).returns(false)
56 assert_equivalent [CommunityTrackPlugin::Step, CommunityTrackPlugin::Track], @plugin.content_types 59 assert_equivalent [CommunityTrackPlugin::Step, CommunityTrackPlugin::Track], @plugin.content_types
57 end 60 end
58 61
59 should 'return Track and Step as a content type if params is nil' do 62 should 'return Track and Step as a content type if params is nil' do
60 - parent = fast_create(Blog, :profile_id => @profile.id)  
61 - @params = nil 63 + parent = fast_create(Blog, :profile_id => profile.id)
  64 + context.stubs(:params).returns(nil)
62 assert_equivalent [CommunityTrackPlugin::Step, CommunityTrackPlugin::Track], @plugin.content_types 65 assert_equivalent [CommunityTrackPlugin::Step, CommunityTrackPlugin::Track], @plugin.content_types
63 end 66 end
64 67
plugins/custom_forms/controllers/custom_forms_plugin_myprofile_controller.rb
@@ -23,7 +23,7 @@ class CustomFormsPluginMyprofileController &lt; MyProfileController @@ -23,7 +23,7 @@ class CustomFormsPluginMyprofileController &lt; MyProfileController
23 23
24 respond_to do |format| 24 respond_to do |format|
25 if @form.save 25 if @form.save
26 - flash[:notice] = _("Custom form #{@form.name} was successfully created.") 26 + flash[:notice] = _("Custom form %s was successfully created.") % @form.name
27 format.html { redirect_to(:action=>'index') } 27 format.html { redirect_to(:action=>'index') }
28 else 28 else
29 format.html { render :action => 'new' } 29 format.html { render :action => 'new' }
@@ -43,7 +43,7 @@ class CustomFormsPluginMyprofileController &lt; MyProfileController @@ -43,7 +43,7 @@ class CustomFormsPluginMyprofileController &lt; MyProfileController
43 43
44 respond_to do |format| 44 respond_to do |format|
45 if @form.save 45 if @form.save
46 - flash[:notice] = _("Custom form #{@form.name} was successfully updated.") 46 + flash[:notice] = _("Custom form %s was successfully updated.") % @form.name
47 format.html { redirect_to(:action=>'index') } 47 format.html { redirect_to(:action=>'index') }
48 else 48 else
49 session['notice'] = _('Form could not be updated') 49 session['notice'] = _('Form could not be updated')
plugins/custom_forms/test/functional/custom_forms_plugin_myprofile_controller_test.rb
@@ -226,7 +226,7 @@ class CustomFormsPluginMyprofileControllerTest &lt; ActionController::TestCase @@ -226,7 +226,7 @@ class CustomFormsPluginMyprofileControllerTest &lt; ActionController::TestCase
226 end 226 end
227 227
228 should 'list pending submissions for a form' do 228 should 'list pending submissions for a form' do
229 - person = fast_create(Person) 229 + person = create_user('john').person
230 form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software', :for_admission => true) 230 form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software', :for_admission => true)
231 task = CustomFormsPlugin::AdmissionSurvey.create!(:form_id => form.id, :target => person, :requestor => profile) 231 task = CustomFormsPlugin::AdmissionSurvey.create!(:form_id => form.id, :target => person, :requestor => profile)
232 232
plugins/custom_forms/test/unit/custom_forms_plugin/admission_survey_test.rb
@@ -3,7 +3,7 @@ require File.dirname(__FILE__) + &#39;/../../../../../test/test_helper&#39; @@ -3,7 +3,7 @@ require File.dirname(__FILE__) + &#39;/../../../../../test/test_helper&#39;
3 class CustomFormsPlugin::AdmissionSurveyTest < ActiveSupport::TestCase 3 class CustomFormsPlugin::AdmissionSurveyTest < ActiveSupport::TestCase
4 should 'add member to community on perform' do 4 should 'add member to community on perform' do
5 profile = fast_create(Community) 5 profile = fast_create(Community)
6 - person = fast_create(Person) 6 + person = create_user('john').person
7 form = CustomFormsPlugin::Form.create!(:name => 'Simple Form', :profile => profile) 7 form = CustomFormsPlugin::Form.create!(:name => 'Simple Form', :profile => profile)
8 task = CustomFormsPlugin::AdmissionSurvey.create!(:form_id => form.id, :target => person, :requestor => profile) 8 task = CustomFormsPlugin::AdmissionSurvey.create!(:form_id => form.id, :target => person, :requestor => profile)
9 9
plugins/custom_forms/test/unit/custom_forms_plugin/form_test.rb
@@ -244,7 +244,7 @@ class CustomFormsPlugin::FormTest &lt; ActiveSupport::TestCase @@ -244,7 +244,7 @@ class CustomFormsPlugin::FormTest &lt; ActiveSupport::TestCase
244 244
245 should 'cancel survey tasks after removing a form' do 245 should 'cancel survey tasks after removing a form' do
246 profile = fast_create(Profile) 246 profile = fast_create(Profile)
247 - person = fast_create(Person) 247 + person = create_user('john').person
248 248
249 form1 = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => profile) 249 form1 = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => profile)
250 form2 = CustomFormsPlugin::Form.create!(:name => 'Operation System', :profile => profile) 250 form2 = CustomFormsPlugin::Form.create!(:name => 'Operation System', :profile => profile)
plugins/custom_forms/test/unit/custom_forms_plugin/membership_survey_test.rb
@@ -13,7 +13,7 @@ class CustomFormsPlugin::MembershipSurveyTest &lt; ActiveSupport::TestCase @@ -13,7 +13,7 @@ class CustomFormsPlugin::MembershipSurveyTest &lt; ActiveSupport::TestCase
13 13
14 should 'create submission with answers on perform' do 14 should 'create submission with answers on perform' do
15 profile = fast_create(Profile) 15 profile = fast_create(Profile)
16 - person = fast_create(Person) 16 + person = create_user('john').person
17 form = CustomFormsPlugin::Form.create!(:name => 'Simple Form', :profile => profile) 17 form = CustomFormsPlugin::Form.create!(:name => 'Simple Form', :profile => profile)
18 field = CustomFormsPlugin::Field.create!(:name => 'Name', :form => form) 18 field = CustomFormsPlugin::Field.create!(:name => 'Name', :form => form)
19 task = CustomFormsPlugin::MembershipSurvey.create!(:form_id => form.id, :submission => {field.id.to_s => 'Jack'}, :target => person, :requestor => profile) 19 task = CustomFormsPlugin::MembershipSurvey.create!(:form_id => form.id, :submission => {field.id.to_s => 'Jack'}, :target => person, :requestor => profile)
@@ -31,7 +31,7 @@ class CustomFormsPlugin::MembershipSurveyTest &lt; ActiveSupport::TestCase @@ -31,7 +31,7 @@ class CustomFormsPlugin::MembershipSurveyTest &lt; ActiveSupport::TestCase
31 31
32 should 'have a scope that retrieves all tasks requested by profile' do 32 should 'have a scope that retrieves all tasks requested by profile' do
33 profile = fast_create(Profile) 33 profile = fast_create(Profile)
34 - person = fast_create(Person) 34 + person = create_user('john').person
35 form = CustomFormsPlugin::Form.create!(:name => 'Simple Form', :profile => profile) 35 form = CustomFormsPlugin::Form.create!(:name => 'Simple Form', :profile => profile)
36 task1 = CustomFormsPlugin::MembershipSurvey.create!(:form_id => form.id, :target => person, :requestor => profile) 36 task1 = CustomFormsPlugin::MembershipSurvey.create!(:form_id => form.id, :target => person, :requestor => profile)
37 task2 = CustomFormsPlugin::MembershipSurvey.create!(:form_id => form.id, :target => person, :requestor => fast_create(Profile)) 37 task2 = CustomFormsPlugin::MembershipSurvey.create!(:form_id => form.id, :target => person, :requestor => fast_create(Profile))
plugins/custom_forms/test/unit/ext/role_assingment_test.rb
@@ -5,7 +5,7 @@ class RoleAssignmentsTest &lt; ActiveSupport::TestCase @@ -5,7 +5,7 @@ class RoleAssignmentsTest &lt; ActiveSupport::TestCase
5 environment = Environment.default 5 environment = Environment.default
6 environment.enable_plugin(CustomFormsPlugin) 6 environment.enable_plugin(CustomFormsPlugin)
7 organization = fast_create(Organization) 7 organization = fast_create(Organization)
8 - person = fast_create(Person) 8 + person = create_user('john').person
9 f1 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 1', :on_membership => true) 9 f1 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 1', :on_membership => true)
10 f2 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 2', :on_membership => true) 10 f2 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 2', :on_membership => true)
11 f3 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 3', :on_membership => false) 11 f3 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 3', :on_membership => false)
@@ -19,7 +19,7 @@ class RoleAssignmentsTest &lt; ActiveSupport::TestCase @@ -19,7 +19,7 @@ class RoleAssignmentsTest &lt; ActiveSupport::TestCase
19 environment = Environment.default 19 environment = Environment.default
20 environment.enable_plugin(CustomFormsPlugin) 20 environment.enable_plugin(CustomFormsPlugin)
21 organization = fast_create(Organization) 21 organization = fast_create(Organization)
22 - person = fast_create(Person) 22 + person = create_user('john').person
23 form = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form', :on_membership => true, :access => 'associated') 23 form = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form', :on_membership => true, :access => 'associated')
24 24
25 assert_difference 'CustomFormsPlugin::MembershipSurvey.count', 1 do 25 assert_difference 'CustomFormsPlugin::MembershipSurvey.count', 1 do
@@ -31,7 +31,7 @@ class RoleAssignmentsTest &lt; ActiveSupport::TestCase @@ -31,7 +31,7 @@ class RoleAssignmentsTest &lt; ActiveSupport::TestCase
31 environment = Environment.default 31 environment = Environment.default
32 environment.enable_plugin(CustomFormsPlugin) 32 environment.enable_plugin(CustomFormsPlugin)
33 organization = fast_create(Organization) 33 organization = fast_create(Organization)
34 - person = fast_create(Person) 34 + person = create_user('john').person
35 form1 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 1', :on_membership => true) 35 form1 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 1', :on_membership => true)
36 organization.add_member(person) 36 organization.add_member(person)
37 37
@@ -59,7 +59,7 @@ class RoleAssignmentsTest &lt; ActiveSupport::TestCase @@ -59,7 +59,7 @@ class RoleAssignmentsTest &lt; ActiveSupport::TestCase
59 environment = Environment.default 59 environment = Environment.default
60 environment.enable_plugin(CustomFormsPlugin) 60 environment.enable_plugin(CustomFormsPlugin)
61 organization = fast_create(Organization) 61 organization = fast_create(Organization)
62 - person = fast_create(Person) 62 + person = create_user('john').person
63 f1 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 1', :for_admission => true) 63 f1 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 1', :for_admission => true)
64 f2 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 2', :for_admission => true) 64 f2 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 2', :for_admission => true)
65 f3 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 3', :for_admission => false) 65 f3 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 3', :for_admission => false)
plugins/lattes_curriculum/README.md 0 → 100644
@@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
  1 +README - Lattes Curriculum (LattesCurriculum Plugin)
  2 +================================
  3 +
  4 +Lattes Curriculum is a plugin that allow users to show their academic informations in the wall, extracted from CNPQ's lattes plataform
  5 +
  6 +INSTALL
  7 +=======
  8 +
  9 +Enable Plugin
  10 +-------------
  11 +
  12 +Also, you need to enable LattesCurriculum Plugin on your Noosfero:
  13 +
  14 +cd <your_noosfero_dir>
  15 +./script/noosfero-plugins enable lattes_curriculum
  16 +
  17 +Active Plugin
  18 +-------------
  19 +
  20 +As a Noosfero administrator user, go to administrator panel:
  21 +
  22 +- Click on "Enable/disable plugins" option
  23 +- Click on "LattesCurriculumPlugin" check-box
  24 +
  25 +Running LattesCurriculum tests
  26 +--------------------
  27 +
  28 +$ rake test:noosfero_plugins:lattes_curriculum
  29 +
  30 +LICENSE
  31 +=======
  32 +
  33 +Copyright (c) The Author developers.
  34 +
  35 +See Noosfero license.
  36 +
  37 +
  38 +AUTHORS
  39 +=======
  40 +
  41 +Jose Pedro (1jpsneto at gmail.com)
  42 +Thiago Kairala (thiagor.kairala at gmail.com)
  43 +Ana Paula Vargas (anapaulavnoronha at gmail.com)
  44 +Leandro Veloso (leandrovelosorodrigues at gmail.com)
  45 +Arthur Del Esposte (arthurmde at gmail.com)
0 \ No newline at end of file 46 \ No newline at end of file
plugins/lattes_curriculum/db/migrate/20140814210103_create_academic_infos.rb 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +class CreateAcademicInfos < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :academic_infos do |t|
  4 + t.references :person
  5 + t.column :lattes_url, :string
  6 + end
  7 + end
  8 +
  9 + def self.down
  10 + drop_table :academic_infos
  11 + end
  12 +end
plugins/lattes_curriculum/features/lattes_curriculum.feature 0 → 100644
@@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
  1 +Feature: import lattes information
  2 + As an user
  3 + I want to inform my lattes url address
  4 + So that I can import my academic informations automatically
  5 +
  6 + Background:
  7 + Given "LattesCurriculumPlugin" plugin is enabled
  8 + And I am logged in as admin
  9 + And I go to /admin/plugins
  10 + And I check "Lattes Curriculum Plugin"
  11 + And I press "Save changes"
  12 + And I go to /admin/features/manage_fields
  13 + Given I follow "Person's fields"
  14 + And I check "person_fields_lattes_url_active"
  15 + And I check "person_fields_lattes_url_signup"
  16 + And I press "save_person_fields"
  17 +
  18 + Scenario: Don't accept edit the profile with invalid lattes url
  19 + Given I am on admin_user's control panel
  20 + When I follow "Edit Profile"
  21 + And I fill in "Lattes URL" with "http://youtube.com.br/"
  22 + And I press "Save"
  23 + Then I should see "Academic info lattes url is invalid"
  24 +
  25 + Scenario: Import lattes informations
  26 + Given I am on admin_user's control panel
  27 + And the field lattes_url is public for all users
  28 + When I follow "Edit Profile"
  29 + And I fill in "Lattes URL" with "http://lattes.cnpq.br/2864976228727880"
  30 + And I press "Save"
  31 + And I go to /profile/admin_user#lattes_tab
  32 + Then I should see "Endereço para acessar este CV: http://lattes.cnpq.br/2864976228727880"
  33 +
  34 + Scenario: Don't show lattes informations for blank lattes urls
  35 + Given I am on admin_user's control panel
  36 + And the field lattes_url is public for all users
  37 + When I follow "Edit Profile"
  38 + And I press "Save"
  39 + And I go to /profile/admin_user
  40 + Then I should not see "Lattes"
  41 +
  42 + Scenario: Inform problem if the informed lattes doesn't exist
  43 + Given I am on admin_user's control panel
  44 + And the field lattes_url is public for all users
  45 + When I follow "Edit Profile"
  46 + And I fill in "Lattes URL" with "http://lattes.cnpq.br/123456"
  47 + And I press "Save"
  48 + And I go to /profile/admin_user#lattes_tab
  49 + Then I should see "Lattes not found. Please, make sure the informed URL is correct."
0 \ No newline at end of file 50 \ No newline at end of file
plugins/lattes_curriculum/lib/academic_info.rb 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +class AcademicInfo < ActiveRecord::Base
  2 +
  3 + belongs_to :person
  4 +
  5 + attr_accessible :lattes_url
  6 + validate :lattes_url_validate?
  7 +
  8 + def lattes_url_validate?
  9 + unless AcademicInfo.matches?(self.lattes_url)
  10 + self.errors.add(:lattes_url, _(" is invalid."))
  11 + end
  12 + end
  13 +
  14 + def self.matches?(info)
  15 + lattes = nil
  16 + if info.class == String
  17 + lattes = info
  18 + elsif info.class == Hash
  19 + lattes = info[:lattes_url]
  20 + end
  21 + return lattes.blank? || lattes =~ /^http:\/\/lattes.cnpq.br\/\d+$/
  22 + end
  23 +end
plugins/lattes_curriculum/lib/ext/person.rb 0 → 100755
@@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
  1 +require_dependency 'person'
  2 +
  3 +class Person
  4 +
  5 + attr_accessible :lattes_url, :academic_info_attributes
  6 +
  7 + has_one :academic_info
  8 +
  9 + after_destroy do |person|
  10 + if !person.environment.nil? &&
  11 +person.environment.plugin_enabled?(LattesCurriculumPlugin) &&
  12 +!person.academic_info.nil?
  13 + person.academic_info.destroy
  14 + end
  15 + end
  16 +
  17 + accepts_nested_attributes_for :academic_info
  18 +
  19 + def lattes_url
  20 + if self.environment && self.environment.plugin_enabled?(LattesCurriculumPlugin)
  21 + self.academic_info.nil? ? nil : self.academic_info.lattes_url
  22 + end
  23 + end
  24 +
  25 + def lattes_url= value
  26 + if self.environment && self.environment.plugin_enabled?(LattesCurriculumPlugin)
  27 + self.academic_info.lattes_url = value unless self.academic_info.nil?
  28 + end
  29 + end
  30 +
  31 + FIELDS << "lattes_url"
  32 +end
plugins/lattes_curriculum/lib/html_parser.rb 0 → 100755
@@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
  1 +require 'rubygems'
  2 +require 'nokogiri'
  3 +require 'open-uri'
  4 +
  5 +Encoding.default_external = Encoding::UTF_8
  6 +Encoding.default_internal = Encoding::UTF_8
  7 +
  8 +class Html_parser
  9 + def get_html(lattes_link = "")
  10 + begin
  11 + page = Nokogiri::HTML(open(lattes_link), nil, "UTF-8")
  12 + page = page.css(".main-content").to_s()
  13 + page = remove_class_tooltip(page)
  14 + page = remove_img(page)
  15 + page = remove_select(page)
  16 + page = remove_footer(page)
  17 + page = remove_further_informations(page)
  18 + rescue OpenURI::HTTPError => e
  19 + page = _("Lattes not found. Please, make sure the informed URL is correct.")
  20 + rescue Timeout::Error => e
  21 + page = _("Lattes Platform is unreachable. Please, try it later.")
  22 + end
  23 + end
  24 +
  25 + def remove_class_tooltip(page = "")
  26 + while page.include? 'class="tooltip"' do
  27 + page['class="tooltip"'] = 'class="link_not_to_mark"'
  28 + end
  29 +
  30 + return page
  31 + end
  32 +
  33 + def remove_img(page = "")
  34 + fist_part_to_keep, *rest = page.split('<img')
  35 + second_part = rest.join(" ")
  36 + part_to_throw_away, *after_img = second_part.split('>',2)
  37 + page = fist_part_to_keep + after_img.join(" ")
  38 + end
  39 +
  40 + def remove_select(page = "")
  41 + while page.include? '<label' do
  42 + first_part_to_keep, *rest = page.split('<label')
  43 + second_part = rest.join(" ")
  44 + part_to_throw_away, *after_img = second_part.split('</select>')
  45 + page = first_part_to_keep + after_img.join(" ")
  46 + end
  47 +
  48 + return page
  49 + end
  50 +
  51 + def remove_footer(page = "")
  52 + first_part_to_keep, *rest = page.split('<div class="rodape-cv">')
  53 + second_part = rest.join(" ")
  54 + part_to_throw_away, *after_img = second_part.split('Imprimir Curr&iacute;culo</a>')
  55 + page = first_part_to_keep + after_img.join(" ")
  56 + end
  57 +
  58 + def remove_further_informations(page = "")
  59 + first_part_to_keep, *rest = page.split('<a name="OutrasI')
  60 + second_part = rest.join(" ")
  61 + part_to_throw_away, *after_img = second_part.split('</div>',2)
  62 + page = first_part_to_keep + after_img.join(" ")
  63 + end
  64 +end
plugins/lattes_curriculum/lib/lattes_curriculum_plugin.rb 0 → 100755
@@ -0,0 +1,139 @@ @@ -0,0 +1,139 @@
  1 +class LattesCurriculumPlugin < Noosfero::Plugin
  2 +
  3 + def self.plugin_name
  4 + "Lattes Curriculum Plugin"
  5 + end
  6 +
  7 + def self.plugin_description
  8 + _("A plugin that imports the lattes curriculum into the users profiles")
  9 + end
  10 +
  11 + def js_files
  12 + ["singup_complement.js"]
  13 + end
  14 +
  15 + def stylesheet?
  16 + true
  17 + end
  18 +
  19 + def extra_optional_fields
  20 + fields = []
  21 +
  22 + lattes_url = {
  23 + :name => 'lattes_url',
  24 + :label => 'Lattes URL',
  25 + :object_name => :academic_infos,
  26 + :method => :lattes_url,
  27 + :value => context.profile.nil? ? "" : context.profile.academic_info.lattes_url
  28 + }
  29 +
  30 + fields << lattes_url
  31 +
  32 + return fields
  33 + end
  34 +
  35 + def profile_tabs
  36 + if show_lattes_tab?
  37 + href = context.profile.academic_info.lattes_url
  38 + html_parser = Html_parser.new
  39 + {
  40 + :title => _("Lattes"),
  41 + :id => 'lattes_tab',
  42 + :content => lambda{html_parser.get_html(href)},
  43 + :start => false
  44 + }
  45 + end
  46 + end
  47 +
  48 + def profile_editor_transaction_extras
  49 + if context.profile.person?
  50 + if context.params.has_key?(:academic_infos)
  51 + academic_info_transaction
  52 + end
  53 + end
  54 + end
  55 +
  56 + def profile_editor_controller_filters
  57 + validate_lattes_url_block = proc do
  58 + if request.post?
  59 + if !params[:academic_infos].blank?
  60 + @profile_data = profile
  61 +
  62 + academic_infos = {"academic_info_attributes" => params[:academic_infos]}
  63 +
  64 + params_profile_data = params[:profile_data]
  65 + params_profile_data = params_profile_data.merge(academic_infos)
  66 +
  67 + @profile_data.attributes = params_profile_data
  68 + @profile_data.valid?
  69 +
  70 + @possible_domains = profile.possible_domains
  71 +
  72 + unless AcademicInfo.matches?(params[:academic_infos])
  73 + @profile_data.errors.add(:lattes_url, _(' Invalid lattes url'))
  74 + render :action => :edit, :profile => profile.identifier
  75 + end
  76 + end
  77 + end
  78 + end
  79 +
  80 + create_academic_info_block = proc do
  81 + if profile && profile.person? && profile.academic_info.nil?
  82 + profile.academic_info = AcademicInfo.new
  83 + end
  84 + end
  85 +
  86 + [{:type => 'before_filter',
  87 + :method_name => 'validate_lattes_url',
  88 + :options => {:only => 'edit'},
  89 + :block => validate_lattes_url_block },
  90 + {:type => 'before_filter',
  91 + :method_name => 'create_academic_info',
  92 + :options => {:only => 'edit'},
  93 + :block => create_academic_info_block }]
  94 + end
  95 +
  96 + def account_controller_filters
  97 + validate_lattes_url_block = proc do
  98 + if request.post?
  99 + params[:profile_data] ||= {}
  100 + params[:profile_data][:academic_info_attributes] = params[:academic_infos]
  101 +
  102 + if !params[:academic_infos].blank? && !AcademicInfo.matches?(params[:academic_infos])
  103 + @person = Person.new(params[:profile_data])
  104 + @person.environment = environment
  105 + @user = User.new(params[:user])
  106 + @person.errors.add(:lattes_url, _(' Invalid lattes url'))
  107 + render :action => :signup
  108 + end
  109 + end
  110 + end
  111 +
  112 + create_academic_info_block = proc do
  113 + if profile && profile.person? && profile.academic_info.nil?
  114 + profile.academic_info = AcademicInfo.new
  115 + end
  116 + end
  117 +
  118 + [{:type => 'before_filter',
  119 + :method_name => 'validate_lattes_url',
  120 + :options => {:only => 'signup'},
  121 + :block => validate_lattes_url_block },
  122 + {:type => 'before_filter',
  123 + :method_name => 'create_academic_info',
  124 + :options => {:only => 'edit'},
  125 + :block => create_academic_info_block }]
  126 + end
  127 +
  128 + protected
  129 +
  130 + def academic_info_transaction
  131 + AcademicInfo.transaction do
  132 + context.profile.academic_info.update_attributes!(context.params[:academic_infos])
  133 + end
  134 + end
  135 +
  136 + def show_lattes_tab?
  137 + return context.profile.person? && !context.profile.academic_info.nil? && !context.profile.academic_info.lattes_url.blank? && context.profile.public_fields.include?("lattes_url")
  138 + end
  139 +end
plugins/lattes_curriculum/public/images/grey-bg.png 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +8950 4e47 0d0a 1a0a 0000 000d 4948 4452
  2 +0000 0009 0000 0027 0806 0000 00dd 3762
  3 +5600 0000 0173 5247 4200 aece 1ce9 0000
  4 +0006 624b 4744 00ff 00ff 00ff a0bd a793
  5 +0000 0009 7048 5973 0000 0dd7 0000 0dd7
  6 +0142 289b 7800 0000 0774 494d 4507 dc05
  7 +040f 1a01 22dc e3a3 0000 003b 4944 4154
  8 +38cb 635c 30bd f53f 0301 c0c4 4004 188c
  9 +8a58 18fe 13a3 8808 552c ff87 6810 101b
  10 +4eff 87af efa8 1704 c33e 1550 29b7 0c4a
  11 +dffd 1fa2 b905 009c 4210 1067 9fec 9d00
  12 +0000 0049 454e 44ae 4260 82
0 \ No newline at end of file 13 \ No newline at end of file
plugins/lattes_curriculum/public/singup_complement.js 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +jQuery(function($){
  2 + $(document).ready(function(){
  3 + $('#lattes_id_field').blur(function(){
  4 + var value = this.value
  5 + })
  6 +
  7 + $('#lattes_id_field').focus(function(){
  8 + $('#lattes-id-balloon').fadeIn('slow')
  9 + })
  10 +
  11 + $('#lattes_id_field').blur(function(){
  12 + $('#lattes-id-balloon').fadeOut('slow')
  13 + })
  14 + })
  15 +})
0 \ No newline at end of file 16 \ No newline at end of file
plugins/lattes_curriculum/public/style.css 0 → 100644
@@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
  1 +#signup-form label[for="lattes_id_field"],
  2 +{
  3 + display: block;
  4 +}
  5 +
  6 +#signup-form small#lattes-id-balloon
  7 +{
  8 + display: none;
  9 + width: 142px;
  10 + height: 69px;
  11 + color: #FFFFFF;
  12 + font-weight: bold;
  13 + font-size: 11px;
  14 + padding: 5px 10px 45px 10px;
  15 + margin: 0;
  16 + line-height: 1.5em;
  17 + background: transparent url(/images/gray-balloon.png) bottom center no-repeat;
  18 + position: absolute;
  19 + z-index: 2;
  20 + right: -150px;
  21 + top: -75px;
  22 +}
  23 +
  24 +#signup-form .formfield.checking {
  25 + border-color: transparent;
  26 + background-image: none;
  27 +}
  28 +
  29 +#signup-form small#lattes-id-balloon
  30 +{
  31 + top: -80px;
  32 +}
  33 +
  34 +#signup-form #signup-lattes-id
  35 +{
  36 + position: relative;
  37 +}
plugins/lattes_curriculum/test/unit/academic_info_test.rb 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +require "test_helper"
  2 +
  3 +class AcademicInfoTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @academic_info = AcademicInfo.new
  7 + end
  8 +
  9 + should 'not ve invalid lattes url' do
  10 + @academic_info.lattes_url = "http://softwarelivre.org/"
  11 + assert !@academic_info.save
  12 + end
  13 +
  14 + should 'accept blank lattes url' do
  15 + @academic_info.lattes_url = ""
  16 + assert @academic_info.save
  17 + end
  18 +
  19 + should 'save with correct lattes url' do
  20 + @academic_info.lattes_url = "http://lattes.cnpq.br/2193972715230641"
  21 + assert @academic_info.save
  22 + end
  23 +end
plugins/lattes_curriculum/test/unit/html_parser_test.rb 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +#!/bin/env ruby
  2 +# encoding: utf-8
  3 +
  4 +require "test_helper"
  5 +
  6 +class HtmlParserTest < ActiveSupport::TestCase
  7 +
  8 + def setup
  9 + @parser = Html_parser.new
  10 + end
  11 +
  12 + should 'be not nil the instance' do
  13 + assert_not_nil @parser
  14 + end
  15 +
  16 + should 'be not nil the return get_html' do
  17 + result = @parser.get_html("http://lattes.cnpq.br/2193972715230641")
  18 + assert result.include?("Endereço para acessar este CV")
  19 + end
  20 +
  21 + should 'inform that lattes was not found' do
  22 + assert_equal "Lattes not found. Please, make sure the informed URL is correct.", @parser.get_html("http://lattes.cnpq.br/123")
  23 + end
  24 +end
plugins/lattes_curriculum/test/unit/lattes_curriculum_test.rb 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +require "test_helper"
  2 +
  3 +class LattesCurriculumPluginTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @plugin = LattesCurriculumPlugin.new
  7 + end
  8 +
  9 + should 'be a noosfero plugin' do
  10 + assert_kind_of Noosfero::Plugin, @plugin
  11 + end
  12 +
  13 + should 'have name' do
  14 + assert_equal 'Lattes Curriculum Plugin', LattesCurriculumPlugin.plugin_name
  15 + end
  16 +
  17 + should 'have description' do
  18 + assert_equal _('A plugin that imports the lattes curriculum into the users profiles'), LattesCurriculumPlugin.plugin_description
  19 + end
  20 +
  21 + should 'have stylesheet' do
  22 + assert @plugin.stylesheet?
  23 + end
  24 +end
plugins/lattes_curriculum/test/unit/person_test.rb 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +require "test_helper"
  2 +
  3 +class PersonTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @environment = Environment.default
  7 + @environment.enable_plugin(LattesCurriculumPlugin)
  8 + end
  9 +
  10 + attr_reader :environment
  11 +
  12 + should 'destroy academic info if person is removed' do
  13 + person = fast_create(Person)
  14 + academic_info = fast_create(AcademicInfo, :person_id => person.id,
  15 +:lattes_url => 'http://lattes.cnpq.br/2193972715230')
  16 +
  17 + assert_difference 'AcademicInfo.count', -1 do
  18 + person.destroy
  19 + end
  20 + end
  21 +
  22 + should 'add lattes_url field to Person' do
  23 + assert_includes Person.fields, 'lattes_url'
  24 + end
  25 +
  26 +end
plugins/people_block/controllers/people_block_plugin_profile_controller.rb
@@ -6,12 +6,13 @@ class PeopleBlockPluginProfileController &lt; ProfileController @@ -6,12 +6,13 @@ class PeopleBlockPluginProfileController &lt; ProfileController
6 if is_cache_expired?(profile.members_cache_key(params)) 6 if is_cache_expired?(profile.members_cache_key(params))
7 unless params[:role_key].blank? 7 unless params[:role_key].blank?
8 role = Role.find_by_key_and_environment_id(params[:role_key], profile.environment) 8 role = Role.find_by_key_and_environment_id(params[:role_key], profile.environment)
9 - @members = profile.members.with_role(role.id).includes(relations_to_include).paginate(:per_page => members_per_page, :page => params[:npage]) 9 + @members = profile.members.with_role(role.id)
10 @members_title = role.name 10 @members_title = role.name
11 else 11 else
12 - @members = profile.members.includes(relations_to_include).paginate(:per_page => members_per_page, :page => params[:npage]) 12 + @members = profile.members
13 @members_title = 'members' 13 @members_title = 'members'
14 end 14 end
  15 + @members = @members.includes(relations_to_include).paginate(:per_page => members_per_page, :page => params[:npage], :total_entries => @members.count)
15 end 16 end
16 render "profile/members" 17 render "profile/members"
17 end 18 end
plugins/people_block/test/unit/friends_block_test.rb
1 require File.dirname(__FILE__) + '/../test_helper' 1 require File.dirname(__FILE__) + '/../test_helper'
2 2
3 -class FriendsBlockTest < ActiveSupport::TestCase 3 +class FriendsBlockTest < ActionView::TestCase
4 4
5 should 'inherit from Block' do 5 should 'inherit from Block' do
6 assert_kind_of Block, FriendsBlock.new 6 assert_kind_of Block, FriendsBlock.new
@@ -8,6 +8,7 @@ class FriendsBlockTest &lt; ActiveSupport::TestCase @@ -8,6 +8,7 @@ class FriendsBlockTest &lt; ActiveSupport::TestCase
8 8
9 9
10 should 'declare its default title' do 10 should 'declare its default title' do
  11 + FriendsBlock.any_instance.expects(:profile_count).returns(0)
11 assert_not_equal Block.new.default_title, FriendsBlock.new.default_title 12 assert_not_equal Block.new.default_title, FriendsBlock.new.default_title
12 end 13 end
13 14
@@ -60,7 +61,7 @@ class FriendsBlockTest &lt; ActiveSupport::TestCase @@ -60,7 +61,7 @@ class FriendsBlockTest &lt; ActiveSupport::TestCase
60 61
61 62
62 should 'prioritize profiles with image by default' do 63 should 'prioritize profiles with image by default' do
63 - assert FriendsBlock.new.prioritize_people_with_image 64 + assert FriendsBlock.new.prioritize_profiles_with_image
64 end 65 end
65 66
66 67
@@ -98,10 +99,10 @@ class FriendsBlockTest &lt; ActiveSupport::TestCase @@ -98,10 +99,10 @@ class FriendsBlockTest &lt; ActiveSupport::TestCase
98 block = FriendsBlock.new 99 block = FriendsBlock.new
99 block.expects(:owner).returns(person1).at_least_once 100 block.expects(:owner).returns(person1).at_least_once
100 101
101 - expects(:_).with('View all').returns('View all')  
102 - expects(:link_to).with('View all', :profile => 'mytestperson', :controller => 'profile', :action => 'friends').returns('link-to-friends')  
103 -  
104 - assert_equal 'link-to-friends', instance_eval(&block.footer) 102 + instance_eval(&block.footer)
  103 + assert_select 'a.view-all' do |elements|
  104 + assert_select '[href=/profile/mytestperson/friends]'
  105 + end
105 end 106 end
106 107
107 108
plugins/people_block/test/unit/members_block_test.rb
1 require File.dirname(__FILE__) + '/../test_helper' 1 require File.dirname(__FILE__) + '/../test_helper'
2 2
3 -class MembersBlockTest < ActiveSupport::TestCase 3 +class MembersBlockTest < ActionView::TestCase
4 4
5 should 'inherit from Block' do 5 should 'inherit from Block' do
6 assert_kind_of Block, MembersBlock.new 6 assert_kind_of Block, MembersBlock.new
@@ -60,7 +60,7 @@ class MembersBlockTest &lt; ActiveSupport::TestCase @@ -60,7 +60,7 @@ class MembersBlockTest &lt; ActiveSupport::TestCase
60 60
61 61
62 should 'prioritize profiles with image by default' do 62 should 'prioritize profiles with image by default' do
63 - assert MembersBlock.new.prioritize_people_with_image 63 + assert MembersBlock.new.prioritize_profiles_with_image
64 end 64 end
65 65
66 66
@@ -145,10 +145,10 @@ class MembersBlockTest &lt; ActiveSupport::TestCase @@ -145,10 +145,10 @@ class MembersBlockTest &lt; ActiveSupport::TestCase
145 block.box = profile.boxes.first 145 block.box = profile.boxes.first
146 block.save! 146 block.save!
147 147
148 - expects(:_).with('View all').returns('View all')  
149 - expects(:link_to).with('View all' , :profile => 'mytestuser', :controller => 'people_block_plugin_profile', :action => 'members', :role_key => block.visible_role).returns('link-to-members')  
150 -  
151 - assert_equal 'link-to-members', instance_eval(&block.footer) 148 + instance_eval(&block.footer)
  149 + assert_select 'a.view-all' do |elements|
  150 + assert_select '[href=/profile/mytestuser/plugin/people_block/members]'
  151 + end
152 end 152 end
153 153
154 should 'provide link to members page with a selected role' do 154 should 'provide link to members page with a selected role' do
@@ -158,10 +158,10 @@ class MembersBlockTest &lt; ActiveSupport::TestCase @@ -158,10 +158,10 @@ class MembersBlockTest &lt; ActiveSupport::TestCase
158 block.visible_role = 'profile_member' 158 block.visible_role = 'profile_member'
159 block.save! 159 block.save!
160 160
161 - expects(:_).with('View all').returns('View all')  
162 - expects(:link_to).with('View all' , :profile => 'mytestuser', :controller => 'people_block_plugin_profile', :action => 'members', :role_key => block.visible_role).returns('link-to-members')  
163 -  
164 - assert_equal 'link-to-members', instance_eval(&block.footer) 161 + instance_eval(&block.footer)
  162 + assert_select 'a.view-all' do |elements|
  163 + assert_select '[href=/profile/mytestuser/plugin/people_block/members?role_key=profile_member]'
  164 + end
165 end 165 end
166 166
167 should 'provide a role to be displayed (and default to nil)' do 167 should 'provide a role to be displayed (and default to nil)' do
plugins/people_block/test/unit/people_block_test.rb
1 require File.dirname(__FILE__) + '/../test_helper' 1 require File.dirname(__FILE__) + '/../test_helper'
2 2
3 -class PeopleBlockTest < ActiveSupport::TestCase 3 +class PeopleBlockTest < ActionView::TestCase
4 4
5 should 'inherit from Block' do 5 should 'inherit from Block' do
6 assert_kind_of Block, PeopleBlock.new 6 assert_kind_of Block, PeopleBlock.new
@@ -106,14 +106,12 @@ class PeopleBlockTest &lt; ActiveSupport::TestCase @@ -106,14 +106,12 @@ class PeopleBlockTest &lt; ActiveSupport::TestCase
106 106
107 should 'link to "all people"' do 107 should 'link to "all people"' do
108 env = fast_create(Environment) 108 env = fast_create(Environment)
109 -  
110 block = PeopleBlock.new 109 block = PeopleBlock.new
111 110
112 - stubs(:_).with('View all').returns('View all')  
113 - stubs(:link_to).returns('link-to-people')  
114 - stubs(:url_for).returns(' ')  
115 -  
116 - assert_equal 'link-to-people', instance_exec(&block.footer) 111 + instance_eval(&block.footer)
  112 + assert_select 'a.view-all' do |elements|
  113 + assert_select '[href=/search/people]'
  114 + end
117 end 115 end
118 116
119 117
plugins/pjax/lib/pjax_plugin.rb 0 → 100644
@@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
  1 +class PjaxPlugin < Noosfero::Plugin
  2 +
  3 + def self.plugin_name
  4 + I18n.t('pjax_plugin.lib.plugin.name')
  5 + end
  6 +
  7 + def self.plugin_description
  8 + I18n.t('pjax_plugin.lib.plugin.description')
  9 + end
  10 +
  11 + def stylesheet?
  12 + true
  13 + end
  14 +
  15 + def js_files
  16 + ['jquery.pjax.js', 'patchwork.js', 'loading-overlay', 'pjax', ].map{ |j| "javascripts/#{j}" }
  17 + end
  18 +
  19 + def head_ending
  20 + #TODO: add pjax meta
  21 + end
  22 +
  23 + def body_beginning
  24 + lambda{ render 'pjax_layouts/load_state_script' }
  25 + end
  26 +
  27 + PjaxCheck = lambda do
  28 + return unless request.headers['X-PJAX']
  29 + # raise makes pjax fallback to a regular request
  30 + raise "Pjax can't be used here" if params[:controller] == 'account'
  31 +
  32 + @pjax = true
  33 + @pjax_loaded_themes = request.headers['X-PJAX-Themes'].to_s.split(',') || []
  34 +
  35 + unless self.respond_to? :get_layout_with_pjax
  36 + self.class.send :define_method, :get_layout_with_pjax do
  37 + if @pjax then 'pjax' else get_layout_without_pjax end
  38 + end
  39 + self.class.alias_method_chain :get_layout, :pjax
  40 + end
  41 + end
  42 +
  43 + def application_controller_filters
  44 + [{
  45 + :type => 'before_filter', :method_name => 'pjax_check',
  46 + :options => {}, :block => PjaxCheck,
  47 + }]
  48 + end
  49 +
  50 + protected
  51 +
  52 +end
plugins/pjax/locales/en.yml 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +
  2 +"en": &en-US
  3 + pjax_plugin:
  4 + lib:
  5 + plugin:
  6 + name: "pjax plugin"
  7 + description: "Use pjax for page's links"
  8 +
  9 +'en_US':
  10 + <<: *en-US
  11 +'en-US':
  12 + <<: *en-US
plugins/pjax/locales/pt.yml 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +
  2 +"pt": &pt-BR
  3 + pjax_plugin:
  4 + lib:
  5 + plugin:
  6 + name: "pjax plugin"
  7 + description: "Usa o pjax para os links da página"
  8 +
  9 +'pt_BR':
  10 + <<: *pt-BR
  11 +'pt-BR':
  12 + <<: *pt-BR
  13 +
plugins/pjax/public/images/loading-gears.gif 0 → 100644

617 KB

plugins/pjax/public/images/loading-overlay.gif 0 → 100644

3.4 KB

plugins/pjax/public/javascripts/jquery.pjax.js 0 → 100644
@@ -0,0 +1,838 @@ @@ -0,0 +1,838 @@
  1 +// jquery.pjax.js
  2 +// copyright chris wanstrath
  3 +// https://github.com/defunkt/jquery-pjax
  4 +
  5 +(function($){
  6 +
  7 +// When called on a container with a selector, fetches the href with
  8 +// ajax into the container or with the data-pjax attribute on the link
  9 +// itself.
  10 +//
  11 +// Tries to make sure the back button and ctrl+click work the way
  12 +// you'd expect.
  13 +//
  14 +// Exported as $.fn.pjax
  15 +//
  16 +// Accepts a jQuery ajax options object that may include these
  17 +// pjax specific options:
  18 +//
  19 +//
  20 +// container - Where to stick the response body. Usually a String selector.
  21 +// $(container).html(xhr.responseBody)
  22 +// (default: current jquery context)
  23 +// push - Whether to pushState the URL. Defaults to true (of course).
  24 +// replace - Want to use replaceState instead? That's cool.
  25 +//
  26 +// For convenience the second parameter can be either the container or
  27 +// the options object.
  28 +//
  29 +// Returns the jQuery object
  30 +function fnPjax(selector, container, options) {
  31 + var context = this
  32 + return this.on('click.pjax', selector, function(event) {
  33 + var opts = $.extend({}, optionsFor(container, options))
  34 + if (!opts.container)
  35 + opts.container = $(this).attr('data-pjax') || context
  36 + handleClick(event, opts)
  37 + })
  38 +}
  39 +
  40 +// Public: pjax on click handler
  41 +//
  42 +// Exported as $.pjax.click.
  43 +//
  44 +// event - "click" jQuery.Event
  45 +// options - pjax options
  46 +//
  47 +// Examples
  48 +//
  49 +// $(document).on('click', 'a', $.pjax.click)
  50 +// // is the same as
  51 +// $(document).pjax('a')
  52 +//
  53 +// $(document).on('click', 'a', function(event) {
  54 +// var container = $(this).closest('[data-pjax-container]')
  55 +// $.pjax.click(event, container)
  56 +// })
  57 +//
  58 +// Returns nothing.
  59 +function handleClick(event, container, options) {
  60 + options = optionsFor(container, options)
  61 +
  62 + var link = event.currentTarget
  63 +
  64 + if (link.tagName.toUpperCase() !== 'A')
  65 + throw "$.fn.pjax or $.pjax.click requires an anchor element"
  66 +
  67 + // Middle click, cmd click, and ctrl click should open
  68 + // links in a new tab as normal.
  69 + if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey )
  70 + return
  71 +
  72 + // Ignore cross origin links
  73 + if ( location.protocol !== link.protocol || location.hostname !== link.hostname )
  74 + return
  75 +
  76 + // Ignore anchors on the same page
  77 + if (link.hash && link.href.replace(link.hash, '') ===
  78 + location.href.replace(location.hash, ''))
  79 + return
  80 +
  81 + // Ignore empty anchor "foo.html#"
  82 + if (link.href === location.href + '#')
  83 + return
  84 +
  85 + var defaults = {
  86 + url: link.href,
  87 + container: $(link).attr('data-pjax'),
  88 + target: link
  89 + }
  90 +
  91 + var opts = $.extend({}, defaults, options)
  92 + var clickEvent = $.Event('pjax:click')
  93 + $(link).trigger(clickEvent, [opts])
  94 +
  95 + if (!clickEvent.isDefaultPrevented()) {
  96 + pjax(opts)
  97 + event.preventDefault()
  98 + }
  99 +}
  100 +
  101 +// Public: pjax on form submit handler
  102 +//
  103 +// Exported as $.pjax.submit
  104 +//
  105 +// event - "click" jQuery.Event
  106 +// options - pjax options
  107 +//
  108 +// Examples
  109 +//
  110 +// $(document).on('submit', 'form', function(event) {
  111 +// var container = $(this).closest('[data-pjax-container]')
  112 +// $.pjax.submit(event, container)
  113 +// })
  114 +//
  115 +// Returns nothing.
  116 +function handleSubmit(event, container, options) {
  117 + options = optionsFor(container, options)
  118 +
  119 + var form = event.currentTarget
  120 +
  121 + if (form.tagName.toUpperCase() !== 'FORM')
  122 + throw "$.pjax.submit requires a form element"
  123 +
  124 + var defaults = {
  125 + type: form.method.toUpperCase(),
  126 + url: form.action,
  127 + data: $(form).serializeArray(),
  128 + container: $(form).attr('data-pjax'),
  129 + target: form
  130 + }
  131 +
  132 + pjax($.extend({}, defaults, options))
  133 +
  134 + event.preventDefault()
  135 +}
  136 +
  137 +// Loads a URL with ajax, puts the response body inside a container,
  138 +// then pushState()'s the loaded URL.
  139 +//
  140 +// Works just like $.ajax in that it accepts a jQuery ajax
  141 +// settings object (with keys like url, type, data, etc).
  142 +//
  143 +// Accepts these extra keys:
  144 +//
  145 +// container - Where to stick the response body.
  146 +// $(container).html(xhr.responseBody)
  147 +// push - Whether to pushState the URL. Defaults to true (of course).
  148 +// replace - Want to use replaceState instead? That's cool.
  149 +//
  150 +// Use it just like $.ajax:
  151 +//
  152 +// var xhr = $.pjax({ url: this.href, container: '#main' })
  153 +// console.log( xhr.readyState )
  154 +//
  155 +// Returns whatever $.ajax returns.
  156 +function pjax(options) {
  157 + options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)
  158 +
  159 + if ($.isFunction(options.url)) {
  160 + options.url = options.url()
  161 + }
  162 +
  163 + var target = options.target
  164 +
  165 + var hash = parseURL(options.url).hash
  166 +
  167 + var context = options.context = findContainerFor(options.container)
  168 +
  169 + // We want the browser to maintain two separate internal caches: one
  170 + // for pjax'd partial page loads and one for normal page loads.
  171 + // Without adding this secret parameter, some browsers will often
  172 + // confuse the two.
  173 + if (!options.data) options.data = {}
  174 + options.data._pjax = context.selector
  175 +
  176 + function fire(type, args) {
  177 + var event = $.Event(type, { relatedTarget: target })
  178 + context.trigger(event, args)
  179 + return !event.isDefaultPrevented()
  180 + }
  181 +
  182 + var timeoutTimer
  183 +
  184 + options.beforeSend = function(xhr, settings) {
  185 + // No timeout for non-GET requests
  186 + // Its not safe to request the resource again with a fallback method.
  187 + if (settings.type !== 'GET') {
  188 + settings.timeout = 0
  189 + }
  190 +
  191 + xhr.setRequestHeader('X-PJAX', 'true')
  192 + xhr.setRequestHeader('X-PJAX-Container', context.selector)
  193 +
  194 + if (!fire('pjax:beforeSend', [xhr, settings]))
  195 + return false
  196 +
  197 + if (settings.timeout > 0) {
  198 + timeoutTimer = setTimeout(function() {
  199 + if (fire('pjax:timeout', [xhr, options]))
  200 + xhr.abort('timeout')
  201 + }, settings.timeout)
  202 +
  203 + // Clear timeout setting so jquerys internal timeout isn't invoked
  204 + settings.timeout = 0
  205 + }
  206 +
  207 + options.requestUrl = parseURL(settings.url).href
  208 + }
  209 +
  210 + options.complete = function(xhr, textStatus) {
  211 + if (timeoutTimer)
  212 + clearTimeout(timeoutTimer)
  213 +
  214 + fire('pjax:complete', [xhr, textStatus, options])
  215 +
  216 + fire('pjax:end', [xhr, options])
  217 + }
  218 +
  219 + options.error = function(xhr, textStatus, errorThrown) {
  220 + var container = extractContainer("", xhr, options)
  221 +
  222 + var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options])
  223 + if (options.type == 'GET' && textStatus !== 'abort' && allowed) {
  224 + locationReplace(container.url)
  225 + }
  226 + }
  227 +
  228 + options.success = function(data, status, xhr) {
  229 + // If $.pjax.defaults.version is a function, invoke it first.
  230 + // Otherwise it can be a static string.
  231 + var currentVersion = (typeof $.pjax.defaults.version === 'function') ?
  232 + $.pjax.defaults.version() :
  233 + $.pjax.defaults.version
  234 +
  235 + var latestVersion = xhr.getResponseHeader('X-PJAX-Version')
  236 +
  237 + var container = extractContainer(data, xhr, options)
  238 +
  239 + // If there is a layout version mismatch, hard load the new url
  240 + if (currentVersion && latestVersion && currentVersion !== latestVersion) {
  241 + locationReplace(container.url)
  242 + return
  243 + }
  244 +
  245 + // If the new response is missing a body, hard load the page
  246 + if (!container.contents) {
  247 + locationReplace(container.url)
  248 + return
  249 + }
  250 +
  251 + pjax.state = {
  252 + id: options.id || uniqueId(),
  253 + url: container.url,
  254 + title: container.title,
  255 + container: context.selector,
  256 + fragment: options.fragment,
  257 + timeout: options.timeout
  258 + }
  259 +
  260 + if (options.push || options.replace) {
  261 + window.history.replaceState(pjax.state, container.title, container.url)
  262 + }
  263 +
  264 + // Clear out any focused controls before inserting new page contents.
  265 + document.activeElement.blur()
  266 +
  267 + if (container.title) document.title = container.title
  268 + context.html(container.contents)
  269 +
  270 + // FF bug: Won't autofocus fields that are inserted via JS.
  271 + // This behavior is incorrect. So if theres no current focus, autofocus
  272 + // the last field.
  273 + //
  274 + // http://www.w3.org/html/wg/drafts/html/master/forms.html
  275 + var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0]
  276 + if (autofocusEl && document.activeElement !== autofocusEl) {
  277 + autofocusEl.focus();
  278 + }
  279 +
  280 + executeScriptTags(container.scripts)
  281 +
  282 + // Scroll to top by default
  283 + if (typeof options.scrollTo === 'number')
  284 + $(window).scrollTop(options.scrollTo)
  285 +
  286 + // If the URL has a hash in it, make sure the browser
  287 + // knows to navigate to the hash.
  288 + if ( hash !== '' ) {
  289 + // Avoid using simple hash set here. Will add another history
  290 + // entry. Replace the url with replaceState and scroll to target
  291 + // by hand.
  292 + //
  293 + // window.location.hash = hash
  294 + var url = parseURL(container.url)
  295 + url.hash = hash
  296 +
  297 + pjax.state.url = url.href
  298 + window.history.replaceState(pjax.state, container.title, url.href)
  299 +
  300 + var target = $(url.hash)
  301 + if (target.length) $(window).scrollTop(target.offset().top)
  302 + }
  303 +
  304 + fire('pjax:success', [data, status, xhr, options])
  305 + }
  306 +
  307 +
  308 + // Initialize pjax.state for the initial page load. Assume we're
  309 + // using the container and options of the link we're loading for the
  310 + // back button to the initial page. This ensures good back button
  311 + // behavior.
  312 + if (!pjax.state) {
  313 + pjax.state = {
  314 + id: uniqueId(),
  315 + url: window.location.href,
  316 + title: document.title,
  317 + container: context.selector,
  318 + fragment: options.fragment,
  319 + timeout: options.timeout
  320 + }
  321 + window.history.replaceState(pjax.state, document.title)
  322 + }
  323 +
  324 + // Cancel the current request if we're already pjaxing
  325 + var xhr = pjax.xhr
  326 + if ( xhr && xhr.readyState < 4) {
  327 + xhr.onreadystatechange = $.noop
  328 + xhr.abort()
  329 + }
  330 +
  331 + pjax.options = options
  332 + var xhr = pjax.xhr = $.ajax(options)
  333 +
  334 + if (xhr.readyState > 0) {
  335 + if (options.push && !options.replace) {
  336 + // Cache current container element before replacing it
  337 + cachePush(pjax.state.id, context.clone().contents())
  338 +
  339 + window.history.pushState(null, "", stripPjaxParam(options.requestUrl))
  340 + }
  341 +
  342 + fire('pjax:start', [xhr, options])
  343 + fire('pjax:send', [xhr, options])
  344 + }
  345 +
  346 + return pjax.xhr
  347 +}
  348 +
  349 +// Public: Reload current page with pjax.
  350 +//
  351 +// Returns whatever $.pjax returns.
  352 +function pjaxReload(container, options) {
  353 + var defaults = {
  354 + url: window.location.href,
  355 + push: false,
  356 + replace: true,
  357 + scrollTo: false
  358 + }
  359 +
  360 + return pjax($.extend(defaults, optionsFor(container, options)))
  361 +}
  362 +
  363 +// Internal: Hard replace current state with url.
  364 +//
  365 +// Work for around WebKit
  366 +// https://bugs.webkit.org/show_bug.cgi?id=93506
  367 +//
  368 +// Returns nothing.
  369 +function locationReplace(url) {
  370 + window.history.replaceState(null, "", "#")
  371 + window.location.replace(url)
  372 +}
  373 +
  374 +
  375 +var initialPop = true
  376 +var initialURL = window.location.href
  377 +var initialState = window.history.state
  378 +
  379 +// Initialize $.pjax.state if possible
  380 +// Happens when reloading a page and coming forward from a different
  381 +// session history.
  382 +if (initialState && initialState.container) {
  383 + pjax.state = initialState
  384 +}
  385 +
  386 +// Non-webkit browsers don't fire an initial popstate event
  387 +if ('state' in window.history) {
  388 + initialPop = false
  389 +}
  390 +
  391 +// popstate handler takes care of the back and forward buttons
  392 +//
  393 +// You probably shouldn't use pjax on pages with other pushState
  394 +// stuff yet.
  395 +function onPjaxPopstate(event) {
  396 + var state = event.state
  397 +
  398 + if (state && state.container) {
  399 + // When coming forward from a separate history session, will get an
  400 + // initial pop with a state we are already at. Skip reloading the current
  401 + // page.
  402 + if (initialPop && initialURL == state.url) return
  403 +
  404 + // If popping back to the same state, just skip.
  405 + // Could be clicking back from hashchange rather than a pushState.
  406 + if (pjax.state.id === state.id) return
  407 +
  408 + var container = $(state.container)
  409 + if (container.length) {
  410 + var direction, contents = cacheMapping[state.id]
  411 +
  412 + if (pjax.state) {
  413 + // Since state ids always increase, we can deduce the history
  414 + // direction from the previous state.
  415 + direction = pjax.state.id < state.id ? 'forward' : 'back'
  416 +
  417 + // Cache current container before replacement and inform the
  418 + // cache which direction the history shifted.
  419 + cachePop(direction, pjax.state.id, container.clone().contents())
  420 + }
  421 +
  422 + var popstateEvent = $.Event('pjax:popstate', {
  423 + state: state,
  424 + direction: direction
  425 + })
  426 + container.trigger(popstateEvent)
  427 +
  428 + var options = {
  429 + id: state.id,
  430 + url: state.url,
  431 + container: container,
  432 + push: false,
  433 + fragment: state.fragment,
  434 + timeout: state.timeout,
  435 + scrollTo: false
  436 + }
  437 +
  438 + if (contents) {
  439 + container.trigger('pjax:start', [null, options])
  440 +
  441 + if (state.title) document.title = state.title
  442 + container.html(contents)
  443 + pjax.state = state
  444 +
  445 + container.trigger('pjax:end', [null, options])
  446 + } else {
  447 + pjax(options)
  448 + }
  449 +
  450 + // Force reflow/relayout before the browser tries to restore the
  451 + // scroll position.
  452 + container[0].offsetHeight
  453 + } else {
  454 + locationReplace(location.href)
  455 + }
  456 + }
  457 + initialPop = false
  458 +}
  459 +
  460 +// Fallback version of main pjax function for browsers that don't
  461 +// support pushState.
  462 +//
  463 +// Returns nothing since it retriggers a hard form submission.
  464 +function fallbackPjax(options) {
  465 + var url = $.isFunction(options.url) ? options.url() : options.url,
  466 + method = options.type ? options.type.toUpperCase() : 'GET'
  467 +
  468 + var form = $('<form>', {
  469 + method: method === 'GET' ? 'GET' : 'POST',
  470 + action: url,
  471 + style: 'display:none'
  472 + })
  473 +
  474 + if (method !== 'GET' && method !== 'POST') {
  475 + form.append($('<input>', {
  476 + type: 'hidden',
  477 + name: '_method',
  478 + value: method.toLowerCase()
  479 + }))
  480 + }
  481 +
  482 + var data = options.data
  483 + if (typeof data === 'string') {
  484 + $.each(data.split('&'), function(index, value) {
  485 + var pair = value.split('=')
  486 + form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}))
  487 + })
  488 + } else if (typeof data === 'object') {
  489 + for (key in data)
  490 + form.append($('<input>', {type: 'hidden', name: key, value: data[key]}))
  491 + }
  492 +
  493 + $(document.body).append(form)
  494 + form.submit()
  495 +}
  496 +
  497 +// Internal: Generate unique id for state object.
  498 +//
  499 +// Use a timestamp instead of a counter since ids should still be
  500 +// unique across page loads.
  501 +//
  502 +// Returns Number.
  503 +function uniqueId() {
  504 + return (new Date).getTime()
  505 +}
  506 +
  507 +// Internal: Strips _pjax param from url
  508 +//
  509 +// url - String
  510 +//
  511 +// Returns String.
  512 +function stripPjaxParam(url) {
  513 + return url
  514 + .replace(/\?_pjax=[^&]+&?/, '?')
  515 + .replace(/_pjax=[^&]+&?/, '')
  516 + .replace(/[\?&]$/, '')
  517 +}
  518 +
  519 +// Internal: Parse URL components and returns a Locationish object.
  520 +//
  521 +// url - String URL
  522 +//
  523 +// Returns HTMLAnchorElement that acts like Location.
  524 +function parseURL(url) {
  525 + var a = document.createElement('a')
  526 + a.href = url
  527 + return a
  528 +}
  529 +
  530 +// Internal: Build options Object for arguments.
  531 +//
  532 +// For convenience the first parameter can be either the container or
  533 +// the options object.
  534 +//
  535 +// Examples
  536 +//
  537 +// optionsFor('#container')
  538 +// // => {container: '#container'}
  539 +//
  540 +// optionsFor('#container', {push: true})
  541 +// // => {container: '#container', push: true}
  542 +//
  543 +// optionsFor({container: '#container', push: true})
  544 +// // => {container: '#container', push: true}
  545 +//
  546 +// Returns options Object.
  547 +function optionsFor(container, options) {
  548 + // Both container and options
  549 + if ( container && options )
  550 + options.container = container
  551 +
  552 + // First argument is options Object
  553 + else if ( $.isPlainObject(container) )
  554 + options = container
  555 +
  556 + // Only container
  557 + else
  558 + options = {container: container}
  559 +
  560 + // Find and validate container
  561 + if (options.container)
  562 + options.container = findContainerFor(options.container)
  563 +
  564 + return options
  565 +}
  566 +
  567 +// Internal: Find container element for a variety of inputs.
  568 +//
  569 +// Because we can't persist elements using the history API, we must be
  570 +// able to find a String selector that will consistently find the Element.
  571 +//
  572 +// container - A selector String, jQuery object, or DOM Element.
  573 +//
  574 +// Returns a jQuery object whose context is `document` and has a selector.
  575 +function findContainerFor(container) {
  576 + container = $(container)
  577 +
  578 + if ( !container.length ) {
  579 + throw "no pjax container for " + container.selector
  580 + } else if ( container.selector !== '' && container.context === document ) {
  581 + return container
  582 + } else if ( container.attr('id') ) {
  583 + return $('#' + container.attr('id'))
  584 + } else {
  585 + throw "cant get selector for pjax container!"
  586 + }
  587 +}
  588 +
  589 +// Internal: Filter and find all elements matching the selector.
  590 +//
  591 +// Where $.fn.find only matches descendants, findAll will test all the
  592 +// top level elements in the jQuery object as well.
  593 +//
  594 +// elems - jQuery object of Elements
  595 +// selector - String selector to match
  596 +//
  597 +// Returns a jQuery object.
  598 +function findAll(elems, selector) {
  599 + return elems.filter(selector).add(elems.find(selector));
  600 +}
  601 +
  602 +function parseHTML(html) {
  603 + return $.parseHTML(html, document, true)
  604 +}
  605 +
  606 +// Internal: Extracts container and metadata from response.
  607 +//
  608 +// 1. Extracts X-PJAX-URL header if set
  609 +// 2. Extracts inline <title> tags
  610 +// 3. Builds response Element and extracts fragment if set
  611 +//
  612 +// data - String response data
  613 +// xhr - XHR response
  614 +// options - pjax options Object
  615 +//
  616 +// Returns an Object with url, title, and contents keys.
  617 +function extractContainer(data, xhr, options) {
  618 + var obj = {}
  619 +
  620 + // Prefer X-PJAX-URL header if it was set, otherwise fallback to
  621 + // using the original requested url.
  622 + obj.url = stripPjaxParam(xhr.getResponseHeader('X-PJAX-URL') || options.requestUrl)
  623 +
  624 + // Attempt to parse response html into elements
  625 + if (/<html/i.test(data)) {
  626 + var $head = $(parseHTML(data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0]))
  627 + var $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0]))
  628 + } else {
  629 + var $head = $body = $(parseHTML(data))
  630 + }
  631 +
  632 + // If response data is empty, return fast
  633 + if ($body.length === 0)
  634 + return obj
  635 +
  636 + // If there's a <title> tag in the header, use it as
  637 + // the page's title.
  638 + obj.title = findAll($head, 'title').last().text()
  639 +
  640 + if (options.fragment) {
  641 + // If they specified a fragment, look for it in the response
  642 + // and pull it out.
  643 + if (options.fragment === 'body') {
  644 + var $fragment = $body
  645 + } else {
  646 + var $fragment = findAll($body, options.fragment).first()
  647 + }
  648 +
  649 + if ($fragment.length) {
  650 + obj.contents = $fragment.contents()
  651 +
  652 + // If there's no title, look for data-title and title attributes
  653 + // on the fragment
  654 + if (!obj.title)
  655 + obj.title = $fragment.attr('title') || $fragment.data('title')
  656 + }
  657 +
  658 + } else if (!/<html/i.test(data)) {
  659 + obj.contents = $body
  660 + }
  661 +
  662 + // Clean up any <title> tags
  663 + if (obj.contents) {
  664 + // Remove any parent title elements
  665 + obj.contents = obj.contents.not(function() { return $(this).is('title') })
  666 +
  667 + // Then scrub any titles from their descendants
  668 + obj.contents.find('title').remove()
  669 +
  670 + // Gather all script[src] elements
  671 + //obj.scripts = findAll(obj.contents, 'script[src]').remove()
  672 + obj.contents = obj.contents.not(obj.scripts)
  673 + }
  674 +
  675 + // Trim any whitespace off the title
  676 + if (obj.title) obj.title = $.trim(obj.title)
  677 +
  678 + return obj
  679 +}
  680 +
  681 +// Load an execute scripts using standard script request.
  682 +//
  683 +// Avoids jQuery's traditional $.getScript which does a XHR request and
  684 +// globalEval.
  685 +//
  686 +// scripts - jQuery object of script Elements
  687 +//
  688 +// Returns nothing.
  689 +function executeScriptTags(scripts) {
  690 + if (!scripts) return
  691 +
  692 + var existingScripts = $('script[src]')
  693 +
  694 + scripts.each(function() {
  695 + var src = this.src
  696 + var matchedScripts = existingScripts.filter(function() {
  697 + return this.src === src
  698 + })
  699 + if (matchedScripts.length) return
  700 +
  701 + var script = document.createElement('script')
  702 + script.type = $(this).attr('type')
  703 + script.src = $(this).attr('src')
  704 + document.head.appendChild(script)
  705 + })
  706 +}
  707 +
  708 +// Internal: History DOM caching class.
  709 +var cacheMapping = {}
  710 +var cacheForwardStack = []
  711 +var cacheBackStack = []
  712 +
  713 +// Push previous state id and container contents into the history
  714 +// cache. Should be called in conjunction with `pushState` to save the
  715 +// previous container contents.
  716 +//
  717 +// id - State ID Number
  718 +// value - DOM Element to cache
  719 +//
  720 +// Returns nothing.
  721 +function cachePush(id, value) {
  722 + cacheMapping[id] = value
  723 + cacheBackStack.push(id)
  724 +
  725 + // Remove all entires in forward history stack after pushing
  726 + // a new page.
  727 + while (cacheForwardStack.length)
  728 + delete cacheMapping[cacheForwardStack.shift()]
  729 +
  730 + // Trim back history stack to max cache length.
  731 + while (cacheBackStack.length > pjax.defaults.maxCacheLength)
  732 + delete cacheMapping[cacheBackStack.shift()]
  733 +}
  734 +
  735 +// Shifts cache from directional history cache. Should be
  736 +// called on `popstate` with the previous state id and container
  737 +// contents.
  738 +//
  739 +// direction - "forward" or "back" String
  740 +// id - State ID Number
  741 +// value - DOM Element to cache
  742 +//
  743 +// Returns nothing.
  744 +function cachePop(direction, id, value) {
  745 + var pushStack, popStack
  746 + cacheMapping[id] = value
  747 +
  748 + if (direction === 'forward') {
  749 + pushStack = cacheBackStack
  750 + popStack = cacheForwardStack
  751 + } else {
  752 + pushStack = cacheForwardStack
  753 + popStack = cacheBackStack
  754 + }
  755 +
  756 + pushStack.push(id)
  757 + if (id = popStack.pop())
  758 + delete cacheMapping[id]
  759 +}
  760 +
  761 +// Public: Find version identifier for the initial page load.
  762 +//
  763 +// Returns String version or undefined.
  764 +function findVersion() {
  765 + return $('meta').filter(function() {
  766 + var name = $(this).attr('http-equiv')
  767 + return name && name.toUpperCase() === 'X-PJAX-VERSION'
  768 + }).attr('content')
  769 +}
  770 +
  771 +// Install pjax functions on $.pjax to enable pushState behavior.
  772 +//
  773 +// Does nothing if already enabled.
  774 +//
  775 +// Examples
  776 +//
  777 +// $.pjax.enable()
  778 +//
  779 +// Returns nothing.
  780 +function enable() {
  781 + $.fn.pjax = fnPjax
  782 + $.pjax = pjax
  783 + $.pjax.enable = $.noop
  784 + $.pjax.disable = disable
  785 + $.pjax.click = handleClick
  786 + $.pjax.submit = handleSubmit
  787 + $.pjax.reload = pjaxReload
  788 + $.pjax.defaults = {
  789 + timeout: 650,
  790 + push: true,
  791 + replace: false,
  792 + type: 'GET',
  793 + dataType: 'html',
  794 + scrollTo: 0,
  795 + maxCacheLength: 20,
  796 + version: findVersion
  797 + }
  798 + $(window).on('popstate.pjax', onPjaxPopstate)
  799 +}
  800 +
  801 +// Disable pushState behavior.
  802 +//
  803 +// This is the case when a browser doesn't support pushState. It is
  804 +// sometimes useful to disable pushState for debugging on a modern
  805 +// browser.
  806 +//
  807 +// Examples
  808 +//
  809 +// $.pjax.disable()
  810 +//
  811 +// Returns nothing.
  812 +function disable() {
  813 + $.fn.pjax = function() { return this }
  814 + $.pjax = fallbackPjax
  815 + $.pjax.enable = enable
  816 + $.pjax.disable = $.noop
  817 + $.pjax.click = $.noop
  818 + $.pjax.submit = $.noop
  819 + $.pjax.reload = function() { window.location.reload() }
  820 +
  821 + $(window).off('popstate.pjax', onPjaxPopstate)
  822 +}
  823 +
  824 +
  825 +// Add the state property to jQuery's event object so we can use it in
  826 +// $(window).bind('popstate')
  827 +if ( $.inArray('state', $.event.props) < 0 )
  828 + $.event.props.push('state')
  829 +
  830 +// Is pjax supported by this browser?
  831 +$.support.pjax =
  832 + window.history && window.history.pushState && window.history.replaceState &&
  833 + // pushState isn't reliable on iOS until 5.
  834 + !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)
  835 +
  836 +$.support.pjax ? enable() : disable()
  837 +
  838 +})(jQuery);
plugins/pjax/public/javascripts/loading-overlay.js 0 → 100644
@@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
  1 +if (typeof loading_overlay === 'undefined') {
  2 +
  3 +// block user actions while making a post. Also indicate the network transaction
  4 +loading_overlay = {
  5 +
  6 + show: function (selector) {
  7 + var element = jQuery(selector);
  8 + var overlay = jQuery('<div>', {
  9 + class: 'loading-overlay',
  10 + css: {
  11 + width: element.outerWidth(),
  12 + height: element.outerHeight(),
  13 + left: element.position().left,
  14 + top: element.position().top,
  15 + marginLeft: parseFloat(element.css('margin-left')),
  16 + marginTop: parseFloat(element.css('margin-top')),
  17 + marginRight: parseFloat(element.css('margin-right')),
  18 + marginBottom: parseFloat(element.css('margin-bottom')),
  19 + },
  20 + }).appendTo(element).get(0);
  21 +
  22 + overlay.dest = element;
  23 + element.loading_overlay = overlay;
  24 + },
  25 +
  26 + hide: function (selector) {
  27 + var element = jQuery(selector);
  28 + var overlay = element.find('.loading-overlay');
  29 + overlay.remove();
  30 + },
  31 +
  32 +};
  33 +
  34 +}
plugins/pjax/public/javascripts/patchwork.js 0 → 100644
@@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
  1 +var patch = (function () {
  2 + /*jshint evil: true */
  3 +
  4 + "use strict";
  5 +
  6 + var global = new Function("return this;")(), // Get a reference to the global object
  7 + fnProps = Object.getOwnPropertyNames(Function); // Get the own ("static") properties of the Function constructor
  8 +
  9 + return function (original, originalRef, patches) {
  10 +
  11 + var ref = global[originalRef] = original, // Maintain a reference to the original constructor as a new property on the global object
  12 + args = [],
  13 + newRef, // This will be the new patched constructor
  14 + i;
  15 +
  16 + patches.called = patches.called || originalRef; // If we are not patching static calls just pass them through to the original function
  17 +
  18 + for (i = 0; i < original.length; i++) { // Match the arity of the original constructor
  19 + args[i] = "a" + i; // Give the arguments a name (native constructors don't care, but user-defined ones will break otherwise)
  20 + }
  21 +
  22 + if (patches.constructed) { // This string is evaluated to create the patched constructor body in the case that we are patching newed calls
  23 + args.push("'use strict'; return (!!this ? " + patches.constructed + " : " + patches.called + ").apply(null, arguments);");
  24 + } else { // This string is evaluated to create the patched constructor body in the case that we are only patching static calls
  25 + args.push("'use strict'; return (!!this ? new (Function.prototype.bind.apply(" + originalRef + ", [{}].concat([].slice.call(arguments))))() : " + patches.called + ".apply(null, arguments));");
  26 + }
  27 +
  28 + newRef = new (Function.prototype.bind.apply(Function, [{}].concat(args)))(); // Create a new function to wrap the patched constructor
  29 + newRef.prototype = original.prototype; // Keep a reference to the original prototype to ensure instances of the patch appear as instances of the original
  30 + newRef.prototype.constructor = newRef; // Ensure the constructor of patched instances is the patched constructor
  31 +
  32 + Object.getOwnPropertyNames(ref).forEach(function (property) { // Add any "static" properties of the original constructor to the patched one
  33 + if (fnProps.indexOf(property) < 0) { // Don't include static properties of Function since the patched constructor will already have them
  34 + newRef[property] = ref[property];
  35 + }
  36 + });
  37 +
  38 + return newRef; // Return the patched constructor
  39 + };
  40 +
  41 +}());
0 \ No newline at end of file 42 \ No newline at end of file