Commit 5cb9df6dfe3276be2d7b63585e7050bd2025283c

Authored by Victor Costa
2 parents d2f53367 538bb500

Merge branch 'master' into rails3_chat

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

Too many changes.

To preserve performance only 100 of 180 files displayed.

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 &lt;diegoamc90@gmail.com&gt;
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 &lt;diegoamc90@gmail.com&gt;
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 &lt;ludwig9003@gmail.com&gt;
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 &lt;rr.manzo@gmail.com&gt;
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 &lt;renanteruoc@gmail.com&gt;
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>
... ...
DEVELOPMENT.md 0 → 100644
... ... @@ -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 &lt; 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 &lt; 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 &lt; 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 &lt; 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
1 1 module RoleHelper
  2 +
  3 + def role_available_permissions(role)
  4 + role.kind == "Environment" ? ['Environment', 'Profile'] : [role.kind]
  5 + end
  6 +
2 7 end
... ...
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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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
... ... @@ -61,9 +61,6 @@
61 61 <%= submit_button(:search, _('Search')) %>
62 62 </div>
63 63  
64   - <div>
65   - <%= lightbox_link_to _('More options'), :controller => 'search', :action => 'popup' %>
66   - </div>
67 64 <% end %>
68 65 </div>
69 66 <% end %>
... ...
app/views/layouts/_content.html.erb 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +<div id="content-inner">
  2 + <%= insert_boxes(yield) %>
  3 + <br style='clear: both'/>
  4 +</div><!-- end id="content-inner" -->
... ...
app/views/layouts/application-ng.html.erb
... ... @@ -17,7 +17,7 @@
17 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/generic_message.text.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +<%= _('Dear %s,') % @requestor %>
  2 +
  3 +<%= word_wrap(@message) %>
  4 +
  5 +<%= _('Greetings,') %>
  6 +
  7 +--
  8 +<%= _('%s team.') % @environment %>
  9 +<%= @url %>
... ...
app/views/task_mailer/task_activated.text.erb
... ... @@ -1 +0,0 @@
1   -task_cancelled.text.erb
2 0 \ No newline at end of file
app/views/task_mailer/task_cancelled.text.erb
... ... @@ -1,9 +0,0 @@
1   -<%= _('Dear %s,') % @requestor %>
2   -
3   -<%= word_wrap(@message) %>
4   -
5   -<%= _('Greetings,') %>
6   -
7   ---
8   -<%= _('%s team.') % @environment %>
9   -<%= @url %>
app/views/task_mailer/task_created.text.erb
... ... @@ -1 +0,0 @@
1   -task_cancelled.text.erb
2 0 \ No newline at end of file
app/views/task_mailer/task_finished.text.erb
... ... @@ -1 +0,0 @@
1   -task_cancelled.text.erb
2 0 \ No newline at end of file
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 &lt; 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
... ... @@ -1,3 +0,0 @@
1   ----
2   -BUNDLE_WITHOUT: test:cucumber
3   -
debian/changelog
  1 +noosfero (1.0) wheezy; urgency=low
  2 +
  3 + * Noosfero 1.0 \o/
  4 +
  5 + -- Antonio Terceiro <terceiro@colivre.coop.br> Mon, 22 Dec 2014 10:52:21 -0300
  6 +
  7 +noosfero (1.0~rc4) wheezy-test; urgency=low
  8 +
  9 + * Fourth release candidate
  10 +
  11 + -- Antonio Terceiro <terceiro@colivre.coop.br> Wed, 19 Nov 2014 10:31:16 -0300
  12 +
1 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/filter-gemfile 0 → 100755
... ... @@ -0,0 +1,5 @@
  1 +#!/bin/sh
  2 +
  3 +set -e
  4 +
  5 +sed -e '/^group\s*:\(test\|cucumber\)/,/^end/ d' Gemfile
... ...
debian/noosfero.install
... ... @@ -7,9 +7,6 @@ util usr/share/noosfero
7 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
1 1 # See an example of this file at:
2 2 # /usr/share/doc/noosfero/examples/
3 3 production:
4   - gravatar: wavatar
  4 + gravatar: mm
... ...
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 &quot;(.*)&quot; 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
1 1 class LogMemoryConsumptionJob < Struct.new(:last_stat)
2 2 # Number of entries do display
3 3 N = 20
4   - # In seconds
5   - PERIOD = 10
6 4  
7 5 def perform
8 6 logpath = File.join(Rails.root, 'log', "#{ENV['RAILS_ENV']}_memory_consumption.log")
... ...
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
1 1 module Noosfero
2 2 PROJECT = 'noosfero'
3   - VERSION = '1.0~rc3'
  3 + VERSION = '1.0'
4 4 end
... ...
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
1 1 class UserActivationJob < Struct.new(:user_id)
2 2 def perform
3 3 user = User.find(user_id)
4   - user.destroy unless user.activated?
  4 + user.destroy unless user.activated? || user.person.is_template?
5 5 end
6 6 end
... ...
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 &quot;comment-count-#{@group_id}&quot;, @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 /\{&quot;Send an e-mail&quot;:\{&quot;href&quot;:&quot;\/contact\/#{@community.identifier}\/new&quot;\}\}/, @response.body
  53 + end
  54 +
  55 + should 'display *report* button when the user is logged in and is a member of the community' do
  56 + get :index, :profile => @community.identifier
  57 + assert_match /\{&quot;Report abuse&quot;:\{&quot;href&quot;:&quot;\/profile\/#{@community.identifier}\/report_abuse&quot;\}\}/, @response.body
  58 + end
  59 +
  60 + should 'display *join* button when the user is logged in and is not a member of the community' do
  61 + @community.remove_member @user
  62 + get :index, :profile => @community.identifier
  63 + assert_tag :span, :attributes => {:class => 'community-block-button icon-add'}
  64 + end
  65 +
  66 + should 'display *control panel* link option when the user is logged in and is community admin' do
  67 + get :index, :profile => @community.identifier
  68 + assert_match /\{&quot;Control panel&quot;:\{&quot;href&quot;:&quot;\/myprofile\/#{@community.identifier}&quot;\}\}/, @response.body
  69 + end
  70 +
  71 + should 'display *join* button when the user is not logged in' do
  72 + logout
  73 + get :index, :profile => @community.identifier
  74 + assert_tag :span, :attributes => {:class => 'community-block-button icon-add'}
  75 + end
  76 +
  77 + should 'not display *arrow* button when the user is not logged in' do
  78 + logout
  79 + get :index, :profile => @community.identifier
  80 + assert_no_tag :span, :attributes => {:class => 'community-block-button icon-arrow'}
  81 + end
  82 +
  83 +end
... ...
plugins/community_block/test/functional/commmunity_block_plugin_profile_design_controller_test.rb
... ... @@ -1,87 +0,0 @@
1   -require File.dirname(__FILE__) + '/../test_helper'
2   -
3   -# Re-raise errors caught by the controller.
4   -class ProfileController
5   - append_view_path File.join(File.dirname(__FILE__) + '/../../views')
6   - def rescue_action(e)
7   - raise e
8   - end
9   -end
10   -
11   -class ProfileControllerTest < ActionController::TestCase
12   -
13   - def setup
14   - @controller = ProfileController.new
15   - @request = ActionController::TestRequest.new
16   - @response = ActionController::TestResponse.new
17   -
18   - @user = create_user('testinguser').person
19   - login_as(@user.identifier)
20   -
21   - @community = fast_create(Community, :environment_id => Environment.default)
22   - @community.add_member @user
23   - @community.add_admin @user
24   -
25   - @environment = @community.environment
26   - @environment.enabled_plugins = ['CommunityBlock']
27   - @environment.save!
28   -
29   - CommunityBlock.delete_all
30   - @box1 = create(Box, :owner => @community)
31   - @community.boxes = [@box1]
32   -
33   - @block = CommunityBlock.new
34   - @block.box = @box1
35   - @block.save!
36   -
37   - @community.blocks<<@block
38   - @community.save!
39   - end
40   -
41   - should 'display community-block' do
42   - get :index, :profile => @community.identifier
43   - assert_tag :div, :attributes => {:class => 'community-block-logo'}
44   - assert_tag :div, :attributes => {:class => 'community-block-info'}
45   - assert_tag :div, :attributes => {:class => 'community-block-title'}
46   - assert_tag :div, :attributes => {:class => 'community-block-description'}
47   - end
48   -
49   - should 'display *leave* button when the user is logged in and is a member of the community' do
50   - get :index, :profile => @community.identifier
51   - assert_tag :span, :attributes => {:class => 'community-block-button icon-remove'}
52   - end
53   -
54   - should 'display *send email to administrators* button when the user is logged in and is a member of the community' do
55   - get :index, :profile => @community.identifier
56   - assert_match /\{&quot;Send an e-mail&quot;:\{&quot;href&quot;:&quot;\/contact\/#{@community.identifier}\/new&quot;\}\}/, @response.body
57   - end
58   -
59   - should 'display *report* button when the user is logged in and is a member of the community' do
60   - get :index, :profile => @community.identifier
61   - assert_match /\{&quot;Report abuse&quot;:\{&quot;href&quot;:&quot;\/profile\/#{@community.identifier}\/report_abuse&quot;\}\}/, @response.body
62   - end
63   -
64   - should 'display *join* button when the user is logged in and is not a member of the community' do
65   - @community.remove_member @user
66   - get :index, :profile => @community.identifier
67   - assert_tag :span, :attributes => {:class => 'community-block-button icon-add'}
68   - end
69   -
70   - should 'display *control panel* link option when the user is logged in and is community admin' do
71   - get :index, :profile => @community.identifier
72   - assert_match /\{&quot;Control panel&quot;:\{&quot;href&quot;:&quot;\/myprofile\/#{@community.identifier}&quot;\}\}/, @response.body
73   - end
74   -
75   - should 'display *join* button when the user is not logged in' do
76   - logout
77   - get :index, :profile => @community.identifier
78   - assert_tag :span, :attributes => {:class => 'community-block-button icon-add'}
79   - end
80   -
81   - should 'not display *arrow* button when the user is not logged in' do
82   - logout
83   - get :index, :profile => @community.identifier
84   - assert_no_tag :span, :attributes => {:class => 'community-block-button icon-arrow'}
85   - end
86   -
87   -end
plugins/community_block/views/community_block.html.erb
... ... @@ -23,7 +23,7 @@
23 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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__) + &#39;/../../../../../test/test_helper&#39;
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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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)
... ...
plugins/lattes_curriculum/README.md 0 → 100644
... ... @@ -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
... ... @@ -0,0 +1,12 @@
  1 +class CreateAcademicInfos < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :academic_infos do |t|
  4 + t.references :person
  5 + t.column :lattes_url, :string
  6 + end
  7 + end
  8 +
  9 + def self.down
  10 + drop_table :academic_infos
  11 + end
  12 +end
... ...
plugins/lattes_curriculum/features/lattes_curriculum.feature 0 → 100644
... ... @@ -0,0 +1,49 @@
  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
... ...
plugins/lattes_curriculum/lib/academic_info.rb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +class AcademicInfo < ActiveRecord::Base
  2 +
  3 + belongs_to :person
  4 +
  5 + attr_accessible :lattes_url
  6 + validate :lattes_url_validate?
  7 +
  8 + def lattes_url_validate?
  9 + unless AcademicInfo.matches?(self.lattes_url)
  10 + self.errors.add(:lattes_url, _(" is invalid."))
  11 + end
  12 + end
  13 +
  14 + def self.matches?(info)
  15 + lattes = nil
  16 + if info.class == String
  17 + lattes = info
  18 + elsif info.class == Hash
  19 + lattes = info[:lattes_url]
  20 + end
  21 + return lattes.blank? || lattes =~ /^http:\/\/lattes.cnpq.br\/\d+$/
  22 + end
  23 +end
... ...
plugins/lattes_curriculum/lib/ext/person.rb 0 → 100755
... ... @@ -0,0 +1,32 @@
  1 +require_dependency 'person'
  2 +
  3 +class Person
  4 +
  5 + attr_accessible :lattes_url, :academic_info_attributes
  6 +
  7 + has_one :academic_info
  8 +
  9 + after_destroy do |person|
  10 + if !person.environment.nil? &&
  11 +person.environment.plugin_enabled?(LattesCurriculumPlugin) &&
  12 +!person.academic_info.nil?
  13 + person.academic_info.destroy
  14 + end
  15 + end
  16 +
  17 + accepts_nested_attributes_for :academic_info
  18 +
  19 + def lattes_url
  20 + if self.environment && self.environment.plugin_enabled?(LattesCurriculumPlugin)
  21 + self.academic_info.nil? ? nil : self.academic_info.lattes_url
  22 + end
  23 + end
  24 +
  25 + def lattes_url= value
  26 + if self.environment && self.environment.plugin_enabled?(LattesCurriculumPlugin)
  27 + self.academic_info.lattes_url = value unless self.academic_info.nil?
  28 + end
  29 + end
  30 +
  31 + FIELDS << "lattes_url"
  32 +end
... ...
plugins/lattes_curriculum/lib/html_parser.rb 0 → 100755
... ... @@ -0,0 +1,64 @@
  1 +require 'rubygems'
  2 +require 'nokogiri'
  3 +require 'open-uri'
  4 +
  5 +Encoding.default_external = Encoding::UTF_8
  6 +Encoding.default_internal = Encoding::UTF_8
  7 +
  8 +class Html_parser
  9 + def get_html(lattes_link = "")
  10 + begin
  11 + page = Nokogiri::HTML(open(lattes_link), nil, "UTF-8")
  12 + page = page.css(".main-content").to_s()
  13 + page = remove_class_tooltip(page)
  14 + page = remove_img(page)
  15 + page = remove_select(page)
  16 + page = remove_footer(page)
  17 + page = remove_further_informations(page)
  18 + rescue OpenURI::HTTPError => e
  19 + page = _("Lattes not found. Please, make sure the informed URL is correct.")
  20 + rescue Timeout::Error => e
  21 + page = _("Lattes Platform is unreachable. Please, try it later.")
  22 + end
  23 + end
  24 +
  25 + def remove_class_tooltip(page = "")
  26 + while page.include? 'class="tooltip"' do
  27 + page['class="tooltip"'] = 'class="link_not_to_mark"'
  28 + end
  29 +
  30 + return page
  31 + end
  32 +
  33 + def remove_img(page = "")
  34 + fist_part_to_keep, *rest = page.split('<img')
  35 + second_part = rest.join(" ")
  36 + part_to_throw_away, *after_img = second_part.split('>',2)
  37 + page = fist_part_to_keep + after_img.join(" ")
  38 + end
  39 +
  40 + def remove_select(page = "")
  41 + while page.include? '<label' do
  42 + first_part_to_keep, *rest = page.split('<label')
  43 + second_part = rest.join(" ")
  44 + part_to_throw_away, *after_img = second_part.split('</select>')
  45 + page = first_part_to_keep + after_img.join(" ")
  46 + end
  47 +
  48 + return page
  49 + end
  50 +
  51 + def remove_footer(page = "")
  52 + first_part_to_keep, *rest = page.split('<div class="rodape-cv">')
  53 + second_part = rest.join(" ")
  54 + part_to_throw_away, *after_img = second_part.split('Imprimir Curr&iacute;culo</a>')
  55 + page = first_part_to_keep + after_img.join(" ")
  56 + end
  57 +
  58 + def remove_further_informations(page = "")
  59 + first_part_to_keep, *rest = page.split('<a name="OutrasI')
  60 + second_part = rest.join(" ")
  61 + part_to_throw_away, *after_img = second_part.split('</div>',2)
  62 + page = first_part_to_keep + after_img.join(" ")
  63 + end
  64 +end
... ...
plugins/lattes_curriculum/lib/lattes_curriculum_plugin.rb 0 → 100755
... ... @@ -0,0 +1,139 @@
  1 +class LattesCurriculumPlugin < Noosfero::Plugin
  2 +
  3 + def self.plugin_name
  4 + "Lattes Curriculum Plugin"
  5 + end
  6 +
  7 + def self.plugin_description
  8 + _("A plugin that imports the lattes curriculum into the users profiles")
  9 + end
  10 +
  11 + def js_files
  12 + ["singup_complement.js"]
  13 + end
  14 +
  15 + def stylesheet?
  16 + true
  17 + end
  18 +
  19 + def extra_optional_fields
  20 + fields = []
  21 +
  22 + lattes_url = {
  23 + :name => 'lattes_url',
  24 + :label => 'Lattes URL',
  25 + :object_name => :academic_infos,
  26 + :method => :lattes_url,
  27 + :value => context.profile.nil? ? "" : context.profile.academic_info.lattes_url
  28 + }
  29 +
  30 + fields << lattes_url
  31 +
  32 + return fields
  33 + end
  34 +
  35 + def profile_tabs
  36 + if show_lattes_tab?
  37 + href = context.profile.academic_info.lattes_url
  38 + html_parser = Html_parser.new
  39 + {
  40 + :title => _("Lattes"),
  41 + :id => 'lattes_tab',
  42 + :content => lambda{html_parser.get_html(href)},
  43 + :start => false
  44 + }
  45 + end
  46 + end
  47 +
  48 + def profile_editor_transaction_extras
  49 + if context.profile.person?
  50 + if context.params.has_key?(:academic_infos)
  51 + academic_info_transaction
  52 + end
  53 + end
  54 + end
  55 +
  56 + def profile_editor_controller_filters
  57 + validate_lattes_url_block = proc do
  58 + if request.post?
  59 + if !params[:academic_infos].blank?
  60 + @profile_data = profile
  61 +
  62 + academic_infos = {"academic_info_attributes" => params[:academic_infos]}
  63 +
  64 + params_profile_data = params[:profile_data]
  65 + params_profile_data = params_profile_data.merge(academic_infos)
  66 +
  67 + @profile_data.attributes = params_profile_data
  68 + @profile_data.valid?
  69 +
  70 + @possible_domains = profile.possible_domains
  71 +
  72 + unless AcademicInfo.matches?(params[:academic_infos])
  73 + @profile_data.errors.add(:lattes_url, _(' Invalid lattes url'))
  74 + render :action => :edit, :profile => profile.identifier
  75 + end
  76 + end
  77 + end
  78 + end
  79 +
  80 + create_academic_info_block = proc do
  81 + if profile && profile.person? && profile.academic_info.nil?
  82 + profile.academic_info = AcademicInfo.new
  83 + end
  84 + end
  85 +
  86 + [{:type => 'before_filter',
  87 + :method_name => 'validate_lattes_url',
  88 + :options => {:only => 'edit'},
  89 + :block => validate_lattes_url_block },
  90 + {:type => 'before_filter',
  91 + :method_name => 'create_academic_info',
  92 + :options => {:only => 'edit'},
  93 + :block => create_academic_info_block }]
  94 + end
  95 +
  96 + def account_controller_filters
  97 + validate_lattes_url_block = proc do
  98 + if request.post?
  99 + params[:profile_data] ||= {}
  100 + params[:profile_data][:academic_info_attributes] = params[:academic_infos]
  101 +
  102 + if !params[:academic_infos].blank? && !AcademicInfo.matches?(params[:academic_infos])
  103 + @person = Person.new(params[:profile_data])
  104 + @person.environment = environment
  105 + @user = User.new(params[:user])
  106 + @person.errors.add(:lattes_url, _(' Invalid lattes url'))
  107 + render :action => :signup
  108 + end
  109 + end
  110 + end
  111 +
  112 + create_academic_info_block = proc do
  113 + if profile && profile.person? && profile.academic_info.nil?
  114 + profile.academic_info = AcademicInfo.new
  115 + end
  116 + end
  117 +
  118 + [{:type => 'before_filter',
  119 + :method_name => 'validate_lattes_url',
  120 + :options => {:only => 'signup'},
  121 + :block => validate_lattes_url_block },
  122 + {:type => 'before_filter',
  123 + :method_name => 'create_academic_info',
  124 + :options => {:only => 'edit'},
  125 + :block => create_academic_info_block }]
  126 + end
  127 +
  128 + protected
  129 +
  130 + def academic_info_transaction
  131 + AcademicInfo.transaction do
  132 + context.profile.academic_info.update_attributes!(context.params[:academic_infos])
  133 + end
  134 + end
  135 +
  136 + def show_lattes_tab?
  137 + return context.profile.person? && !context.profile.academic_info.nil? && !context.profile.academic_info.lattes_url.blank? && context.profile.public_fields.include?("lattes_url")
  138 + end
  139 +end
... ...
plugins/lattes_curriculum/public/images/grey-bg.png 0 → 100644
... ... @@ -0,0 +1,12 @@
  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
... ...
plugins/lattes_curriculum/public/singup_complement.js 0 → 100644
... ... @@ -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
... ...
plugins/lattes_curriculum/public/style.css 0 → 100644
... ... @@ -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
... ...
plugins/lattes_curriculum/test/unit/html_parser_test.rb 0 → 100644
... ... @@ -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
... ...
plugins/lattes_curriculum/test/unit/person_test.rb 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +require "test_helper"
  2 +
  3 +class PersonTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @environment = Environment.default
  7 + @environment.enable_plugin(LattesCurriculumPlugin)
  8 + end
  9 +
  10 + attr_reader :environment
  11 +
  12 + should 'destroy academic info if person is removed' do
  13 + person = fast_create(Person)
  14 + academic_info = fast_create(AcademicInfo, :person_id => person.id,
  15 +:lattes_url => 'http://lattes.cnpq.br/2193972715230')
  16 +
  17 + assert_difference 'AcademicInfo.count', -1 do
  18 + person.destroy
  19 + end
  20 + end
  21 +
  22 + should 'add lattes_url field to Person' do
  23 + assert_includes Person.fields, 'lattes_url'
  24 + end
  25 +
  26 +end
... ...
plugins/people_block/controllers/people_block_plugin_profile_controller.rb
... ... @@ -6,12 +6,13 @@ class PeopleBlockPluginProfileController &lt; ProfileController
6 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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  
... ...
plugins/pjax/lib/pjax_plugin.rb 0 → 100644
... ... @@ -0,0 +1,52 @@
  1 +class PjaxPlugin < Noosfero::Plugin
  2 +
  3 + def self.plugin_name
  4 + I18n.t('pjax_plugin.lib.plugin.name')
  5 + end
  6 +
  7 + def self.plugin_description
  8 + I18n.t('pjax_plugin.lib.plugin.description')
  9 + end
  10 +
  11 + def stylesheet?
  12 + true
  13 + end
  14 +
  15 + def js_files
  16 + ['jquery.pjax.js', 'patchwork.js', 'loading-overlay', 'pjax', ].map{ |j| "javascripts/#{j}" }
  17 + end
  18 +
  19 + def head_ending
  20 + #TODO: add pjax meta
  21 + end
  22 +
  23 + def body_beginning
  24 + lambda{ render 'pjax_layouts/load_state_script' }
  25 + end
  26 +
  27 + PjaxCheck = lambda do
  28 + return unless request.headers['X-PJAX']
  29 + # raise makes pjax fallback to a regular request
  30 + raise "Pjax can't be used here" if params[:controller] == 'account'
  31 +
  32 + @pjax = true
  33 + @pjax_loaded_themes = request.headers['X-PJAX-Themes'].to_s.split(',') || []
  34 +
  35 + unless self.respond_to? :get_layout_with_pjax
  36 + self.class.send :define_method, :get_layout_with_pjax do
  37 + if @pjax then 'pjax' else get_layout_without_pjax end
  38 + end
  39 + self.class.alias_method_chain :get_layout, :pjax
  40 + end
  41 + end
  42 +
  43 + def application_controller_filters
  44 + [{
  45 + :type => 'before_filter', :method_name => 'pjax_check',
  46 + :options => {}, :block => PjaxCheck,
  47 + }]
  48 + end
  49 +
  50 + protected
  51 +
  52 +end
... ...
plugins/pjax/locales/en.yml 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +
  2 +"en": &en-US
  3 + pjax_plugin:
  4 + lib:
  5 + plugin:
  6 + name: "pjax plugin"
  7 + description: "Use pjax for page's links"
  8 +
  9 +'en_US':
  10 + <<: *en-US
  11 +'en-US':
  12 + <<: *en-US
... ...
plugins/pjax/locales/pt.yml 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +
  2 +"pt": &pt-BR
  3 + pjax_plugin:
  4 + lib:
  5 + plugin:
  6 + name: "pjax plugin"
  7 + description: "Usa o pjax para os links da página"
  8 +
  9 +'pt_BR':
  10 + <<: *pt-BR
  11 +'pt-BR':
  12 + <<: *pt-BR
  13 +
... ...
plugins/pjax/public/images/loading-gears.gif 0 → 100644

617 KB

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

3.4 KB

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