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 | ... | ... |