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 | 40 | Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com> |
| 41 | 41 | Alessandro Palmeira + Paulo Meirelles + João M. M. da Silva <alessandro.palmeira@gmail.com> |
| 42 | 42 | Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com> |
| 43 | +analosnak <analosnak@gmail.com> | |
| 43 | 44 | Ana Losnak <analosnak@gmail.com> |
| 44 | 45 | Andre Bernardes <andrebsguedes@gmail.com> |
| 45 | 46 | Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br> |
| ... | ... | @@ -81,7 +82,6 @@ Carlos Morais + Diego Araújo <diegoamc90@gmail.com> |
| 81 | 82 | Carlos Morais + Eduardo Morais <carlos88morais@gmail.com> |
| 82 | 83 | Carlos Morais + Paulo Meirelles <carlos88morais@gmail.com> |
| 83 | 84 | Carlos Morais + Pedro Leal <carlos88morais@gmail.com> |
| 84 | -Daniela Feitosa <dani@dohko.(none)> | |
| 85 | 85 | Daniel Alves + Diego Araújo <danpaulalves@gmail.com> |
| 86 | 86 | Daniel Alves + Diego Araújo <diegoamc90@gmail.com> |
| 87 | 87 | Daniel Alves + Diego Araújo + Guilherme Rojas <danpaulalves@gmail.com> |
| ... | ... | @@ -119,7 +119,6 @@ Diego Araújo + Renan Teruo <diegoamc90@gmail.com> |
| 119 | 119 | Diego Araujo + Rodrigo Souto + Rafael Manzo <rr.manzo@gmail.com> |
| 120 | 120 | Diego + Jefferson <diegoamc90@gmail.com> |
| 121 | 121 | Diego Martinez <diegoamc90@gmail.com> |
| 122 | -Diego Martinez <diego@diego-K55A.(none)> | |
| 123 | 122 | Diego + Renan <renanteruoc@gmail.com> |
| 124 | 123 | Eduardo Tourinho Edington <eduardo.edington@serpro.gov.br> |
| 125 | 124 | Evandro Jr <evandrojr@gmail.com> |
| ... | ... | @@ -195,9 +194,11 @@ Luis David Aguilar Carlos <ludwig9003@gmail.com> |
| 195 | 194 | Luiz Fernando de Freitas Matos <luiz@luizff.matos@gmail.com> |
| 196 | 195 | Marcos Ramos <ms.ramos@outlook.com> |
| 197 | 196 | Martín Olivera <molivera@solar.org.ar> |
| 197 | +Michal Čihař <michal@cihar.com> | |
| 198 | 198 | Moises Machado <moises@colivre.coop.br> |
| 199 | 199 | Naíla Alves <naila@colivre.coop.br> |
| 200 | 200 | Nanda Lopes <nanda.listas+psl@gmail.com> |
| 201 | +Parley Martins <parleypachecomartins@gmail.com> | |
| 201 | 202 | Paulo Meirelles + Alessandro Palmeira + João M. M. da Silva <paulo@softwarelivre.org> |
| 202 | 203 | Paulo Meirelles + Alessandro Palmeira <paulo@softwarelivre.org> |
| 203 | 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 | 221 | Rafael Reggiani Manzo <rr.manzo@gmail.com> |
| 221 | 222 | Raphaël Rousseau <raph@r4f.org> |
| 222 | 223 | Raquel Lira <raquel.lira@gmail.com> |
| 224 | +Raquel <rcordioli@gmail.com> | |
| 223 | 225 | Renan Teruo + Caio Salgado <renanteruoc@gmail.com> |
| 224 | 226 | Renan Teruoc + Diego Araujo <renanteruoc@gmail.com> |
| 225 | 227 | Renan Teruo + Diego Araujo <renanteruoc@gmail.com> |
| ... | ... | @@ -227,13 +229,13 @@ Renan Teruo + Diego Araújo <renanteruoc@gmail.com> |
| 227 | 229 | Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com> |
| 228 | 230 | Renan Teruo + Rafael Manzo <renanteruoc@gmail.com> |
| 229 | 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 | 232 | Rodrigo Souto <rodrigo@colivre.coop.br> |
| 232 | 233 | Ronny Kursawe <kursawe.ronny@googlemail.com> |
| 233 | 234 | root <root@debian.sdr.serpro> |
| 234 | 235 | Samuel R. C. Vale <srcvale@holoscopio.com> |
| 235 | 236 | Tallys Martins <tallysmartins@gmail.com> |
| 236 | 237 | tallys <tallys@tallys.(none)> |
| 238 | +Thiago Zoroastro <thiago.zoroastro@bol.com.br> | |
| 237 | 239 | Valessio Brito <contato@valessiobrito.com.br> |
| 238 | 240 | Valessio Brito <contato@valessiobrito.info> |
| 239 | 241 | Valessio Brito <valessio@gmail.com> | ... | ... |
| ... | ... | @@ -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 | 41 | gem 'selenium-webdriver', '~> 2.39.0' |
| 42 | 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 | 49 | end | ... | ... |
app/controllers/my_profile/profile_editor_controller.rb
| ... | ... | @@ -16,14 +16,16 @@ class ProfileEditorController < MyProfileController |
| 16 | 16 | if request.post? |
| 17 | 17 | params[:profile_data][:fields_privacy] ||= {} if profile.person? && params[:profile_data].is_a?(Hash) |
| 18 | 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 | 27 | end |
| 25 | 28 | end |
| 26 | - end | |
| 27 | 29 | end |
| 28 | 30 | end |
| 29 | 31 | ... | ... |
app/controllers/public/account_controller.rb
| ... | ... | @@ -193,7 +193,7 @@ class AccountController < ApplicationController |
| 193 | 193 | else |
| 194 | 194 | @change_password.errors[:base] << _('Could not find any user with %s equal to "%s".') % [fields_label, params[:value]] |
| 195 | 195 | end |
| 196 | - rescue ActiveRecord::RecordInvald | |
| 196 | + rescue ActiveRecord::RecordInvalid | |
| 197 | 197 | @change_password.errors[:base] << _('Could not perform password recovery for the user.') |
| 198 | 198 | end |
| 199 | 199 | end | ... | ... |
app/controllers/public/chat_controller.rb
| ... | ... | @@ -19,7 +19,7 @@ class ChatController < PublicController |
| 19 | 19 | def avatar |
| 20 | 20 | profile = environment.profiles.find_by_identifier(params[:id]) |
| 21 | 21 | filename, mimetype = profile_icon(profile, :minor, true) |
| 22 | - if filename =~ /^https?:/ | |
| 22 | + if filename =~ /^(https?:)?\/\// | |
| 23 | 23 | redirect_to filename |
| 24 | 24 | else |
| 25 | 25 | data = File.read(File.join(Rails.root, 'public', filename)) | ... | ... |
app/controllers/public/profile_controller.rb
| ... | ... | @@ -65,13 +65,13 @@ class ProfileController < PublicController |
| 65 | 65 | |
| 66 | 66 | def friends |
| 67 | 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 | 69 | end |
| 70 | 70 | end |
| 71 | 71 | |
| 72 | 72 | def members |
| 73 | 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 | 75 | end |
| 76 | 76 | end |
| 77 | 77 | ... | ... |
app/helpers/application_helper.rb
| ... | ... | @@ -433,19 +433,19 @@ module ApplicationHelper |
| 433 | 433 | end |
| 434 | 434 | |
| 435 | 435 | def theme_site_title |
| 436 | - theme_include('site_title') | |
| 436 | + @theme_site_title ||= theme_include 'site_title' | |
| 437 | 437 | end |
| 438 | 438 | |
| 439 | 439 | def theme_header |
| 440 | - theme_include('header') | |
| 440 | + @theme_header ||= theme_include 'header' | |
| 441 | 441 | end |
| 442 | 442 | |
| 443 | 443 | def theme_footer |
| 444 | - theme_include('footer') | |
| 444 | + @theme_footer ||= theme_include 'footer' | |
| 445 | 445 | end |
| 446 | 446 | |
| 447 | 447 | def theme_extra_navigation |
| 448 | - theme_include('navigation') | |
| 448 | + @theme_extra_navigation ||= theme_include 'navigation' | |
| 449 | 449 | end |
| 450 | 450 | |
| 451 | 451 | def is_testing_theme |
| ... | ... | @@ -674,13 +674,14 @@ module ApplicationHelper |
| 674 | 674 | html.join "\n" |
| 675 | 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 | 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 | 685 | end |
| 685 | 686 | |
| 686 | 687 | def file_field_or_thumbnail(label, image, i) |
| ... | ... | @@ -907,13 +908,15 @@ module ApplicationHelper |
| 907 | 908 | end |
| 908 | 909 | |
| 909 | 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 | 920 | end |
| 918 | 921 | |
| 919 | 922 | # DEPRECATED. Do not use this. |
| ... | ... | @@ -1285,11 +1288,13 @@ module ApplicationHelper |
| 1285 | 1288 | end |
| 1286 | 1289 | |
| 1287 | 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 | 1298 | end |
| 1294 | 1299 | |
| 1295 | 1300 | def expirable_link_to(expired, content, url, options = {}) |
| ... | ... | @@ -1377,7 +1382,7 @@ module ApplicationHelper |
| 1377 | 1382 | # are old things that do not support it we are keeping this hot spot. |
| 1378 | 1383 | html = @plugins.pipeline(:parse_content, html, source).first |
| 1379 | 1384 | end |
| 1380 | - html | |
| 1385 | + html && html.html_safe | |
| 1381 | 1386 | end |
| 1382 | 1387 | |
| 1383 | 1388 | def convert_macro(html, source) | ... | ... |
app/helpers/content_viewer_helper.rb
| ... | ... | @@ -10,7 +10,7 @@ module ContentViewerHelper |
| 10 | 10 | end |
| 11 | 11 | |
| 12 | 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 | 14 | end |
| 15 | 15 | |
| 16 | 16 | def article_title(article, args = {}) | ... | ... |
app/helpers/layout_helper.rb
| ... | ... | @@ -17,6 +17,8 @@ module LayoutHelper |
| 17 | 17 | unless plugins_javascripts.empty? |
| 18 | 18 | output += javascript_include_tag plugins_javascripts, :cache => "cache/plugins-#{Digest::MD5.hexdigest plugins_javascripts.to_s}" |
| 19 | 19 | end |
| 20 | + output += theme_javascript_ng.to_s | |
| 21 | + | |
| 20 | 22 | output |
| 21 | 23 | end |
| 22 | 24 | |
| ... | ... | @@ -85,6 +87,10 @@ module LayoutHelper |
| 85 | 87 | theme_path + '/style.css' |
| 86 | 88 | end |
| 87 | 89 | |
| 90 | + def layout_template | |
| 91 | + if profile then profile.layout_template else environment.layout_template end | |
| 92 | + end | |
| 93 | + | |
| 88 | 94 | def addthis_javascript |
| 89 | 95 | if NOOSFERO_CONF['addthis_enabled'] |
| 90 | 96 | '<script src="https://s7.addthis.com/js/152/addthis_widget.js"></script>' |
| ... | ... | @@ -92,7 +98,7 @@ module LayoutHelper |
| 92 | 98 | end |
| 93 | 99 | |
| 94 | 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 | 102 | end |
| 97 | 103 | end |
| 98 | 104 | ... | ... |
app/helpers/role_helper.rb
app/helpers/sweeper_helper.rb
| ... | ... | @@ -56,12 +56,12 @@ module SweeperHelper |
| 56 | 56 | if profile |
| 57 | 57 | profile.blocks.each {|block| |
| 58 | 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 | 61 | end |
| 62 | 62 | environment.blocks.each {|block| |
| 63 | 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 | 67 | blocks_to_expire.uniq! | ... | ... |
app/models/person.rb
| ... | ... | @@ -21,6 +21,12 @@ class Person < Profile |
| 21 | 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 | 30 | def has_permission_with_plugins?(permission, profile) |
| 25 | 31 | permissions = [has_permission_without_plugins?(permission, profile)] |
| 26 | 32 | permissions += plugins.map do |plugin| | ... | ... |
app/models/profile.rb
| ... | ... | @@ -97,7 +97,7 @@ class Profile < ActiveRecord::Base |
| 97 | 97 | end |
| 98 | 98 | |
| 99 | 99 | def members_by_name |
| 100 | - members.order(:name) | |
| 100 | + members.order('profiles.name') | |
| 101 | 101 | end |
| 102 | 102 | |
| 103 | 103 | class << self |
| ... | ... | @@ -108,8 +108,8 @@ class Profile < ActiveRecord::Base |
| 108 | 108 | alias_method_chain :count, :distinct |
| 109 | 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 | 113 | end |
| 114 | 114 | |
| 115 | 115 | acts_as_having_boxes | ... | ... |
app/models/task.rb
| ... | ... | @@ -285,8 +285,9 @@ class Task < ActiveRecord::Base |
| 285 | 285 | # If |
| 286 | 286 | def send_notification(action) |
| 287 | 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 | 291 | end |
| 291 | 292 | end |
| 292 | 293 | end | ... | ... |
app/models/user.rb
| ... | ... | @@ -201,6 +201,10 @@ class User < ActiveRecord::Base |
| 201 | 201 | Digest::MD5.hexdigest(password) |
| 202 | 202 | end |
| 203 | 203 | |
| 204 | + add_encryption_method :salted_md5 do |password, salt| | |
| 205 | + Digest::MD5.hexdigest(password+salt) | |
| 206 | + end | |
| 207 | + | |
| 204 | 208 | add_encryption_method :clear do |password, salt| |
| 205 | 209 | password |
| 206 | 210 | end |
| ... | ... | @@ -350,6 +354,7 @@ class User < ActiveRecord::Base |
| 350 | 354 | end |
| 351 | 355 | |
| 352 | 356 | def delay_activation_check |
| 357 | + return if person.is_template? | |
| 353 | 358 | Delayed::Job.enqueue(UserActivationJob.new(self.id), {:priority => 0, :run_at => 72.hours.from_now}) |
| 354 | 359 | end |
| 355 | 360 | end | ... | ... |
app/sweepers/role_assignment_sweeper.rb
| ... | ... | @@ -13,7 +13,7 @@ class RoleAssignmentSweeper < ActiveRecord::Observer |
| 13 | 13 | protected |
| 14 | 14 | |
| 15 | 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 | 17 | expire_cache(role_assignment.resource) if role_assignment.resource.kind_of?(Profile) |
| 18 | 18 | end |
| 19 | 19 | ... | ... |
app/views/file_presenter/_generic.html.erb
| 1 | 1 | <span class="download-link"> |
| 2 | 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 | 4 | </span> |
| 5 | 5 | |
| 6 | 6 | <div class="uploaded-file-description <%= 'empty' if generic.abstract.blank? %>"> | ... | ... |
app/views/file_presenter/_image.html.erb
| ... | ... | @@ -28,7 +28,7 @@ |
| 28 | 28 | |
| 29 | 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 | 33 | <div class="uploaded-file-description <%= 'empty' if image.abstract.blank? %>"> |
| 34 | 34 | <%= image.abstract %> | ... | ... |
app/views/home/index.html.erb
app/views/layouts/application-ng.html.erb
| ... | ... | @@ -17,7 +17,7 @@ |
| 17 | 17 | <meta property="og:url" content="<%= @page ? url_for(@page.url) : @environment.top_url %>"> |
| 18 | 18 | <meta property="og:title" content="<%= h page_title %>"> |
| 19 | 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 | 22 | <!-- site root --> |
| 23 | 23 | <meta property="noosfero:root" content="<%= Noosfero.root %>"/> |
| ... | ... | @@ -72,10 +72,7 @@ |
| 72 | 72 | <div id="navigation-end"></div> |
| 73 | 73 | </div><!-- end id="navigation" --> |
| 74 | 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 | 76 | </div><!-- end id="content" --> |
| 80 | 77 | </div><!-- end id="wrap-2" --> |
| 81 | 78 | </div><!-- end id="wrap-1" --> |
| ... | ... | @@ -84,7 +81,6 @@ |
| 84 | 81 | <%= theme_footer %> |
| 85 | 82 | </div><!-- end id="theme-footer" --> |
| 86 | 83 | <%= noosfero_layout_features %> |
| 87 | - <%= theme_javascript_ng %> | |
| 88 | 84 | <%= addthis_javascript %> |
| 89 | 85 | <%= |
| 90 | 86 | @plugins.dispatch(:body_ending).map do |content| | ... | ... |
app/views/profile/_profile_comment_form.html.erb
| ... | ... | @@ -10,8 +10,8 @@ |
| 10 | 10 | :rows => 1, |
| 11 | 11 | :class => 'submit-with-keypress', |
| 12 | 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 | 15 | :value => _('Leave your comment'), |
| 16 | 16 | :style => 'color: #ccc' %> |
| 17 | 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 | 9 | :rows => 1, |
| 10 | 10 | :class => 'submit-with-keypress', |
| 11 | 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 | 14 | :value => _('Leave your comment') %> |
| 15 | 15 | <%= hidden_field_tag 'scrap[scrap_id]', scrap.id %> |
| 16 | 16 | <%= hidden_field_tag 'receiver_id', scrap.sender.id %> | ... | ... |
app/views/profile_editor/_person_form.html.erb
| ... | ... | @@ -27,6 +27,10 @@ |
| 27 | 27 | <%= optional_field(@person, 'district', labelled_form_field(_('District'), text_field(:profile_data, :district, :rel => _('District')))) %> |
| 28 | 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 | 34 | <% optional_field(@person, 'schooling') do %> |
| 31 | 35 | <div class="formfieldline"> |
| 32 | 36 | <label class='formlabel' for='profile_data_schooling'><%= _('Schooling') %></label> | ... | ... |
app/views/role/_form.html.erb
| ... | ... | @@ -6,10 +6,14 @@ |
| 6 | 6 | |
| 7 | 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 | 17 | <% end %> |
| 14 | 18 | |
| 15 | 19 | <% button_bar do %> | ... | ... |
app/views/role/edit.html.erb
| 1 | 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 | 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 | 111 | # Make sure the secret is at least 30 characters and all random, |
| 112 | 112 | # no regular words or you'll be exposed to dictionary attacks. |
| 113 | 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 | 116 | config.time_zone = File.read('/etc/timezone').split("\n").first |
| 119 | 117 | config.active_record.default_timezone = :local | ... | ... |
config/noosfero.yml.dist
| ... | ... | @@ -5,7 +5,7 @@ development: |
| 5 | 5 | addthis_pub: your-user-name |
| 6 | 6 | addthis_logo: http://localhost:3000/images/logo-200x50.png |
| 7 | 7 | addthis_options: favorites, email, digg, delicious, technorati, slashdot, twitter, more |
| 8 | - gravatar: wavatar | |
| 8 | + gravatar: mm | |
| 9 | 9 | googlemaps_initial_zoom: 4 |
| 10 | 10 | exception_recipients: [admin@example.com] |
| 11 | 11 | max_upload_size: 5MB | ... | ... |
db/migrate/20140724134600_remove_environment_statistics_block_sooner.rb
0 → 100644
| ... | ... | @@ -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 | 1 | class FixYamlEncoding < ActiveRecord::Migration |
| 2 | 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 | 12 | end |
| 11 | 13 | |
| 12 | 14 | def self.down |
| ... | ... | @@ -16,15 +18,34 @@ class FixYamlEncoding < ActiveRecord::Migration |
| 16 | 18 | private |
| 17 | 19 | |
| 18 | 20 | def self.fix_encoding(model, param) |
| 19 | - result = model.find(:all, :conditions => "#{param} LIKE '%!binary%'") | |
| 21 | + result = model.all | |
| 20 | 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 | 37 | end |
| 23 | 38 | |
| 24 | 39 | def self.deep_fix(hash) |
| 25 | 40 | hash.each do |value| |
| 26 | - value.force_encoding('UTF-8') if value.is_a?(String) && !value.frozen? && value.encoding == Encoding::ASCII_8BIT | |
| 27 | 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 | 49 | end |
| 29 | 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 | 13 | noosfero (1.0~rc3) wheezy-test; urgency=low |
| 2 | 14 | |
| 3 | 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 | 19 | noosfero (1.0~rc2) wheezy-test; urgency=low |
| 8 | 20 | |
| 9 | 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 | 25 | noosfero (1.0~rc1) wheezy-test; urgency=low |
| 14 | 26 | |
| 15 | 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 | 31 | noosfero (0.99.0~rc20140618202455) wheezy-test; urgency=low |
| 20 | 32 | ... | ... |
debian/control
| ... | ... | @@ -51,6 +51,7 @@ Depends: |
| 51 | 51 | ruby-hpricot, |
| 52 | 52 | ruby-nokogiri, |
| 53 | 53 | ruby-acts-as-taggable-on, |
| 54 | + ruby-progressbar, | |
| 54 | 55 | ruby-prototype-rails, |
| 55 | 56 | ruby-rails-autolink, |
| 56 | 57 | memcached, |
| ... | ... | @@ -60,6 +61,11 @@ Depends: |
| 60 | 61 | dbconfig-common, |
| 61 | 62 | adduser, |
| 62 | 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 | 69 | ${misc:Depends} |
| 64 | 70 | Recommends: |
| 65 | 71 | postgresql, | ... | ... |
debian/noosfero.install
| ... | ... | @@ -7,9 +7,6 @@ util usr/share/noosfero |
| 7 | 7 | Rakefile usr/share/noosfero |
| 8 | 8 | vendor usr/share/noosfero |
| 9 | 9 | |
| 10 | -Gemfile usr/share/noosfero | |
| 11 | -debian/bundle/config usr/share/noosfero/.bundle | |
| 12 | - | |
| 13 | 10 | config/application.rb usr/share/noosfero/config |
| 14 | 11 | config/boot.rb usr/share/noosfero/config |
| 15 | 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 | 15 | usr/share/noosfero/public/designs/themes/noosfero usr/share/noosfero/public/designs/themes/default |
| 16 | 16 | usr/share/noosfero/public/designs/icons/tango usr/share/noosfero/public/designs/icons/default |
| 17 | 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 | 20 | dh_link usr/lib/noosfero/dbinstall usr/share/dbconfig-common/scripts/noosfero/install/$$db; \ |
| 21 | 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 | 27 | override_dh_installinit: |
| 24 | 28 | dh_installinit -pnoosfero --onlyscripts |
| 25 | 29 | ... | ... |
etc/noosfero/varnish-noosfero.vcl
| ... | ... | @@ -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 | 20 | sub vcl_error { |
| 14 | 21 | set obj.http.Content-Type = "text/html; charset=utf-8"; |
| 15 | 22 | ... | ... |
features/gallery_navigation.feature
| ... | ... | @@ -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 | 20 | Scenario: The Aurium's gravatar picture must link to his gravatar profile |
| 21 | 21 | # because Aurium has his picture registered at garvatar.com. |
| 22 | 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 | 25 | @selenium |
| 26 | 26 | Scenario: The NoOne's gravatar picture must link to Gravatar homepage |
| 27 | 27 | # because NoOne <nobody@colivre.coop.br> has no picture registered. |
| 28 | 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 | 278 | Then "José da Silva" should be a member of "Free Software" |
| 279 | 279 | |
| 280 | 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 | 281 | Scenario: user registration is moderated by admin |
| 305 | 282 | Given feature "admin_must_approve_new_users" is enabled on environment |
| 306 | 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 | 762 | assert_equal confirmation, a.text |
| 763 | 763 | a.accept |
| 764 | 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 | 773 | \ No newline at end of file | ... | ... |
lib/log_memory_consumption_job.rb
lib/noosfero.rb
| ... | ... | @@ -70,16 +70,6 @@ module Noosfero |
| 70 | 70 | end |
| 71 | 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 | 73 | def self.url_options |
| 84 | 74 | case Rails.env |
| 85 | 75 | when 'development' | ... | ... |
lib/noosfero/gravatar.rb
| 1 | 1 | module Noosfero::Gravatar |
| 2 | 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 | 4 | :only_path => false, |
| 5 | 5 | }.merge(options).map{|k,v| '%s=%s' % [ k,v ] }.join('&') |
| 6 | 6 | end |
| 7 | 7 | |
| 8 | 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 | 10 | end |
| 11 | 11 | end | ... | ... |
lib/noosfero/plugin.rb
| ... | ... | @@ -88,18 +88,29 @@ class Noosfero::Plugin |
| 88 | 88 | # This is a generic method that initialize any possible filter defined by a |
| 89 | 89 | # plugin to a specific controller |
| 90 | 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 | 114 | end |
| 104 | 115 | end |
| 105 | 116 | end |
| ... | ... | @@ -531,6 +542,18 @@ class Noosfero::Plugin |
| 531 | 542 | nil |
| 532 | 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 | 557 | # -> Adds additional blocks to profiles and environments. |
| 535 | 558 | # Your plugin must implements a class method called 'extra_blocks' |
| 536 | 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 | 7 | from = ENV['PREV_HEAD'] || "origin/#{current_branch}" |
| 8 | 8 | to = ENV['HEAD'] || current_branch |
| 9 | 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 | 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 | 22 | # explicitly changed tests |
| 14 | 23 | tests = changed_files.select { |f| f =~ /test\/.*_test\.rb$/ } |
| 15 | 24 | features = changed_files.select { |f| f =~ /\.feature$/ } |
| ... | ... | @@ -26,7 +35,14 @@ namespace :ci do |
| 26 | 35 | |
| 27 | 36 | sh 'testrb', '-Itest', *tests unless tests.empty? |
| 28 | 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 | 46 | end |
| 31 | 47 | |
| 32 | 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 | 9 | @all_plugins = Dir.glob('plugins/*').map { |f| File.basename(f) } - ['template'] |
| 2 | 10 | @all_plugins.sort! |
| 3 | 11 | @all_tasks = [:units, :functionals, :integration, :cucumber, :selenium] |
| ... | ... | @@ -104,7 +112,7 @@ def run_test(name, files) |
| 104 | 112 | end |
| 105 | 113 | |
| 106 | 114 | def run_testrb(files) |
| 107 | - sh 'testrb', '-Itest', *files | |
| 115 | + sh 'testrb', '-I.:test', *files | |
| 108 | 116 | end |
| 109 | 117 | |
| 110 | 118 | def run_cucumber(profile, files) |
| ... | ... | @@ -167,6 +175,7 @@ def test_sequence(plugins, tasks) |
| 167 | 175 | end |
| 168 | 176 | end |
| 169 | 177 | rollback_plugins_state |
| 178 | + yield(failed) if block_given? | |
| 170 | 179 | fail 'There are broken tests to be fixed!' if fail_flag |
| 171 | 180 | end |
| 172 | 181 | |
| ... | ... | @@ -195,13 +204,39 @@ namespace :test do |
| 195 | 204 | @all_tasks.each do |taskname| |
| 196 | 205 | desc "Run #{taskname} tests for all plugins" |
| 197 | 206 | task taskname do |
| 198 | - test_sequence(@all_plugins, taskname) | |
| 207 | + test_sequence(@all_plugins - @broken_plugins, taskname) | |
| 199 | 208 | end |
| 200 | 209 | end |
| 201 | 210 | end |
| 202 | 211 | |
| 203 | 212 | desc "Run all tests for all plugins" |
| 204 | 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 | 217 | end |
| 207 | 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 | 83 | begin |
| 84 | 84 | File.open("AUTHORS.md", 'w') do |output| |
| 85 | 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 | 87 | output.puts AUTHORS_FOOTER |
| 88 | 88 | end |
| 89 | 89 | commit_changes(['AUTHORS.md'], 'Updating authors file') if !pendencies_on_authors[:ok] |
| ... | ... | @@ -137,7 +137,17 @@ EOF |
| 137 | 137 | new_version += '~rc1' |
| 138 | 138 | end |
| 139 | 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 | 151 | end |
| 142 | 152 | |
| 143 | 153 | puts "Current version: #{$version}" | ... | ... |
lib/unifreire_terminology.rb
| ... | ... | @@ -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
lib/white_list_filter.rb
| ... | ... | @@ -9,7 +9,7 @@ module WhiteListFilter |
| 9 | 9 | unless iframe =~ /src=['"].*src=['"]/ |
| 10 | 10 | trusted_sites.each do |trusted_site| |
| 11 | 11 | re_dom = trusted_site.gsub('.', '\.') |
| 12 | - if iframe =~ /src=["']https?:\/\/(www\.)?#{re_dom}\// | |
| 12 | + if iframe =~ /src=["'](https?:)?\/\/(www\.)?#{re_dom}\// | |
| 13 | 13 | result = iframe |
| 14 | 14 | end |
| 15 | 15 | end | ... | ... |
lib/zen3_terminology.rb
| ... | ... | @@ -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 | 8 | if @no_more_pages |
| 9 | 9 | page.replace_html "comments_list_group_#{@group_id}_more", "" |
| 10 | 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 | 12 | end | ... | ... |
plugins/community_block/test/functional/commmunity_block_plugin_profile_controller_test.rb
0 → 100644
| ... | ... | @@ -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 | -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 | 23 | <%= link_to( |
| 24 | 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 | 27 | :class => 'simplemenu-trigger') %> |
| 28 | 28 | |
| 29 | 29 | <% end %> |
| ... | ... | @@ -32,11 +32,11 @@ |
| 32 | 32 | <% if profile.members.include?(user) || profile.already_request_membership?(user) %> |
| 33 | 33 | <%= link_to( |
| 34 | 34 | content_tag('span', '', :class => 'community-block-button icon-remove'), |
| 35 | - profile.leave_url) %> | |
| 35 | + profile.leave_url, :class => 'join-community') %> | |
| 36 | 36 | <% else %> |
| 37 | 37 | <%= link_to( |
| 38 | 38 | content_tag('span', '', :class => 'community-block-button icon-add'), |
| 39 | - profile.join_url) %> | |
| 39 | + profile.join_url, :class => 'join-community') %> | |
| 40 | 40 | <% end %> |
| 41 | 41 | <% else %> |
| 42 | 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 | 101 | should 'render tracks in track list block' do |
| 102 | 102 | @block = CommunityTrackPlugin::TrackListBlock.create!(:box => @profile.boxes.last) |
| 103 | 103 | get :view_page, @step.url |
| 104 | - file = File.open('result.html', 'w+') | |
| 105 | - file.write(@response.body) | |
| 106 | - file.close | |
| 107 | 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 | 105 | end |
| 109 | 106 | ... | ... |
plugins/community_track/test/unit/community_track_plugin_test.rb
| ... | ... | @@ -6,10 +6,13 @@ class CommunityTrackPluginTest < ActiveSupport::TestCase |
| 6 | 6 | @plugin = CommunityTrackPlugin.new |
| 7 | 7 | @profile = fast_create(Community) |
| 8 | 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 | 13 | end |
| 11 | 14 | |
| 12 | - attr_reader :profile, :params | |
| 15 | + attr_reader :profile, :params, :context | |
| 13 | 16 | |
| 14 | 17 | should 'has name' do |
| 15 | 18 | assert CommunityTrackPlugin.plugin_name |
| ... | ... | @@ -28,37 +31,37 @@ class CommunityTrackPluginTest < ActiveSupport::TestCase |
| 28 | 31 | end |
| 29 | 32 | |
| 30 | 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 | 35 | assert_not_includes @plugin.content_types, CommunityTrackPlugin::Track |
| 33 | 36 | end |
| 34 | 37 | |
| 35 | 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 | 41 | assert_not_includes @plugin.content_types, CommunityTrackPlugin::Track |
| 39 | 42 | end |
| 40 | 43 | |
| 41 | 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 | 47 | assert_includes @plugin.content_types, CommunityTrackPlugin::Step |
| 45 | 48 | end |
| 46 | 49 | |
| 47 | 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 | 53 | assert_not_includes @plugin.content_types, CommunityTrackPlugin::Step |
| 51 | 54 | end |
| 52 | 55 | |
| 53 | 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 | 59 | assert_equivalent [CommunityTrackPlugin::Step, CommunityTrackPlugin::Track], @plugin.content_types |
| 57 | 60 | end |
| 58 | 61 | |
| 59 | 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 | 65 | assert_equivalent [CommunityTrackPlugin::Step, CommunityTrackPlugin::Track], @plugin.content_types |
| 63 | 66 | end |
| 64 | 67 | ... | ... |
plugins/custom_forms/controllers/custom_forms_plugin_myprofile_controller.rb
| ... | ... | @@ -23,7 +23,7 @@ class CustomFormsPluginMyprofileController < MyProfileController |
| 23 | 23 | |
| 24 | 24 | respond_to do |format| |
| 25 | 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 | 27 | format.html { redirect_to(:action=>'index') } |
| 28 | 28 | else |
| 29 | 29 | format.html { render :action => 'new' } |
| ... | ... | @@ -43,7 +43,7 @@ class CustomFormsPluginMyprofileController < MyProfileController |
| 43 | 43 | |
| 44 | 44 | respond_to do |format| |
| 45 | 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 | 47 | format.html { redirect_to(:action=>'index') } |
| 48 | 48 | else |
| 49 | 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 | 226 | end |
| 227 | 227 | |
| 228 | 228 | should 'list pending submissions for a form' do |
| 229 | - person = fast_create(Person) | |
| 229 | + person = create_user('john').person | |
| 230 | 230 | form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software', :for_admission => true) |
| 231 | 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 | 3 | class CustomFormsPlugin::AdmissionSurveyTest < ActiveSupport::TestCase |
| 4 | 4 | should 'add member to community on perform' do |
| 5 | 5 | profile = fast_create(Community) |
| 6 | - person = fast_create(Person) | |
| 6 | + person = create_user('john').person | |
| 7 | 7 | form = CustomFormsPlugin::Form.create!(:name => 'Simple Form', :profile => profile) |
| 8 | 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 | 244 | |
| 245 | 245 | should 'cancel survey tasks after removing a form' do |
| 246 | 246 | profile = fast_create(Profile) |
| 247 | - person = fast_create(Person) | |
| 247 | + person = create_user('john').person | |
| 248 | 248 | |
| 249 | 249 | form1 = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => profile) |
| 250 | 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 | 13 | |
| 14 | 14 | should 'create submission with answers on perform' do |
| 15 | 15 | profile = fast_create(Profile) |
| 16 | - person = fast_create(Person) | |
| 16 | + person = create_user('john').person | |
| 17 | 17 | form = CustomFormsPlugin::Form.create!(:name => 'Simple Form', :profile => profile) |
| 18 | 18 | field = CustomFormsPlugin::Field.create!(:name => 'Name', :form => form) |
| 19 | 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 | 31 | |
| 32 | 32 | should 'have a scope that retrieves all tasks requested by profile' do |
| 33 | 33 | profile = fast_create(Profile) |
| 34 | - person = fast_create(Person) | |
| 34 | + person = create_user('john').person | |
| 35 | 35 | form = CustomFormsPlugin::Form.create!(:name => 'Simple Form', :profile => profile) |
| 36 | 36 | task1 = CustomFormsPlugin::MembershipSurvey.create!(:form_id => form.id, :target => person, :requestor => profile) |
| 37 | 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 | 5 | environment = Environment.default |
| 6 | 6 | environment.enable_plugin(CustomFormsPlugin) |
| 7 | 7 | organization = fast_create(Organization) |
| 8 | - person = fast_create(Person) | |
| 8 | + person = create_user('john').person | |
| 9 | 9 | f1 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 1', :on_membership => true) |
| 10 | 10 | f2 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 2', :on_membership => true) |
| 11 | 11 | f3 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 3', :on_membership => false) |
| ... | ... | @@ -19,7 +19,7 @@ class RoleAssignmentsTest < ActiveSupport::TestCase |
| 19 | 19 | environment = Environment.default |
| 20 | 20 | environment.enable_plugin(CustomFormsPlugin) |
| 21 | 21 | organization = fast_create(Organization) |
| 22 | - person = fast_create(Person) | |
| 22 | + person = create_user('john').person | |
| 23 | 23 | form = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form', :on_membership => true, :access => 'associated') |
| 24 | 24 | |
| 25 | 25 | assert_difference 'CustomFormsPlugin::MembershipSurvey.count', 1 do |
| ... | ... | @@ -31,7 +31,7 @@ class RoleAssignmentsTest < ActiveSupport::TestCase |
| 31 | 31 | environment = Environment.default |
| 32 | 32 | environment.enable_plugin(CustomFormsPlugin) |
| 33 | 33 | organization = fast_create(Organization) |
| 34 | - person = fast_create(Person) | |
| 34 | + person = create_user('john').person | |
| 35 | 35 | form1 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 1', :on_membership => true) |
| 36 | 36 | organization.add_member(person) |
| 37 | 37 | |
| ... | ... | @@ -59,7 +59,7 @@ class RoleAssignmentsTest < ActiveSupport::TestCase |
| 59 | 59 | environment = Environment.default |
| 60 | 60 | environment.enable_plugin(CustomFormsPlugin) |
| 61 | 61 | organization = fast_create(Organization) |
| 62 | - person = fast_create(Person) | |
| 62 | + person = create_user('john').person | |
| 63 | 63 | f1 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 1', :for_admission => true) |
| 64 | 64 | f2 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 2', :for_admission => true) |
| 65 | 65 | f3 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 3', :for_admission => false) | ... | ... |
| ... | ... | @@ -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 | 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 @@ |
| 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 | 50 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -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 @@ |
| 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 @@ |
| 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 @@ |
| 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 @@ |
| 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 | 13 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -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 | 16 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -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 @@ |
| 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 @@ |
| 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 @@ |
| 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 @@ |
| 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 | 6 | if is_cache_expired?(profile.members_cache_key(params)) |
| 7 | 7 | unless params[:role_key].blank? |
| 8 | 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 | 10 | @members_title = role.name |
| 11 | 11 | else |
| 12 | - @members = profile.members.includes(relations_to_include).paginate(:per_page => members_per_page, :page => params[:npage]) | |
| 12 | + @members = profile.members | |
| 13 | 13 | @members_title = 'members' |
| 14 | 14 | end |
| 15 | + @members = @members.includes(relations_to_include).paginate(:per_page => members_per_page, :page => params[:npage], :total_entries => @members.count) | |
| 15 | 16 | end |
| 16 | 17 | render "profile/members" |
| 17 | 18 | end | ... | ... |
plugins/people_block/test/unit/friends_block_test.rb
| 1 | 1 | require File.dirname(__FILE__) + '/../test_helper' |
| 2 | 2 | |
| 3 | -class FriendsBlockTest < ActiveSupport::TestCase | |
| 3 | +class FriendsBlockTest < ActionView::TestCase | |
| 4 | 4 | |
| 5 | 5 | should 'inherit from Block' do |
| 6 | 6 | assert_kind_of Block, FriendsBlock.new |
| ... | ... | @@ -8,6 +8,7 @@ class FriendsBlockTest < ActiveSupport::TestCase |
| 8 | 8 | |
| 9 | 9 | |
| 10 | 10 | should 'declare its default title' do |
| 11 | + FriendsBlock.any_instance.expects(:profile_count).returns(0) | |
| 11 | 12 | assert_not_equal Block.new.default_title, FriendsBlock.new.default_title |
| 12 | 13 | end |
| 13 | 14 | |
| ... | ... | @@ -60,7 +61,7 @@ class FriendsBlockTest < ActiveSupport::TestCase |
| 60 | 61 | |
| 61 | 62 | |
| 62 | 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 | 65 | end |
| 65 | 66 | |
| 66 | 67 | |
| ... | ... | @@ -98,10 +99,10 @@ class FriendsBlockTest < ActiveSupport::TestCase |
| 98 | 99 | block = FriendsBlock.new |
| 99 | 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 | 106 | end |
| 106 | 107 | |
| 107 | 108 | ... | ... |
plugins/people_block/test/unit/members_block_test.rb
| 1 | 1 | require File.dirname(__FILE__) + '/../test_helper' |
| 2 | 2 | |
| 3 | -class MembersBlockTest < ActiveSupport::TestCase | |
| 3 | +class MembersBlockTest < ActionView::TestCase | |
| 4 | 4 | |
| 5 | 5 | should 'inherit from Block' do |
| 6 | 6 | assert_kind_of Block, MembersBlock.new |
| ... | ... | @@ -60,7 +60,7 @@ class MembersBlockTest < ActiveSupport::TestCase |
| 60 | 60 | |
| 61 | 61 | |
| 62 | 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 | 64 | end |
| 65 | 65 | |
| 66 | 66 | |
| ... | ... | @@ -145,10 +145,10 @@ class MembersBlockTest < ActiveSupport::TestCase |
| 145 | 145 | block.box = profile.boxes.first |
| 146 | 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 | 152 | end |
| 153 | 153 | |
| 154 | 154 | should 'provide link to members page with a selected role' do |
| ... | ... | @@ -158,10 +158,10 @@ class MembersBlockTest < ActiveSupport::TestCase |
| 158 | 158 | block.visible_role = 'profile_member' |
| 159 | 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 | 165 | end |
| 166 | 166 | |
| 167 | 167 | should 'provide a role to be displayed (and default to nil)' do | ... | ... |
plugins/people_block/test/unit/people_block_test.rb
| 1 | 1 | require File.dirname(__FILE__) + '/../test_helper' |
| 2 | 2 | |
| 3 | -class PeopleBlockTest < ActiveSupport::TestCase | |
| 3 | +class PeopleBlockTest < ActionView::TestCase | |
| 4 | 4 | |
| 5 | 5 | should 'inherit from Block' do |
| 6 | 6 | assert_kind_of Block, PeopleBlock.new |
| ... | ... | @@ -106,14 +106,12 @@ class PeopleBlockTest < ActiveSupport::TestCase |
| 106 | 106 | |
| 107 | 107 | should 'link to "all people"' do |
| 108 | 108 | env = fast_create(Environment) |
| 109 | - | |
| 110 | 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 | 115 | end |
| 118 | 116 | |
| 119 | 117 | ... | ... |
| ... | ... | @@ -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 @@ |
| 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 @@ |
| 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 @@ |
| 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 | 42 | \ No newline at end of file | ... | ... |