Commit 5cb9df6dfe3276be2d7b63585e7050bd2025283c
Exists in
theme-brasil-digital-from-staging
and in
9 other branches
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.
AUTHORS.md
| @@ -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 <diegoamc90@gmail.com> | @@ -81,7 +82,6 @@ Carlos Morais + Diego Araújo <diegoamc90@gmail.com> | ||
| 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 <diegoamc90@gmail.com> | @@ -119,7 +119,6 @@ Diego Araújo + Renan Teruo <diegoamc90@gmail.com> | ||
| 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 <ludwig9003@gmail.com> | @@ -195,9 +194,11 @@ Luis David Aguilar Carlos <ludwig9003@gmail.com> | ||
| 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 <rr.manzo@gmail.com> | @@ -220,6 +221,7 @@ Rafael Reggiani Manzo + João M. M. da Silva <rr.manzo@gmail.com> | ||
| 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 <renanteruoc@gmail.com> | @@ -227,13 +229,13 @@ Renan Teruo + Diego Araújo <renanteruoc@gmail.com> | ||
| 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> |
| @@ -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. |
Gemfile
| @@ -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 < MyProfileController | @@ -16,14 +16,16 @@ class ProfileEditorController < 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 < ApplicationController | @@ -193,7 +193,7 @@ class AccountController < 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 < PublicController | @@ -19,7 +19,7 @@ class ChatController < 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 < PublicController | @@ -65,13 +65,13 @@ class ProfileController < 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
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 < Profile | @@ -21,6 +21,12 @@ class Person < 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 < ActiveRecord::Base | @@ -97,7 +97,7 @@ class Profile < 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 < ActiveRecord::Base | @@ -108,8 +108,8 @@ class Profile < 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 < ActiveRecord::Base | @@ -285,8 +285,9 @@ class Task < 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 < ActiveRecord::Base | @@ -201,6 +201,10 @@ class User < 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 < ActiveRecord::Base | @@ -350,6 +354,7 @@ class User < 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 < ActiveRecord::Observer | @@ -13,7 +13,7 @@ class RoleAssignmentSweeper < 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/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/task_activated.text.erb
app/views/task_mailer/task_cancelled.text.erb
app/views/task_mailer/task_created.text.erb
app/views/task_mailer/task_finished.text.erb
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 < ActiveRecord::Migration | @@ -16,15 +18,34 @@ class FixYamlEncoding < 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
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/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
debian/rules
| @@ -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 "(.*)" dialog$/ do |confirmation| | @@ -762,3 +762,11 @@ When /^I confirm the "(.*)" 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
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 "comment-count-#{@group_id}", @comments_count | @@ -8,5 +8,5 @@ page.replace_html "comment-count-#{@group_id}", @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 /\{"Send an e-mail":\{"href":"\/contact\/#{@community.identifier}\/new"\}\}/, @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 /\{"Report abuse":\{"href":"\/profile\/#{@community.identifier}\/report_abuse"\}\}/, @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 /\{"Control panel":\{"href":"\/myprofile\/#{@community.identifier}"\}\}/, @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 /\{"Send an e-mail":\{"href":"\/contact\/#{@community.identifier}\/new"\}\}/, @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 /\{"Report abuse":\{"href":"\/profile\/#{@community.identifier}\/report_abuse"\}\}/, @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 /\{"Control panel":\{"href":"\/myprofile\/#{@community.identifier}"\}\}/, @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 < ActionController::TestCase | @@ -101,9 +101,6 @@ class ContentViewerControllerTest < 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 < ActiveSupport::TestCase | @@ -6,10 +6,13 @@ class CommunityTrackPluginTest < 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 < ActiveSupport::TestCase | @@ -28,37 +31,37 @@ class CommunityTrackPluginTest < 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 < MyProfileController | @@ -23,7 +23,7 @@ class CustomFormsPluginMyprofileController < 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 < MyProfileController | @@ -43,7 +43,7 @@ class CustomFormsPluginMyprofileController < 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 < ActionController::TestCase | @@ -226,7 +226,7 @@ class CustomFormsPluginMyprofileControllerTest < 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__) + '/../../../../../test/test_helper' | @@ -3,7 +3,7 @@ require File.dirname(__FILE__) + '/../../../../../test/test_helper' | ||
| 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 < ActiveSupport::TestCase | @@ -244,7 +244,7 @@ class CustomFormsPlugin::FormTest < 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 < ActiveSupport::TestCase | @@ -13,7 +13,7 @@ class CustomFormsPlugin::MembershipSurveyTest < 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 < ActiveSupport::TestCase | @@ -31,7 +31,7 @@ class CustomFormsPlugin::MembershipSurveyTest < 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 < ActiveSupport::TestCase | @@ -5,7 +5,7 @@ class RoleAssignmentsTest < 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 < ActiveSupport::TestCase | @@ -19,7 +19,7 @@ class RoleAssignmentsTest < 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 < ActiveSupport::TestCase | @@ -31,7 +31,7 @@ class RoleAssignmentsTest < 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 < ActiveSupport::TestCase | @@ -59,7 +59,7 @@ class RoleAssignmentsTest < 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) |
| @@ -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
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 |
| @@ -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 |
| @@ -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 |
| @@ -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í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 |
| @@ -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 |
| @@ -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 |
| @@ -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 |
| @@ -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 |
| @@ -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 < ProfileController | @@ -6,12 +6,13 @@ class PeopleBlockPluginProfileController < 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 < ActiveSupport::TestCase | @@ -8,6 +8,7 @@ class FriendsBlockTest < 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 < ActiveSupport::TestCase | @@ -60,7 +61,7 @@ class FriendsBlockTest < 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 < ActiveSupport::TestCase | @@ -98,10 +99,10 @@ class FriendsBlockTest < 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 < ActiveSupport::TestCase | @@ -60,7 +60,7 @@ class MembersBlockTest < 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 < ActiveSupport::TestCase | @@ -145,10 +145,10 @@ class MembersBlockTest < 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 < ActiveSupport::TestCase | @@ -158,10 +158,10 @@ class MembersBlockTest < 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 < ActiveSupport::TestCase | @@ -106,14 +106,12 @@ class PeopleBlockTest < 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 |
| @@ -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 |
617 KB
3.4 KB
| @@ -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); |
| @@ -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 | +} |
| @@ -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 |