Commit 4f216c9868aab1ffa9ff34ea4df7b1f7f116e93f

Authored by Evandro Junior
2 parents 880d6e8c c456fe22

Merge branch 'master' into I4-Video-Gallery-Plugin

Conflicts:
	app/views/content_viewer/_article_toolbar.html.erb
Showing 1068 changed files with 44887 additions and 53175 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 1068 files displayed.

@@ -40,6 +40,7 @@ Alessandro Palmeira + João M. M. Silva <alessandro.palmeira@gmail.com> @@ -40,6 +40,7 @@ Alessandro Palmeira + João M. M. Silva <alessandro.palmeira@gmail.com>
40 Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com> 40 Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com>
41 Alessandro Palmeira + Paulo Meirelles + João M. M. da Silva <alessandro.palmeira@gmail.com> 41 Alessandro Palmeira + Paulo Meirelles + João M. M. da Silva <alessandro.palmeira@gmail.com>
42 Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com> 42 Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com>
  43 +analosnak <analosnak@gmail.com>
43 Ana Losnak <analosnak@gmail.com> 44 Ana Losnak <analosnak@gmail.com>
44 Andre Bernardes <andrebsguedes@gmail.com> 45 Andre Bernardes <andrebsguedes@gmail.com>
45 Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br> 46 Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br>
@@ -81,7 +82,6 @@ Carlos Morais + Diego Araújo &lt;diegoamc90@gmail.com&gt; @@ -81,7 +82,6 @@ Carlos Morais + Diego Araújo &lt;diegoamc90@gmail.com&gt;
81 Carlos Morais + Eduardo Morais <carlos88morais@gmail.com> 82 Carlos Morais + Eduardo Morais <carlos88morais@gmail.com>
82 Carlos Morais + Paulo Meirelles <carlos88morais@gmail.com> 83 Carlos Morais + Paulo Meirelles <carlos88morais@gmail.com>
83 Carlos Morais + Pedro Leal <carlos88morais@gmail.com> 84 Carlos Morais + Pedro Leal <carlos88morais@gmail.com>
84 -Daniela Feitosa <dani@dohko.(none)>  
85 Daniel Alves + Diego Araújo <danpaulalves@gmail.com> 85 Daniel Alves + Diego Araújo <danpaulalves@gmail.com>
86 Daniel Alves + Diego Araújo <diegoamc90@gmail.com> 86 Daniel Alves + Diego Araújo <diegoamc90@gmail.com>
87 Daniel Alves + Diego Araújo + Guilherme Rojas <danpaulalves@gmail.com> 87 Daniel Alves + Diego Araújo + Guilherme Rojas <danpaulalves@gmail.com>
@@ -119,7 +119,6 @@ Diego Araújo + Renan Teruo &lt;diegoamc90@gmail.com&gt; @@ -119,7 +119,6 @@ Diego Araújo + Renan Teruo &lt;diegoamc90@gmail.com&gt;
119 Diego Araujo + Rodrigo Souto + Rafael Manzo <rr.manzo@gmail.com> 119 Diego Araujo + Rodrigo Souto + Rafael Manzo <rr.manzo@gmail.com>
120 Diego + Jefferson <diegoamc90@gmail.com> 120 Diego + Jefferson <diegoamc90@gmail.com>
121 Diego Martinez <diegoamc90@gmail.com> 121 Diego Martinez <diegoamc90@gmail.com>
122 -Diego Martinez <diego@diego-K55A.(none)>  
123 Diego + Renan <renanteruoc@gmail.com> 122 Diego + Renan <renanteruoc@gmail.com>
124 Eduardo Tourinho Edington <eduardo.edington@serpro.gov.br> 123 Eduardo Tourinho Edington <eduardo.edington@serpro.gov.br>
125 Evandro Jr <evandrojr@gmail.com> 124 Evandro Jr <evandrojr@gmail.com>
@@ -195,9 +194,11 @@ Luis David Aguilar Carlos &lt;ludwig9003@gmail.com&gt; @@ -195,9 +194,11 @@ Luis David Aguilar Carlos &lt;ludwig9003@gmail.com&gt;
195 Luiz Fernando de Freitas Matos <luiz@luizff.matos@gmail.com> 194 Luiz Fernando de Freitas Matos <luiz@luizff.matos@gmail.com>
196 Marcos Ramos <ms.ramos@outlook.com> 195 Marcos Ramos <ms.ramos@outlook.com>
197 Martín Olivera <molivera@solar.org.ar> 196 Martín Olivera <molivera@solar.org.ar>
  197 +Michal Čihař <michal@cihar.com>
198 Moises Machado <moises@colivre.coop.br> 198 Moises Machado <moises@colivre.coop.br>
199 Naíla Alves <naila@colivre.coop.br> 199 Naíla Alves <naila@colivre.coop.br>
200 Nanda Lopes <nanda.listas+psl@gmail.com> 200 Nanda Lopes <nanda.listas+psl@gmail.com>
  201 +Parley Martins <parleypachecomartins@gmail.com>
201 Paulo Meirelles + Alessandro Palmeira + João M. M. da Silva <paulo@softwarelivre.org> 202 Paulo Meirelles + Alessandro Palmeira + João M. M. da Silva <paulo@softwarelivre.org>
202 Paulo Meirelles + Alessandro Palmeira <paulo@softwarelivre.org> 203 Paulo Meirelles + Alessandro Palmeira <paulo@softwarelivre.org>
203 Paulo Meirelles + Carlos Morais <paulo@softwarelivre.org> 204 Paulo Meirelles + Carlos Morais <paulo@softwarelivre.org>
@@ -220,6 +221,7 @@ Rafael Reggiani Manzo + João M. M. da Silva &lt;rr.manzo@gmail.com&gt; @@ -220,6 +221,7 @@ Rafael Reggiani Manzo + João M. M. da Silva &lt;rr.manzo@gmail.com&gt;
220 Rafael Reggiani Manzo <rr.manzo@gmail.com> 221 Rafael Reggiani Manzo <rr.manzo@gmail.com>
221 Raphaël Rousseau <raph@r4f.org> 222 Raphaël Rousseau <raph@r4f.org>
222 Raquel Lira <raquel.lira@gmail.com> 223 Raquel Lira <raquel.lira@gmail.com>
  224 +Raquel <rcordioli@gmail.com>
223 Renan Teruo + Caio Salgado <renanteruoc@gmail.com> 225 Renan Teruo + Caio Salgado <renanteruoc@gmail.com>
224 Renan Teruoc + Diego Araujo <renanteruoc@gmail.com> 226 Renan Teruoc + Diego Araujo <renanteruoc@gmail.com>
225 Renan Teruo + Diego Araujo <renanteruoc@gmail.com> 227 Renan Teruo + Diego Araujo <renanteruoc@gmail.com>
@@ -227,13 +229,13 @@ Renan Teruo + Diego Araújo &lt;renanteruoc@gmail.com&gt; @@ -227,13 +229,13 @@ Renan Teruo + Diego Araújo &lt;renanteruoc@gmail.com&gt;
227 Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com> 229 Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com>
228 Renan Teruo + Rafael Manzo <renanteruoc@gmail.com> 230 Renan Teruo + Rafael Manzo <renanteruoc@gmail.com>
229 Rodrigo Souto + Ana Losnak + Daniel Bucher + Caio Almeida + Leandro Nunes + Daniela Feitosa + Mariel Zasso <noosfero-br@listas.softwarelivre.org> 231 Rodrigo Souto + Ana Losnak + Daniel Bucher + Caio Almeida + Leandro Nunes + Daniela Feitosa + Mariel Zasso <noosfero-br@listas.softwarelivre.org>
230 -Rodrigo Souto <diguliu@gmail.com>  
231 Rodrigo Souto <rodrigo@colivre.coop.br> 232 Rodrigo Souto <rodrigo@colivre.coop.br>
232 Ronny Kursawe <kursawe.ronny@googlemail.com> 233 Ronny Kursawe <kursawe.ronny@googlemail.com>
233 root <root@debian.sdr.serpro> 234 root <root@debian.sdr.serpro>
234 Samuel R. C. Vale <srcvale@holoscopio.com> 235 Samuel R. C. Vale <srcvale@holoscopio.com>
235 Tallys Martins <tallysmartins@gmail.com> 236 Tallys Martins <tallysmartins@gmail.com>
236 tallys <tallys@tallys.(none)> 237 tallys <tallys@tallys.(none)>
  238 +Thiago Zoroastro <thiago.zoroastro@bol.com.br>
237 Valessio Brito <contato@valessiobrito.com.br> 239 Valessio Brito <contato@valessiobrito.com.br>
238 Valessio Brito <contato@valessiobrito.info> 240 Valessio Brito <contato@valessiobrito.info>
239 Valessio Brito <valessio@gmail.com> 241 Valessio Brito <valessio@gmail.com>
DEVELOPMENT.md 0 → 100644
@@ -0,0 +1,124 @@ @@ -0,0 +1,124 @@
  1 +# Noosfero Development Policy
  2 +
  3 +## Developer Roles
  4 +
  5 +* *Developers* are everyone that is contributing code to Noosfero.
  6 +* *Committers* are the people with direct commit access to the Noosfero source
  7 + code. They are responsible for reviewing contributions from other developers
  8 + and integrating them in the Noosfero code base. They are the members of the
  9 + [Noosfero group on Gitlab](https://gitlab.com/groups/noosfero/members).
  10 +* *Release managers* are the people that are managing the release of a new
  11 + Noosfero version and/or the maintainance work of an existing Noosfero stable
  12 + branch. See MAINTAINANCE.md for details on the maintaince policy.
  13 +
  14 +## Development process
  15 +
  16 +* Every new feature or non-trivial bugfix should be reviewed by at least one
  17 + committer. This must be the case even if the original author is a committer.
  18 +
  19 + * In the case the original author is a committer, he/she should feel free to
  20 + commit directly if after 1 week nobody has provided any kind of feedback.
  21 +
  22 + * Developers who are not committers should feel free to ping committers if
  23 + they do not get feedback on their contributions after 1 week.
  24 +
  25 + * On GitLab, one can just add a comment to the merge request; one can also
  26 + @-mention specific committers or other developers who have expertise on
  27 + the area of the contribution.
  28 +
  29 + * Committers should follow the activity of the project, and try to help
  30 + reviewing contributions from others as much as possible.
  31 +
  32 + * On GitLab one can get emails for all activity on a project by setting the
  33 + [notification settings](https://gitlab.com/profile/notifications) to
  34 + "watch".
  35 +
  36 + * Anyone can help by reviewing contributions. Committers are the only ones
  37 + who can give the final approval to a contribution, but everyone is welcome
  38 + to help with code review, testing, etc.
  39 +
  40 + * See note above about setting up notification on GitLab.
  41 +
  42 +* Committers should feel free to push trivial (or urgent) changes directly.
  43 + There are no strict rule on what makes a change trivial or urgent; committers
  44 + are expected to exercise good judgement on a case by case basis.
  45 +
  46 + * Usually changes to the database are not trivial.
  47 +
  48 +* In the case of unsolvable conflict between commiters regarding any change to
  49 + the code, the current release manager(s) will have the final say in the
  50 + matter.
  51 +
  52 +* Release managers are responsible for stablishing a release schedule, and
  53 + about deciding when and what to release.
  54 +
  55 + * Release managers should announce release schedules to the project mailing
  56 + lists in advance.
  57 +
  58 + * The release schedule may include a period of feature freeze, during which
  59 + no new features or any other changes that are not pre-approved by the
  60 + release manager must be committed to the repository.
  61 +
  62 + * Committers must respect the release schedule and feature freezes.
  63 +
  64 +## Maintainance process
  65 +
  66 +### Not all feature releases will be maintained as a stable release
  67 +
  68 +We will be choosing specific release series to be maintained as stable
  69 +releases.
  70 +
  71 +This means that a given release is not guaranteed to be maintained as a stable
  72 +release, but does *not* mean it won't be. Any committer (or anyone, really) can
  73 +decide to maintain a given release as stable and seek help from others to do
  74 +so.
  75 +
  76 +### No merges from stable branches to master
  77 +
  78 +*All* changes must be submitted against the master branch first, and when
  79 +applicable, backported to the desired stable releases. Exceptions to this rules
  80 +are bug fixes that only apply to a given stable branch and not to master.
  81 +
  82 +In the past we had non-trivial changes accepted into stable releases while
  83 +master was way ahead (e.g. during the rails3 migration period), that made the
  84 +merge back into master very painful. By eliminating the need to do these
  85 +merges, we save time for the people responsible for the release, and eliminate
  86 +the possibility of human errors or oversights causing changes to be accepted
  87 +into stable that will be a problem to merge back into master.
  88 +
  89 +By getting all fixes in master first, we improve the chances that a future
  90 +release will not present regressions against bugs that should already be fixed,
  91 +but the fixes got lost in a big, complicated merge (and those won't exist
  92 +anymore, at least not from stable branches to master).
  93 +
  94 +After a fix gets into master, backporting changes into a stable release branch
  95 +is the responsibility of whoever is maintaing that branch, and those interested
  96 +in it. The stable branch release manager(s) are entitled the final say on any
  97 +matters related to that branch.
  98 +
  99 +## Apendix A: how to become a committer
  100 +
  101 +Every developer that wants to be a committer should create [an issue on
  102 +Gitlab](https://gitlab.com/noosfero/noosfero/issues) requesting to be added as
  103 +a committer. This request must include information about the requestor's
  104 +previous contributions to the project.
  105 +
  106 +If 2 or more commiters consider second the request, the requestor is accepted
  107 +as new commiter and added to the Noosfero group.
  108 +
  109 +The existing committers are free to choose whatever criteria they want to
  110 +second the request, but they must be sure that the new committer is a
  111 +responsible developer and knows what she/he is doing. They must be aware that
  112 +seconding these requests means seconding the actions of the new committer: if
  113 +the new committer screw up, her/his seconds screwed up.
  114 +
  115 +## Apendix B: how to become a release manager
  116 +
  117 +A new release manager for the development version of Noosfero (i.e. the one
  118 +that includes new features, a.k.a. the master branch) is apointed by the
  119 +current release manager, and must be a committer first.
  120 +
  121 +Release managers for stable branches are self-appointed, i.e. whoever takes the
  122 +work takes the role. In case of a conflict (e.g. 2+ different people want to do
  123 +the work but can't agree on working together), the development release manager
  124 +decides.
1 source "https://rubygems.org" 1 source "https://rubygems.org"
2 -gem 'rails', '~> 3.2.19' 2 +gem 'rails', '~> 3.2.21'
  3 +gem 'minitest', '~> 3.2.0'
3 gem 'fast_gettext', '~> 0.6.8' 4 gem 'fast_gettext', '~> 0.6.8'
4 gem 'acts-as-taggable-on', '~> 3.0.2' 5 gem 'acts-as-taggable-on', '~> 3.0.2'
5 gem 'prototype-rails', '~> 3.2.1' 6 gem 'prototype-rails', '~> 3.2.1'
@@ -18,6 +19,9 @@ gem &#39;rake&#39;, :require =&gt; false @@ -18,6 +19,9 @@ gem &#39;rake&#39;, :require =&gt; false
18 gem 'rest-client', '~> 1.6.7' 19 gem 'rest-client', '~> 1.6.7'
19 gem 'exception_notification', '~> 4.0.1' 20 gem 'exception_notification', '~> 4.0.1'
20 gem 'gettext', '~> 2.2.1', :require => false, :group => :development 21 gem 'gettext', '~> 2.2.1', :require => false, :group => :development
  22 +gem 'locale', '~> 2.0.5'
  23 +
  24 +gem 'whenever', :require => false
21 25
22 # FIXME list here all actual dependencies (i.e. the ones in debian/control), 26 # FIXME list here all actual dependencies (i.e. the ones in debian/control),
23 # with their GEM names (not the Debian package names) 27 # with their GEM names (not the Debian package names)
@@ -40,8 +44,9 @@ group :cucumber do @@ -40,8 +44,9 @@ group :cucumber do
40 gem 'selenium-webdriver', '~> 2.39.0' 44 gem 'selenium-webdriver', '~> 2.39.0'
41 end 45 end
42 46
43 -# include plugin gemfiles  
44 -Dir.glob(File.join('config', 'plugins', '*')).each do |plugin|  
45 - plugin_gemfile = File.join(plugin, 'Gemfile')  
46 - eval File.read(plugin_gemfile) if File.exists?(plugin_gemfile) 47 +# include gemfiles from enabled plugins
  48 +# plugins in baseplugins/ are not included on purpose. They should not have any
  49 +# dependencies.
  50 +Dir.glob('config/plugins/*/Gemfile').each do |gemfile|
  51 + eval File.read(gemfile)
47 end 52 end
INSTALL.https.md
1 -Setup Noosfero to use HTTPS  
2 -=========================== 1 +# Setup Noosfero to use HTTPS
3 2
4 This document assumes that you have a fully and clean Noosfero 3 This document assumes that you have a fully and clean Noosfero
5 installation as explained at the `INSTALL.md` file. 4 installation as explained at the `INSTALL.md` file.
6 5
7 -SSL certificate  
8 -+++++++++++++++ 6 +## Creating a self-signed SSL certificate
9 7
10 You should get a valid SSL certificate, but if you want to test 8 You should get a valid SSL certificate, but if you want to test
11 your setup before, you could generate a self-signed certificate 9 your setup before, you could generate a self-signed certificate
@@ -17,99 +15,106 @@ as below: @@ -17,99 +15,106 @@ as below:
17 # openssl req -new -x509 -nodes -sha1 -days $[10*365] -key noosfero.key > noosfero.cert 15 # openssl req -new -x509 -nodes -sha1 -days $[10*365] -key noosfero.key > noosfero.cert
18 # cat noosfero.key noosfero.cert > noosfero.pem 16 # cat noosfero.key noosfero.cert > noosfero.pem
19 17
  18 +## Web server configuration
  19 +
20 There are two ways of using SSL with Noosfero: 1) If you are not using 20 There are two ways of using SSL with Noosfero: 1) If you are not using
21 Varnish; and 2) If you are using Varnish. 21 Varnish; and 2) If you are using Varnish.
22 22
23 -1) If you are are not using Varnish  
24 -+++++++++++++++++++++++++++++++++++ 23 +### 1) If you are are not using Varnish
25 24
26 Simply do a redirect in apache to force all connections with SSL: 25 Simply do a redirect in apache to force all connections with SSL:
27 26
28 - <VirtualHost *:8080>  
29 - ServerName test.stoa.usp.br  
30 -  
31 - Redirect / https://example.com/  
32 - </VirtualHost> 27 +```
  28 +<VirtualHost *:8080>
  29 + ServerName test.stoa.usp.br
  30 + Redirect / https://example.com/
  31 +</VirtualHost>
  32 +```
33 33
34 And set a vhost to receive then: 34 And set a vhost to receive then:
35 35
36 - <VirtualHost *:443>  
37 - ServerName example.com  
38 -  
39 - SSLEngine On  
40 - SSLCertificateFile /etc/ssl/certs/cert.pem  
41 - SSLCertificateKeyFile /etc/ssl/private/cert.key  
42 -  
43 - Include /etc/noosfero/apache/virtualhost.conf  
44 - </VirtualHost> 36 +```
  37 +<VirtualHost *:443>
  38 + ServerName example.com
  39 + SSLEngine On
  40 + SSLCertificateFile /etc/ssl/certs/cert.pem
  41 + SSLCertificateKeyFile /etc/ssl/private/cert.key
  42 + Include /etc/noosfero/apache/virtualhost.conf
  43 +</VirtualHost>
  44 +```
45 45
46 Be aware that if you had configured varnish, the requests won't reach 46 Be aware that if you had configured varnish, the requests won't reach
47 it with this configuration. 47 it with this configuration.
48 48
49 -2) If you are using Varnish  
50 -+++++++++++++++++++++++++++  
51 -  
52 -Varnish isn't able to communicate with the SSL protocol, so we will  
53 -need some one who do this and Pound[1] can do the job. In order to  
54 -install it in Debian based systems: 49 +### 2) If you are using Varnish
55 50
56 - $ sudo apt-get install pound 51 +Varnish isn't able to communicate with the SSL protocol, so we will need some
  52 +one else who do this and [Pound](http://www.apsis.ch/pound) can do the job. In
  53 +order to install it in Debian based systems:
57 54
58 -Set Varnish to listen in other port than 80: 55 +```
  56 +$ sudo apt-get install pound
  57 +```
59 58
60 -/etc/defaults/varnish  
61 ---------------------- 59 +Set Varnish to listen in other port than 80 in `/etc/defaults/varnish`:
62 60
63 - DAEMON_OPTS="-a localhost:6081 \  
64 - -T localhost:6082 \  
65 - -f /etc/varnish/default.vcl \  
66 - -S /etc/varnish/secret \  
67 - -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,1G" 61 +```
  62 +DAEMON_OPTS="-a localhost:6081 \
  63 + -T localhost:6082 \
  64 + -f /etc/varnish/default.vcl \
  65 + -S /etc/varnish/secret \
  66 + -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,1G"
  67 +```
68 68
69 Configure Pound: 69 Configure Pound:
70 70
71 - # cp /usr/share/noosfero/etc/pound.cfg /etc/pound/  
72 -  
73 -Edit /etc/pound.cfg and set the IP and domain of your server. 71 +```
  72 +# cp /usr/share/noosfero/etc/pound.cfg /etc/pound/
  73 +```
74 74
75 -Configure Pound to start at system initialization: 75 +Edit `/etc/pound.cfg` and set the IP and domain of your server.
76 76
77 -/etc/default/pound 77 +Configure Pound to start at system initialization. At `/etc/default/pound`:
78 ------------------ 78 ------------------
79 79
80 - startup=1 80 +```
  81 +startup=1
  82 +```
81 83
82 -Set Apache to only listen to localhost: 84 +Set Apache to only listen to localhost, at `/etc/apache2/ports.conf`:
83 85
84 -/etc/apache2/ports.conf  
85 ------------------------  
86 -  
87 - Listen 127.0.0.1:8080 86 +```
  87 +Listen 127.0.0.1:8080
  88 +```
88 89
89 Restart the services: 90 Restart the services:
90 91
91 - $ sudo service apache2 restart  
92 - $ sudo service varnish restart 92 +```
  93 +$ sudo service apache2 restart
  94 +$ sudo service varnish restart
  95 +```
93 96
94 Start pound: 97 Start pound:
95 98
96 - $ sudo service pound start  
97 -  
98 -[1] http://www.apsis.ch/pound 99 +```
  100 +$ sudo service pound start
  101 +```
99 102
100 -Noosfero XMPP chat  
101 -++++++++++++++++++ 103 +## Noosfero XMPP chat
102 104
103 If you want to use chat over HTTPS, then you should add the domain 105 If you want to use chat over HTTPS, then you should add the domain
104 -and IP of your server in the /etc/hosts file, example: 106 +and IP of your server in the /etc/hosts file, example
105 107
106 -/etc/hosts  
107 ----------- 108 +`/etc/hosts:`
108 109
109 - 192.168.1.86 mydomain.example.com 110 +```
  111 +192.168.1.86 mydomain.example.com
  112 +```
110 113
111 -Also, it's recomended that you remove lines above from the file 114 +Also, it's recomended that you remove the lines below from the file
112 `/etc/apache2/sites-enabled/noosfero`: 115 `/etc/apache2/sites-enabled/noosfero`:
113 116
114 - RewriteEngine On  
115 - Include /usr/share/noosfero/util/chat/apache/xmpp.conf 117 +```
  118 +RewriteEngine On
  119 +Include /usr/share/noosfero/util/chat/apache/xmpp.conf
  120 +```
INSTALL.locales.md 0 → 100644
@@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
  1 +Using custom locales
  2 +====================
  3 +
  4 +Personalized translations go into the `config/custom_locales/` directory.
  5 +Custom locales can be identified by the rails environment, schema name in a
  6 +multitenancy setup or domain name until the first dot (e.g env1.coop.br for the
  7 +example below).
  8 +
  9 +Currently, the only filename prefix for the localization file which is
  10 +processed is "environment". For instance, a POT file would be called
  11 +"environment.pot".
  12 +
  13 +The structure of an environment named env1 with custom translations for both
  14 +Portuguese and Spanish and an environment named env2 with custom Russian
  15 +translation would be:
  16 +
  17 + config/
  18 + custom_locales/
  19 + env1/
  20 + environment.pot
  21 + pt/
  22 + environment.po
  23 + es/
  24 + environment.po
  25 + env2/
  26 + environment.pot
  27 + ru/
  28 + environment.po
  29 +
MIGRATION_ISSUES
@@ -1,41 +0,0 @@ @@ -1,41 +0,0 @@
1 -* ruby-get-text incmopatible with rails3. Maybe we can use it's gem  
2 -  
3 -* all js code is inside miscellaneous.js. Would be nice to refactor this  
4 -  
5 -* rails 2 uses prototype instead of jquery  
6 -  
7 -* config/environment.rb maybe still have some code that should be on the initializers  
8 -  
9 -* initializers session_store.rb inflections.rb... don't exist  
10 -  
11 -* rails gems version have to be forced on Gemfile or it will use incompatible pre3vious versions (3.1.3)  
12 -  
13 -* Sweepers are now natively supported on Rails 3. Would be nice to refactor it  
14 -  
15 -* On Rails 3 it is no more possible to add allowed tags to avoid scape. The html_safe initializer is an option.  
16 -  
17 -* error when call sqlite_extensiosn  
18 -  
19 -* error related to action_tracker  
20 -  
21 -* check FIXME's in script/quick-start  
22 -  
23 -* check FIXME's in Gemfile  
24 -  
25 -* Check the FIXME in config/routes.rb  
26 -  
27 -* rewrite conditional routing. See FIXME in lib/route_if.rb and re-implement using the Rails 3 mechanism - http://guides.rubyonrails.org/routing.html#advanced-constraints  
28 -  
29 -* check FIXME's in config/environment.rb  
30 -  
31 -* xss_terminate sucks. We should replace it with the builtin mechanism in Rails 3  
32 -  
33 -* instance_eval on Ruby 1.9 yields self, so lambdas that are passed to instance_eval and do not accept exactly 1 argument will blow up. See http://www.ruby-forum.com/topic/213313 ... search for instance_eval and fix where necessary. In special, most of the blocks still need fixing.  
34 -  
35 -* all instances of <% *_form_for ... %> must be changed to <%= instead of <%  
36 -  
37 -* all ActiveRecord models have to declare explicitly which attributes must be allowed for mass assignment with attr_accessible.  
38 -  
39 -* check if we need to update config/locales/*  
40 -  
41 -* check observe_field and labelled_form_for in app/helpers/application_helper.rb  
app/controllers/admin/admin_panel_controller.rb
@@ -71,4 +71,22 @@ class AdminPanelController &lt; AdminController @@ -71,4 +71,22 @@ class AdminPanelController &lt; AdminController
71 end 71 end
72 end 72 end
73 end 73 end
  74 +
  75 + def manage_organizations_status
  76 + scope = environment.organizations
  77 + @filter = params[:filter] || 'any'
  78 + @title = "Organization profiles"
  79 + @title = @title+" - "+@filter if @filter != 'any'
  80 +
  81 + if @filter == 'enabled'
  82 + scope = scope.visible
  83 + elsif @filter == 'disabled'
  84 + scope = scope.disabled
  85 + end
  86 +
  87 + scope = scope.order('name ASC')
  88 +
  89 + @q = params[:q]
  90 + @collection = find_by_contents(:organizations, scope, @q, {:per_page => 10, :page => params[:npage]})[:results]
  91 + end
74 end 92 end
app/controllers/admin/plugin_admin_controller.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +class PluginAdminController < AdminController
  2 +
  3 + protect 'edit_environment_features', :environment
  4 +
  5 +end
app/controllers/admin/region_validators_controller.rb
@@ -33,7 +33,7 @@ class RegionValidatorsController &lt; AdminController @@ -33,7 +33,7 @@ class RegionValidatorsController &lt; AdminController
33 def load_region_and_search 33 def load_region_and_search
34 @region = environment.regions.find(params[:id]) 34 @region = environment.regions.find(params[:id])
35 if params[:search] 35 if params[:search]
36 - @search = find_by_contents(:organizations, Organization, params[:search])[:results].reject {|item| @region.validator_ids.include?(item.id) } 36 + @search = find_by_contents(:organizations, environment, Organization, params[:search])[:results].reject {|item| @region.validator_ids.include?(item.id) }
37 end 37 end
38 end 38 end
39 39
app/controllers/admin/templates_controller.rb
@@ -40,8 +40,67 @@ class TemplatesController &lt; AdminController @@ -40,8 +40,67 @@ class TemplatesController &lt; AdminController
40 end 40 end
41 end 41 end
42 42
  43 + def set_community_as_default
  44 + begin
  45 + community = environment.communities.find(params[:template_id])
  46 + rescue ActiveRecord::RecordNotFound
  47 + message = _('Community not found. The template could no be changed.')
  48 + community = nil
  49 + end
  50 +
  51 + message = _('%s defined as default') % community.name if set_as_default(community)
  52 + session[:notice] = message
  53 +
  54 + redirect_to :action => 'index'
  55 + end
  56 +
  57 + def set_person_as_default
  58 + begin
  59 + person = environment.people.find(params[:template_id])
  60 + rescue ActiveRecord::RecordNotFound
  61 + message = _('Person not found. The template could no be changed.')
  62 + person = nil
  63 + end
  64 +
  65 + message = _('%s defined as default') % person.name if set_as_default(person)
  66 + session[:notice] = message
  67 +
  68 + redirect_to :action => 'index'
  69 + end
  70 +
  71 + def set_enterprise_as_default
  72 + begin
  73 + enterprise = environment.enterprises.find(params[:template_id])
  74 + rescue ActiveRecord::RecordNotFound
  75 + message = _('Enterprise not found. The template could no be changed.')
  76 + enterprise = nil
  77 + end
  78 +
  79 + message = _('%s defined as default') % enterprise.name if set_as_default(enterprise)
  80 + session[:notice] = message
  81 +
  82 + redirect_to :action => 'index'
  83 + end
  84 +
43 private 85 private
44 86
  87 + def set_as_default(obj)
  88 + return nil if obj.nil?
  89 + case obj.class.name
  90 + when 'Community' then
  91 + environment.community_default_template = obj
  92 + environment.save!
  93 + when 'Person' then
  94 + environment.person_default_template = obj
  95 + environment.save!
  96 + when 'Enterprise' then
  97 + environment.enterprise_default_template = obj
  98 + environment.save!
  99 + else
  100 + nil
  101 + end
  102 + end
  103 +
45 def create_organization_template(klass) 104 def create_organization_template(klass)
46 identifier = params[:name].to_slug 105 identifier = params[:name].to_slug
47 template = klass.new(:name => params[:name], :identifier => identifier, :is_template => true) 106 template = klass.new(:name => params[:name], :identifier => identifier, :is_template => true)
app/controllers/admin/users_controller.rb
@@ -18,7 +18,7 @@ class UsersController &lt; AdminController @@ -18,7 +18,7 @@ class UsersController &lt; AdminController
18 end 18 end
19 scope = scope.order('name ASC') 19 scope = scope.order('name ASC')
20 @q = params[:q] 20 @q = params[:q]
21 - @collection = find_by_contents(:people, scope, @q, {:per_page => per_page, :page => params[:npage]})[:results] 21 + @collection = find_by_contents(:people, environment, scope, @q, {:per_page => per_page, :page => params[:npage]})[:results]
22 end 22 end
23 23
24 def set_admin_role 24 def set_admin_role
app/controllers/application_controller.rb
@@ -127,6 +127,9 @@ class ApplicationController &lt; ActionController::Base @@ -127,6 +127,9 @@ class ApplicationController &lt; ActionController::Base
127 127
128 # TODO: move this logic somewhere else (Domain class?) 128 # TODO: move this logic somewhere else (Domain class?)
129 def detect_stuff_by_domain 129 def detect_stuff_by_domain
  130 + # Sets text domain based on request host for custom internationalization
  131 + FastGettext.text_domain = Domain.custom_locale(request.host)
  132 +
130 @domain = Domain.find_by_name(request.host) 133 @domain = Domain.find_by_name(request.host)
131 if @domain.nil? 134 if @domain.nil?
132 @environment = Environment.default 135 @environment = Environment.default
@@ -180,21 +183,19 @@ class ApplicationController &lt; ActionController::Base @@ -180,21 +183,19 @@ class ApplicationController &lt; ActionController::Base
180 end 183 end
181 end 184 end
182 185
183 - def find_by_contents(asset, scope, query, paginate_options={:page => 1}, options={})  
184 - plugins.dispatch_first(:find_by_contents, asset, scope, query, paginate_options, options) ||  
185 - fallback_find_by_contents(asset, scope, query, paginate_options, options)  
186 - end 186 + include SearchTermHelper
187 187
188 - private 188 + def find_by_contents(asset, context, scope, query, paginate_options={:page => 1}, options={})
  189 + search = plugins.dispatch_first(:find_by_contents, asset, scope, query, paginate_options, options)
  190 + register_search_term(query, scope.count, search[:results].count, context, asset)
  191 + search
  192 + end
189 193
190 - def fallback_find_by_contents(asset, scope, query, paginate_options, options)  
191 - scope = scope.like_search(query) unless query.blank?  
192 - scope = scope.send(options[:filter]) unless options[:filter].blank?  
193 - {:results => scope.paginate(paginate_options)} 194 + def find_suggestions(query, context, asset, options={})
  195 + plugins.dispatch_first(:find_suggestions, query, context, asset, options)
194 end 196 end
195 197
196 def private_environment? 198 def private_environment?
197 @environment.enabled?(:restrict_to_members) 199 @environment.enabled?(:restrict_to_members)
198 end 200 end
199 -  
200 end 201 end
app/controllers/my_profile/cms_controller.rb
@@ -23,6 +23,9 @@ class CmsController &lt; MyProfileController @@ -23,6 +23,9 @@ class CmsController &lt; MyProfileController
23 end 23 end
24 24
25 before_filter :login_required, :except => [:suggest_an_article] 25 before_filter :login_required, :except => [:suggest_an_article]
  26 + before_filter :load_recent_files, :only => [:new, :edit]
  27 +
  28 + helper_method :file_types
26 29
27 protect_if :only => :upload_files do |c, user, profile| 30 protect_if :only => :upload_files do |c, user, profile|
28 article_id = c.params[:parent_id] 31 article_id = c.params[:parent_id]
@@ -30,7 +33,7 @@ class CmsController &lt; MyProfileController @@ -30,7 +33,7 @@ class CmsController &lt; MyProfileController
30 (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))) 33 (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile)))
31 end 34 end
32 35
33 - protect_if :except => [:suggest_an_article, :set_home_page, :edit, :destroy, :publish, :upload_files, :new] do |c, user, profile| 36 + protect_if :except => [:suggest_an_article, :set_home_page, :edit, :destroy, :publish, :publish_on_portal_community, :publish_on_communities, :search_communities_to_publish, :upload_files, :new] do |c, user, profile|
34 user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile)) 37 user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))
35 end 38 end
36 39
@@ -40,7 +43,7 @@ class CmsController &lt; MyProfileController @@ -40,7 +43,7 @@ class CmsController &lt; MyProfileController
40 (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))) 43 (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile)))
41 end 44 end
42 45
43 - protect_if :only => [:destroy, :publish] do |c, user, profile| 46 + protect_if :only => :destroy do |c, user, profile|
44 profile.articles.find(c.params[:id]).allow_post_content?(user) 47 profile.articles.find(c.params[:id]).allow_post_content?(user)
45 end 48 end
46 49
@@ -117,7 +120,7 @@ class CmsController &lt; MyProfileController @@ -117,7 +120,7 @@ class CmsController &lt; MyProfileController
117 @success_back_to = params[:success_back_to] 120 @success_back_to = params[:success_back_to]
118 # user must choose an article type first 121 # user must choose an article type first
119 122
120 - @parent = profile.articles.find(params[:parent_id]) if params && params[:parent_id] 123 + @parent = profile.articles.find(params[:parent_id]) if params && params[:parent_id].present?
121 record_coming 124 record_coming
122 @type = params[:type] 125 @type = params[:type]
123 if @type.blank? 126 if @type.blank?
@@ -163,7 +166,10 @@ class CmsController &lt; MyProfileController @@ -163,7 +166,10 @@ class CmsController &lt; MyProfileController
163 if continue 166 if continue
164 redirect_to :action => 'edit', :id => @article 167 redirect_to :action => 'edit', :id => @article
165 else 168 else
166 - success_redirect 169 + respond_to do |format|
  170 + format.html { success_redirect }
  171 + format.json { render :text => {:id => @article.id, :full_name => profile.identifier + '/' + @article.full_name}.to_json }
  172 + end
167 end 173 end
168 return 174 return
169 end 175 end
@@ -174,6 +180,8 @@ class CmsController &lt; MyProfileController @@ -174,6 +180,8 @@ class CmsController &lt; MyProfileController
174 180
175 post_only :set_home_page 181 post_only :set_home_page
176 def set_home_page 182 def set_home_page
  183 + return render_access_denied unless user.can_change_homepage?
  184 +
177 article = params[:id].nil? ? nil : profile.articles.find(params[:id]) 185 article = params[:id].nil? ? nil : profile.articles.find(params[:id])
178 profile.update_attribute(:home_page, article) 186 profile.update_attribute(:home_page, article)
179 187
@@ -212,6 +220,7 @@ class CmsController &lt; MyProfileController @@ -212,6 +220,7 @@ class CmsController &lt; MyProfileController
212 if @errors.any? 220 if @errors.any?
213 render :action => 'upload_files', :parent_id => @parent_id 221 render :action => 'upload_files', :parent_id => @parent_id
214 else 222 else
  223 + session[:notice] = _('File(s) successfully uploaded')
215 if @back_to 224 if @back_to
216 redirect_to @back_to 225 redirect_to @back_to
217 elsif @parent 226 elsif @parent
@@ -253,28 +262,53 @@ class CmsController &lt; MyProfileController @@ -253,28 +262,53 @@ class CmsController &lt; MyProfileController
253 render :template => 'shared/update_categories', :locals => { :category => @current_category, :object_name => 'article' } 262 render :template => 'shared/update_categories', :locals => { :category => @current_category, :object_name => 'article' }
254 end 263 end
255 264
  265 + def search_communities_to_publish
  266 + render :text => find_by_contents(:profiles, environment, user.memberships, params['q'], {:page => 1}, {:fields => ['name']})[:results].map {|community| {:id => community.id, :name => community.name} }.to_json
  267 + end
  268 +
256 def publish 269 def publish
257 @article = profile.articles.find(params[:id]) 270 @article = profile.articles.find(params[:id])
258 record_coming 271 record_coming
259 - @groups = profile.memberships - [profile]  
260 - @marked_groups = []  
261 - groups_ids = profile.memberships.map{|m|m.id.to_s}  
262 - @marked_groups = params[:marked_groups].map do |key, item|  
263 - if groups_ids.include?(item[:group_id])  
264 - item.merge :group => Profile.find(item.delete(:group_id)) 272 + @failed = {}
  273 + if request.post?
  274 + article_name = params[:name]
  275 + task = ApproveArticle.create!(:article => @article, :name => article_name, :target => user, :requestor => user)
  276 + begin
  277 + task.finish
  278 + rescue Exception => ex
  279 + @failed[ex.message] ? @failed[ex.message] << @article.name : @failed[ex.message] = [@article.name]
  280 + task.cancel
  281 + end
  282 + if @failed.blank?
  283 + session[:notice] = _("Your publish request was sent successfully")
  284 + if @back_to
  285 + redirect_to @back_to
  286 + else
  287 + redirect_to @article.view_url
  288 + end
265 end 289 end
266 - end.compact unless params[:marked_groups].nil? 290 + end
  291 + end
  292 +
  293 + def publish_on_communities
267 if request.post? 294 if request.post?
  295 + @back_to = params[:back_to]
  296 + @article = profile.articles.find(params[:id])
268 @failed = {} 297 @failed = {}
  298 + article_name = params[:name]
  299 + params_marked = (params['q'] || '').split(',').select { |marked| user.memberships.map(&:id).include? marked.to_i }
  300 + @marked_groups = Profile.find(params_marked)
269 if @marked_groups.empty? 301 if @marked_groups.empty?
  302 + redirect_to @back_to
270 return session[:notice] = _("Select some group to publish your article") 303 return session[:notice] = _("Select some group to publish your article")
271 end 304 end
272 @marked_groups.each do |item| 305 @marked_groups.each do |item|
273 - task = ApproveArticle.create!(:article => @article, :name => item[:name], :target => item[:group], :requestor => profile) 306 + task = ApproveArticle.create!(:article => @article, :name => article_name, :target => item, :requestor => user)
274 begin 307 begin
275 - task.finish unless item[:group].moderated_articles? 308 + task.finish unless item.moderated_articles?
276 rescue Exception => ex 309 rescue Exception => ex
277 - @failed[ex.message] ? @failed[ex.message] << item[:group].name : @failed[ex.message] = [item[:group].name] 310 + @failed[ex.message] ? @failed[ex.message] << item.name : @failed[ex.message] = [item.name]
  311 + task.cancel
278 end 312 end
279 end 313 end
280 if @failed.blank? 314 if @failed.blank?
@@ -284,23 +318,27 @@ class CmsController &lt; MyProfileController @@ -284,23 +318,27 @@ class CmsController &lt; MyProfileController
284 else 318 else
285 redirect_to @article.view_url 319 redirect_to @article.view_url
286 end 320 end
  321 + else
  322 + session[:notice] = _("Some of your publish requests couldn't be sent.")
  323 + render :action => 'publish'
287 end 324 end
288 end 325 end
289 end 326 end
290 327
291 def publish_on_portal_community 328 def publish_on_portal_community
292 - @article = profile.articles.find(params[:id])  
293 if request.post? 329 if request.post?
294 - if environment.portal_community 330 + @article = profile.articles.find(params[:id])
  331 + if environment.portal_enabled
295 task = ApproveArticle.create!(:article => @article, :name => params[:name], :target => environment.portal_community, :requestor => user) 332 task = ApproveArticle.create!(:article => @article, :name => params[:name], :target => environment.portal_community, :requestor => user)
296 begin 333 begin
297 task.finish unless environment.portal_community.moderated_articles? 334 task.finish unless environment.portal_community.moderated_articles?
298 - flash[:notice] = _("Your publish request was sent successfully") 335 + session[:notice] = _("Your publish request was sent successfully")
299 rescue 336 rescue
300 - flash[:error] = _("Your publish request couldn't be sent.") 337 + session[:notice] = _("Your publish request couldn't be sent.")
  338 + task.cancel
301 end 339 end
302 else 340 else
303 - flash[:notice] = _("There is no portal community to publish your article.") 341 + session[:notice] = _("There is no portal community to publish your article.")
304 end 342 end
305 343
306 if @back_to 344 if @back_to
@@ -328,7 +366,7 @@ class CmsController &lt; MyProfileController @@ -328,7 +366,7 @@ class CmsController &lt; MyProfileController
328 366
329 def search 367 def search
330 query = params[:q] 368 query = params[:q]
331 - results = find_by_contents(:uploaded_files, profile.files.published, query)[:results] 369 + results = find_by_contents(:uploaded_files, profile, profile.files.published, query)[:results]
332 render :text => article_list_to_json(results), :content_type => 'application/json' 370 render :text => article_list_to_json(results), :content_type => 'application/json'
333 end 371 end
334 372
@@ -339,15 +377,26 @@ class CmsController &lt; MyProfileController @@ -339,15 +377,26 @@ class CmsController &lt; MyProfileController
339 end 377 end
340 378
341 def media_upload 379 def media_upload
342 - files_uploaded = []  
343 parent = check_parent(params[:parent_id]) 380 parent = check_parent(params[:parent_id])
344 - files = [:file1,:file2, :file3].map { |f| params[f] }.compact  
345 if request.post? 381 if request.post?
346 - files.each do |file|  
347 - files_uploaded << UploadedFile.create(:uploaded_data => file, :profile => profile, :parent => parent) unless file == '' 382 + begin
  383 + @file = UploadedFile.create!(:uploaded_data => params[:file], :profile => profile, :parent => parent) unless params[:file] == ''
  384 + @file = FilePresenter.for(@file)
  385 + rescue Exception => exception
  386 + render :text => exception.to_s, :status => :bad_request
348 end 387 end
349 end 388 end
350 - render :text => article_list_to_json(files_uploaded), :content_type => 'text/plain' 389 + end
  390 +
  391 + def published_media_items
  392 + load_recent_files(params[:parent_id], params[:q])
  393 + render :partial => 'published_media_items'
  394 + end
  395 +
  396 + def view_all_media
  397 + paginate_options = {:page => params[:page].blank? ? 1 : params[:page] }
  398 + @key = params[:key].to_sym
  399 + load_recent_files(params[:parent_id], params[:q], paginate_options)
351 end 400 end
352 401
353 protected 402 protected
@@ -442,4 +491,36 @@ class CmsController &lt; MyProfileController @@ -442,4 +491,36 @@ class CmsController &lt; MyProfileController
442 end 491 end
443 end 492 end
444 493
  494 + def file_types
  495 + {:images => _('Images'), :generics => _('Files')}
  496 + end
  497 +
  498 + def load_recent_files(parent_id = nil, q = nil, paginate_options = {:page => 1, :per_page => 6})
  499 + #TODO Since we only have special support for images, I'm limiting myself to
  500 + # consider generic files as non-images. In the future, with more supported
  501 + # file types we'll need to have a smart way to fetch from the database
  502 + # scopes of each supported type as well as the non-supported types as a
  503 + # whole.
  504 + @recent_files = {}
  505 +
  506 + parent = parent_id.present? ? profile.articles.find(parent_id) : nil
  507 + if parent.present?
  508 + files = parent.children.files
  509 + else
  510 + files = profile.files
  511 + end
  512 +
  513 + files = files.reorder('created_at DESC')
  514 + images = files.images
  515 + generics = files.no_images
  516 +
  517 + if q.present?
  518 + @recent_files[:images] = find_by_contents(:images, profile, images, q, paginate_options)[:results]
  519 + @recent_files[:generics] = find_by_contents(:generics, profile, generics, q, paginate_options)[:results]
  520 + else
  521 + @recent_files[:images] = images.paginate(paginate_options)
  522 + @recent_files[:generics] = generics.paginate(paginate_options)
  523 + end
  524 + end
  525 +
445 end 526 end
app/controllers/my_profile/friends_controller.rb
@@ -3,6 +3,7 @@ class FriendsController &lt; MyProfileController @@ -3,6 +3,7 @@ class FriendsController &lt; MyProfileController
3 protect 'manage_friends', :profile 3 protect 'manage_friends', :profile
4 4
5 def index 5 def index
  6 + @suggestions = profile.profile_suggestions.of_person.enabled.includes(:suggestion).limit(per_page)
6 if is_cache_expired?(profile.manage_friends_cache_key(params)) 7 if is_cache_expired?(profile.manage_friends_cache_key(params))
7 @friends = profile.friends.paginate(:per_page => per_page, :page => params[:npage]) 8 @friends = profile.friends.paginate(:per_page => per_page, :page => params[:npage])
8 end 9 end
@@ -16,6 +17,30 @@ class FriendsController &lt; MyProfileController @@ -16,6 +17,30 @@ class FriendsController &lt; MyProfileController
16 end 17 end
17 end 18 end
18 19
  20 + def suggest
  21 + @suggestions = profile.profile_suggestions.of_person.enabled.includes(:suggestion).limit(per_page)
  22 + end
  23 +
  24 + def remove_suggestion
  25 + @person = profile.suggested_people.find_by_identifier(params[:id])
  26 + redirect_to :action => 'suggest' unless @person
  27 + if @person && request.post?
  28 + profile.remove_suggestion(@person)
  29 + @suggestions = profile.profile_suggestions.of_person.enabled.includes(:suggestion).limit(per_page)
  30 + render :partial => 'shared/profile_suggestions_list', :locals => { :suggestions => @suggestions, :collection => :friends_suggestions, :per_page => params[:per_page] || per_page }
  31 + end
  32 + end
  33 +
  34 + def connections
  35 + @suggestion = profile.profile_suggestions.of_person.enabled.find_by_suggestion_id(params[:id])
  36 + if @suggestion
  37 + @tags = @suggestion.tag_connections
  38 + @profiles = @suggestion.profile_connections
  39 + else
  40 + redirect_to :action => 'suggest'
  41 + end
  42 + end
  43 +
19 protected 44 protected
20 45
21 class << self 46 class << self
app/controllers/my_profile/memberships_controller.rb
@@ -20,12 +20,54 @@ class MembershipsController &lt; MyProfileController @@ -20,12 +20,54 @@ class MembershipsController &lt; MyProfileController
20 @community.environment = environment 20 @community.environment = environment
21 @back_to = params[:back_to] || url_for(:action => 'index') 21 @back_to = params[:back_to] || url_for(:action => 'index')
22 if request.post? && @community.valid? 22 if request.post? && @community.valid?
23 - @community = Community.create_after_moderation(user, params[:community].merge({:environment => environment}))  
24 - if @community.new_record? 23 + begin
  24 + # Community was created
  25 + @community = Community.create_after_moderation(user, params[:community].merge({:environment => environment}))
  26 + @community.reload
  27 + redirect_to :action => 'welcome', :community_id => @community.id, :back_to => @back_to
  28 + rescue ActiveRecord::RecordNotFound
  29 + # Community pending approval
25 session[:notice] = _('Your new community creation request will be evaluated by an administrator. You will be notified.') 30 session[:notice] = _('Your new community creation request will be evaluated by an administrator. You will be notified.')
  31 + redirect_to @back_to
26 end 32 end
27 - redirect_to @back_to  
28 return 33 return
29 end 34 end
30 end 35 end
  36 +
  37 + def welcome
  38 + @community = Community.find(params[:community_id])
  39 + @back_to = params[:back_to]
  40 + end
  41 +
  42 + def suggest
  43 + @suggestions = profile.profile_suggestions.of_community.enabled.includes(:suggestion).limit(per_page)
  44 + end
  45 +
  46 + def remove_suggestion
  47 + @community = profile.suggested_communities.find_by_identifier(params[:id])
  48 + custom_per_page = params[:per_page] || per_page
  49 + redirect_to :action => 'suggest' unless @community
  50 + if @community && request.post?
  51 + profile.remove_suggestion(@community)
  52 + @suggestions = profile.profile_suggestions.of_community.enabled.includes(:suggestion).limit(custom_per_page)
  53 + render :partial => 'shared/profile_suggestions_list', :locals => { :suggestions => @suggestions, :collection => :communities_suggestions, :per_page => custom_per_page}
  54 + end
  55 + end
  56 +
  57 + def connections
  58 + @suggestion = profile.profile_suggestions.of_community.enabled.find_by_suggestion_id(params[:id])
  59 + if @suggestion
  60 + @tags = @suggestion.tag_connections
  61 + @profiles = @suggestion.profile_connections
  62 + else
  63 + redirect_to :action => 'suggest'
  64 + end
  65 + end
  66 +
  67 + protected
  68 +
  69 + def per_page
  70 + 12
  71 + end
  72 +
31 end 73 end
app/controllers/my_profile/profile_design_controller.rb
@@ -3,7 +3,16 @@ class ProfileDesignController &lt; BoxOrganizerController @@ -3,7 +3,16 @@ class ProfileDesignController &lt; BoxOrganizerController
3 needs_profile 3 needs_profile
4 4
5 protect 'edit_profile_design', :profile 5 protect 'edit_profile_design', :profile
6 - 6 +
  7 + before_filter :protect_fixed_block, :only => [:save, :move_block]
  8 +
  9 + def protect_fixed_block
  10 + block = boxes_holder.blocks.find(params[:id].gsub(/^block-/, ''))
  11 + if block.fixed && !current_person.is_admin?
  12 + render_access_denied
  13 + end
  14 + end
  15 +
7 def available_blocks 16 def available_blocks
8 blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock, HighlightsBlock ] 17 blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock, HighlightsBlock ]
9 18
app/controllers/my_profile/profile_editor_controller.rb
@@ -3,6 +3,10 @@ class ProfileEditorController &lt; MyProfileController @@ -3,6 +3,10 @@ class ProfileEditorController &lt; MyProfileController
3 protect 'edit_profile', :profile, :except => [:destroy_profile] 3 protect 'edit_profile', :profile, :except => [:destroy_profile]
4 protect 'destroy_profile', :profile, :only => [:destroy_profile] 4 protect 'destroy_profile', :profile, :only => [:destroy_profile]
5 5
  6 + before_filter :access_welcome_page, :only => [:welcome_page]
  7 + before_filter :back_to
  8 + helper_method :has_welcome_page
  9 +
6 def index 10 def index
7 @pending_tasks = Task.to(profile).pending.without_spam.select{|i| user.has_permission?(i.permission, profile)} 11 @pending_tasks = Task.to(profile).pending.without_spam.select{|i| user.has_permission?(i.permission, profile)}
8 end 12 end
@@ -16,14 +20,16 @@ class ProfileEditorController &lt; MyProfileController @@ -16,14 +20,16 @@ class ProfileEditorController &lt; MyProfileController
16 if request.post? 20 if request.post?
17 params[:profile_data][:fields_privacy] ||= {} if profile.person? && params[:profile_data].is_a?(Hash) 21 params[:profile_data][:fields_privacy] ||= {} if profile.person? && params[:profile_data].is_a?(Hash)
18 Profile.transaction do 22 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? 23 + Image.transaction do
  24 + begin
  25 + @plugins.dispatch(:profile_editor_transaction_extras)
  26 + @profile_data.update_attributes!(params[:profile_data])
  27 + redirect_to :action => 'index', :profile => profile.identifier
  28 + rescue Exception => ex
  29 + profile.identifier = params[:profile] if profile.identifier.blank?
  30 + end
24 end 31 end
25 end 32 end
26 - end  
27 end 33 end
28 end 34 end
29 35
@@ -72,10 +78,81 @@ class ProfileEditorController &lt; MyProfileController @@ -72,10 +78,81 @@ class ProfileEditorController &lt; MyProfileController
72 if request.post? 78 if request.post?
73 if @profile.destroy 79 if @profile.destroy
74 session[:notice] = _('The profile was deleted.') 80 session[:notice] = _('The profile was deleted.')
75 - redirect_to :controller => 'home' 81 + if(params[:return_to])
  82 + redirect_to params[:return_to]
  83 + else
  84 + redirect_to :controller => 'home'
  85 + end
76 else 86 else
77 session[:notice] = _('Could not delete profile') 87 session[:notice] = _('Could not delete profile')
78 end 88 end
79 end 89 end
80 end 90 end
  91 +
  92 + def welcome_page
  93 + @welcome_page = profile.welcome_page || TinyMceArticle.new(:name => 'Welcome Page', :profile => profile, :published => false)
  94 + if request.post?
  95 + begin
  96 + @welcome_page.update_attributes!(params[:welcome_page])
  97 + profile.welcome_page = @welcome_page
  98 + profile.save!
  99 + session[:notice] = _('Welcome page saved successfully.')
  100 + redirect_to :action => 'index'
  101 + rescue Exception => exception
  102 + session[:notice] = _('Welcome page could not be saved.')
  103 + end
  104 + end
  105 + end
  106 +
  107 + def deactivate_profile
  108 + if environment.admins.include?(current_person)
  109 + profile = environment.profiles.find(params[:id])
  110 + if profile.disable
  111 + profile.save
  112 + session[:notice] = _("The profile '#{profile.name}' was deactivated.")
  113 + else
  114 + session[:notice] = _('Could not deactivate profile.')
  115 + end
  116 + end
  117 +
  118 + redirect_to_previous_location
  119 + end
  120 +
  121 + def activate_profile
  122 + if environment.admins.include?(current_person)
  123 + profile = environment.profiles.find(params[:id])
  124 +
  125 + if profile.enable
  126 + session[:notice] = _("The profile '#{profile.name}' was activated.")
  127 + else
  128 + session[:notice] = _('Could not activate the profile.')
  129 + end
  130 + end
  131 +
  132 + redirect_to_previous_location
  133 + end
  134 +
  135 + protected
  136 +
  137 + def redirect_to_previous_location
  138 + redirect_to @back_to
  139 + end
  140 +
  141 + #TODO Consider using this as a general controller feature to be available on every action.
  142 + def back_to
  143 + @back_to = params[:back_to] || request.referer || "/"
  144 + end
  145 +
  146 + private
  147 +
  148 + def has_welcome_page
  149 + profile.is_template
  150 + end
  151 +
  152 + def access_welcome_page
  153 + unless has_welcome_page
  154 + render_access_denied
  155 + end
  156 + end
  157 +
81 end 158 end
app/controllers/my_profile/profile_members_controller.rb
@@ -20,7 +20,7 @@ class ProfileMembersController &lt; MyProfileController @@ -20,7 +20,7 @@ class ProfileMembersController &lt; MyProfileController
20 redirect_to :action => :last_admin 20 redirect_to :action => :last_admin
21 elsif @person.define_roles(@roles, profile) 21 elsif @person.define_roles(@roles, profile)
22 session[:notice] = _('Roles successfuly updated') 22 session[:notice] = _('Roles successfuly updated')
23 - redirect_to :controller => 'profile_editor' 23 + redirect_to :action => 'index'
24 else 24 else
25 session[:notice] = _('Couldn\'t change the roles') 25 session[:notice] = _('Couldn\'t change the roles')
26 redirect_to :action => 'index' 26 redirect_to :action => 'index'
app/controllers/public/account_controller.rb
@@ -82,10 +82,12 @@ class AccountController &lt; ApplicationController @@ -82,10 +82,12 @@ class AccountController &lt; ApplicationController
82 if @plugins.dispatch(:allow_user_registration).include?(false) 82 if @plugins.dispatch(:allow_user_registration).include?(false)
83 redirect_back_or_default(:controller => 'home') 83 redirect_back_or_default(:controller => 'home')
84 session[:notice] = _("This environment doesn't allow user registration.") 84 session[:notice] = _("This environment doesn't allow user registration.")
  85 + return
85 end 86 end
86 87
87 store_location(request.referer) unless params[:return_to] or session[:return_to] 88 store_location(request.referer) unless params[:return_to] or session[:return_to]
88 89
  90 + # Tranforming to boolean
89 @block_bot = !!session[:may_be_a_bot] 91 @block_bot = !!session[:may_be_a_bot]
90 @invitation_code = params[:invitation_code] 92 @invitation_code = params[:invitation_code]
91 begin 93 begin
@@ -129,8 +131,8 @@ class AccountController &lt; ApplicationController @@ -129,8 +131,8 @@ class AccountController &lt; ApplicationController
129 check_join_in_community(@user) 131 check_join_in_community(@user)
130 go_to_signup_initial_page 132 go_to_signup_initial_page
131 else 133 else
  134 + redirect_to :controller => :home, :action => :welcome, :template_id => (@user.person.template && @user.person.template.id)
132 session[:notice] = _('Thanks for registering!') 135 session[:notice] = _('Thanks for registering!')
133 - @register_pending = true  
134 end 136 end
135 end 137 end
136 end 138 end
@@ -193,7 +195,7 @@ class AccountController &lt; ApplicationController @@ -193,7 +195,7 @@ class AccountController &lt; ApplicationController
193 else 195 else
194 @change_password.errors[:base] << _('Could not find any user with %s equal to "%s".') % [fields_label, params[:value]] 196 @change_password.errors[:base] << _('Could not find any user with %s equal to "%s".') % [fields_label, params[:value]]
195 end 197 end
196 - rescue ActiveRecord::RecordInvald 198 + rescue ActiveRecord::RecordInvalid
197 @change_password.errors[:base] << _('Could not perform password recovery for the user.') 199 @change_password.errors[:base] << _('Could not perform password recovery for the user.')
198 end 200 end
199 end 201 end
@@ -461,6 +463,8 @@ class AccountController &lt; ApplicationController @@ -461,6 +463,8 @@ class AccountController &lt; ApplicationController
461 redirect_to user.url 463 redirect_to user.url
462 when 'user_control_panel' 464 when 'user_control_panel'
463 redirect_to user.admin_url 465 redirect_to user.admin_url
  466 + when 'welcome_page'
  467 + redirect_to :controller => :home, :action => :welcome, :template_id => (user.template && user.template.id)
464 else 468 else
465 redirect_back_or_default(default) 469 redirect_back_or_default(default)
466 end 470 end
app/controllers/public/chat_controller.rb
@@ -19,7 +19,7 @@ class ChatController &lt; PublicController @@ -19,7 +19,7 @@ class ChatController &lt; PublicController
19 def avatar 19 def avatar
20 profile = environment.profiles.find_by_identifier(params[:id]) 20 profile = environment.profiles.find_by_identifier(params[:id])
21 filename, mimetype = profile_icon(profile, :minor, true) 21 filename, mimetype = profile_icon(profile, :minor, true)
22 - if filename =~ /^https?:/ 22 + if filename =~ /^(https?:)?\/\//
23 redirect_to filename 23 redirect_to filename
24 else 24 else
25 data = File.read(File.join(Rails.root, 'public', filename)) 25 data = File.read(File.join(Rails.root, 'public', filename))
app/controllers/public/contact_controller.rb
1 class ContactController < PublicController 1 class ContactController < PublicController
2 2
3 - before_filter :login_required  
4 -  
5 needs_profile 3 needs_profile
6 4
7 def new 5 def new
8 - @contact 6 + @contact = build_contact
9 if request.post? && params[:confirm] == 'true' 7 if request.post? && params[:confirm] == 'true'
10 - @contact = user.build_contact(profile, params[:contact])  
11 @contact.city = (!params[:city].blank? && City.exists?(params[:city])) ? City.find(params[:city]).name : nil 8 @contact.city = (!params[:city].blank? && City.exists?(params[:city])) ? City.find(params[:city]).name : nil
12 @contact.state = (!params[:state].blank? && State.exists?(params[:state])) ? State.find(params[:state]).name : nil 9 @contact.state = (!params[:state].blank? && State.exists?(params[:state])) ? State.find(params[:state]).name : nil
13 if @contact.deliver 10 if @contact.deliver
@@ -16,8 +13,17 @@ class ContactController &lt; PublicController @@ -16,8 +13,17 @@ class ContactController &lt; PublicController
16 else 13 else
17 session[:notice] = _('Contact not sent') 14 session[:notice] = _('Contact not sent')
18 end 15 end
  16 + end
  17 + end
  18 +
  19 + protected
  20 +
  21 + def build_contact
  22 + params[:contact] ||= {}
  23 + if logged_in?
  24 + user.build_contact profile, params[:contact]
19 else 25 else
20 - @contact = user.build_contact(profile) 26 + Contact.new params[:contact].merge(dest: profile)
21 end 27 end
22 end 28 end
23 29
app/controllers/public/content_viewer_controller.rb
@@ -126,7 +126,7 @@ class ContentViewerController &lt; ApplicationController @@ -126,7 +126,7 @@ class ContentViewerController &lt; ApplicationController
126 elsif !@page.display_to?(user) 126 elsif !@page.display_to?(user)
127 if !profile.public? 127 if !profile.public?
128 private_profile_partial_parameters 128 private_profile_partial_parameters
129 - render :template => 'profile/_private_profile', :status => 403 129 + render :template => 'profile/_private_profile', :status => 403, :formats => [:html]
130 allowed = false 130 allowed = false
131 else #if !profile.visible? 131 else #if !profile.visible?
132 render_access_denied 132 render_access_denied
app/controllers/public/home_controller.rb
@@ -18,4 +18,10 @@ class HomeController &lt; PublicController @@ -18,4 +18,10 @@ class HomeController &lt; PublicController
18 @no_design_blocks = true 18 @no_design_blocks = true
19 end 19 end
20 20
  21 + def welcome
  22 + @no_design_blocks = true
  23 + @display_confirmation_tips = !user.present? && !environment.enabled?(:skip_new_user_email_confirmation)
  24 + @person_template = user && user.template || params[:template_id] && Person.find(params[:template_id])
  25 + end
  26 +
21 end 27 end
app/controllers/public/invite_controller.rb
@@ -4,8 +4,15 @@ class InviteController &lt; PublicController @@ -4,8 +4,15 @@ class InviteController &lt; PublicController
4 before_filter :login_required 4 before_filter :login_required
5 before_filter :check_permissions_to_invite 5 before_filter :check_permissions_to_invite
6 6
7 - def select_address_book 7 + def invite_friends
8 @import_from = params[:import_from] || "manual" 8 @import_from = params[:import_from] || "manual"
  9 + @mail_template = params[:mail_template] || environment.invitation_mail_template(profile)
  10 +
  11 + labels = Profile::SEARCHABLE_FIELDS.except(:nickname).merge(User::SEARCHABLE_FIELDS).map { |name,info| info[:label].downcase }
  12 + last = labels.pop
  13 + label = labels.join(', ')
  14 + @search_fields = "#{label} #{_('or')} #{last}"
  15 +
9 if request.post? 16 if request.post?
10 contact_list = ContactList.create 17 contact_list = ContactList.create
11 Delayed::Job.enqueue GetEmailContactsJob.new(@import_from, params[:login], params[:password], contact_list.id) if @import_from != 'manual' 18 Delayed::Job.enqueue GetEmailContactsJob.new(@import_from, params[:login], params[:password], contact_list.id) if @import_from != 'manual'
@@ -22,7 +29,7 @@ class InviteController &lt; PublicController @@ -22,7 +29,7 @@ class InviteController &lt; PublicController
22 webmail_import_addresses = params[:webmail_import_addresses] 29 webmail_import_addresses = params[:webmail_import_addresses]
23 contacts_to_invite = Invitation.join_contacts(manual_import_addresses, webmail_import_addresses) 30 contacts_to_invite = Invitation.join_contacts(manual_import_addresses, webmail_import_addresses)
24 if !contacts_to_invite.empty? 31 if !contacts_to_invite.empty?
25 - Delayed::Job.enqueue InvitationJob.new(current_user.person.id, contacts_to_invite, params[:mail_template], profile.id, @contact_list.id, locale) 32 + Delayed::Job.enqueue InvitationJob.new(user.id, contacts_to_invite, params[:mail_template], profile.id, @contact_list.id, locale)
26 session[:notice] = _('Your invitations are being sent.') 33 session[:notice] = _('Your invitations are being sent.')
27 if profile.person? 34 if profile.person?
28 redirect_to :controller => 'profile', :action => 'friends' 35 redirect_to :controller => 'profile', :action => 'friends'
@@ -52,16 +59,36 @@ class InviteController &lt; PublicController @@ -52,16 +59,36 @@ class InviteController &lt; PublicController
52 def cancel_fetching_emails 59 def cancel_fetching_emails
53 contact_list = ContactList.find(params[:contact_list]) 60 contact_list = ContactList.find(params[:contact_list])
54 contact_list.destroy 61 contact_list.destroy
55 - redirect_to :action => 'select_address_book' 62 + redirect_to :action => 'invite_friends'
  63 + end
  64 +
  65 + def invite_registered_friend
  66 + contacts_to_invite = params['q'].split(',')
  67 + if !contacts_to_invite.empty? && request.post?
  68 + Delayed::Job.enqueue InvitationJob.new(user.id, contacts_to_invite, '', profile.id, nil, locale)
  69 + session[:notice] = _('Your invitations are being sent.')
  70 + if profile.person?
  71 + redirect_to :controller => 'profile', :action => 'friends'
  72 + else
  73 + redirect_to :controller => 'profile', :action => 'members'
  74 + end
  75 + else
  76 + redirect_to :action => 'invite_friends'
  77 + session[:notice] = _('Please enter a valid profile.')
  78 + end
  79 + end
  80 +
  81 + def search
  82 + scope = profile.invite_friends_only ? user.friends : environment.people
  83 + scope = scope.not_members_of(profile) if profile.organization?
  84 + scope = scope.not_friends_of(profile) if profile.person?
  85 + results = find_by_contents(:people, environment, scope, params['q'], {:page => 1}, {:joins => :user})[:results]
  86 + render :text => prepare_to_token_input(results).to_json
56 end 87 end
57 88
58 protected 89 protected
59 90
60 def check_permissions_to_invite 91 def check_permissions_to_invite
61 - if profile.person? and !current_user.person.has_permission?(:manage_friends, profile) or  
62 - profile.community? and !current_user.person.has_permission?(:invite_members, profile)  
63 - render_access_denied  
64 - end 92 + render_access_denied if !profile.allow_invitation_from?(user)
65 end 93 end
66 -  
67 end 94 end
app/controllers/public/profile_controller.rb
@@ -17,8 +17,11 @@ class ProfileController &lt; PublicController @@ -17,8 +17,11 @@ class ProfileController &lt; PublicController
17 end 17 end
18 @tags = profile.article_tags 18 @tags = profile.article_tags
19 unless profile.display_info_to?(user) 19 unless profile.display_info_to?(user)
20 - profile.visible? ? private_profile : invisible_profile  
21 - render :action => 'index', :status => 403 20 + if profile.visible?
  21 + private_profile
  22 + else
  23 + invisible_profile
  24 + end
22 end 25 end
23 end 26 end
24 27
@@ -62,13 +65,13 @@ class ProfileController &lt; PublicController @@ -62,13 +65,13 @@ class ProfileController &lt; PublicController
62 65
63 def friends 66 def friends
64 if is_cache_expired?(profile.friends_cache_key(params)) 67 if is_cache_expired?(profile.friends_cache_key(params))
65 - @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)
66 end 69 end
67 end 70 end
68 71
69 def members 72 def members
70 if is_cache_expired?(profile.members_cache_key(params)) 73 if is_cache_expired?(profile.members_cache_key(params))
71 - @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)
72 end 75 end
73 end 76 end
74 77
@@ -316,7 +319,7 @@ class ProfileController &lt; PublicController @@ -316,7 +319,7 @@ class ProfileController &lt; PublicController
316 abuse_report = AbuseReport.new(params[:abuse_report]) 319 abuse_report = AbuseReport.new(params[:abuse_report])
317 if !params[:content_type].blank? 320 if !params[:content_type].blank?
318 article = params[:content_type].constantize.find(params[:content_id]) 321 article = params[:content_type].constantize.find(params[:content_id])
319 - abuse_report.content = instance_eval(&article.reported_version) 322 + abuse_report.content = article_reported_version(article)
320 end 323 end
321 324
322 user.register_report(abuse_report, profile) 325 user.register_report(abuse_report, profile)
@@ -395,6 +398,7 @@ class ProfileController &lt; PublicController @@ -395,6 +398,7 @@ class ProfileController &lt; PublicController
395 398
396 def private_profile 399 def private_profile
397 private_profile_partial_parameters 400 private_profile_partial_parameters
  401 + render :action => 'index', :status => 403
398 end 402 end
399 403
400 def invisible_profile 404 def invisible_profile
app/controllers/public/profile_search_controller.rb
@@ -9,9 +9,12 @@ class ProfileSearchController &lt; PublicController @@ -9,9 +9,12 @@ class ProfileSearchController &lt; PublicController
9 @q = params[:q] 9 @q = params[:q]
10 unless @q.blank? 10 unless @q.blank?
11 if params[:where] == 'environment' 11 if params[:where] == 'environment'
12 - redirect_to :controller => 'search', :query => @q 12 + # user is using global search, redirects to the search controller with
  13 + # the query
  14 + search_path = url_for(:controller => 'search', :query => @q)
  15 + request.xhr? ? render(:js => "window.location.href = #{search_path.to_json}") : redirect_to(search_path)
13 else 16 else
14 - @results = find_by_contents(:articles, profile.articles.published, @q, {:per_page => 10, :page => params[:page]})[:results] 17 + @results = find_by_contents(:articles, profile, profile.articles.published, @q, {:per_page => 10, :page => params[:page]})[:results]
15 end 18 end
16 end 19 end
17 end 20 end
app/controllers/public/search_controller.rb
@@ -4,11 +4,11 @@ class SearchController &lt; PublicController @@ -4,11 +4,11 @@ class SearchController &lt; PublicController
4 include SearchHelper 4 include SearchHelper
5 include ActionView::Helpers::NumberHelper 5 include ActionView::Helpers::NumberHelper
6 6
7 - before_filter :redirect_asset_param, :except => :assets  
8 - before_filter :load_category  
9 - before_filter :load_search_assets  
10 - before_filter :load_query  
11 - before_filter :load_filter 7 + before_filter :redirect_asset_param, :except => [:assets, :suggestions]
  8 + before_filter :load_category, :except => :suggestions
  9 + before_filter :load_search_assets, :except => :suggestions
  10 + before_filter :load_query, :except => :suggestions
  11 + before_filter :load_order, :except => :suggestions
12 12
13 # Backwards compatibility with old URLs 13 # Backwards compatibility with old URLs
14 def redirect_asset_param 14 def redirect_asset_param
@@ -20,7 +20,7 @@ class SearchController &lt; PublicController @@ -20,7 +20,7 @@ class SearchController &lt; PublicController
20 20
21 def index 21 def index
22 @searches = {} 22 @searches = {}
23 - @order = [] 23 + @assets = []
24 @names = {} 24 @names = {}
25 @results_only = true 25 @results_only = true
26 26
@@ -28,7 +28,7 @@ class SearchController &lt; PublicController @@ -28,7 +28,7 @@ class SearchController &lt; PublicController
28 load_query 28 load_query
29 @asset = key 29 @asset = key
30 send(key) 30 send(key)
31 - @order << key 31 + @assets << key
32 @names[key] = _(description) 32 @names[key] = _(description)
33 end 33 end
34 @asset = nil 34 @asset = nil
@@ -42,7 +42,7 @@ class SearchController &lt; PublicController @@ -42,7 +42,7 @@ class SearchController &lt; PublicController
42 # view the summary of one category 42 # view the summary of one category
43 def category_index 43 def category_index
44 @searches = {} 44 @searches = {}
45 - @order = [] 45 + @assets = []
46 @names = {} 46 @names = {}
47 limit = MULTIPLE_SEARCH_LIMIT 47 limit = MULTIPLE_SEARCH_LIMIT
48 [ 48 [
@@ -53,7 +53,7 @@ class SearchController &lt; PublicController @@ -53,7 +53,7 @@ class SearchController &lt; PublicController
53 [ :communities, _('Communities'), :recent_communities ], 53 [ :communities, _('Communities'), :recent_communities ],
54 [ :articles, _('Contents'), :recent_articles ] 54 [ :articles, _('Contents'), :recent_articles ]
55 ].each do |asset, name, filter| 55 ].each do |asset, name, filter|
56 - @order << asset 56 + @assets << asset
57 @searches[asset]= {:results => @category.send(filter, limit)} 57 @searches[asset]= {:results => @category.send(filter, limit)}
58 raise "No total_entries for: #{asset}" unless @searches[asset][:results].respond_to?(:total_entries) 58 raise "No total_entries for: #{asset}" unless @searches[asset][:results].respond_to?(:total_entries)
59 @names[asset] = name 59 @names[asset] = name
@@ -90,10 +90,14 @@ class SearchController &lt; PublicController @@ -90,10 +90,14 @@ class SearchController &lt; PublicController
90 end 90 end
91 91
92 def events 92 def events
93 - year = (params[:year] ? params[:year].to_i : Date.today.year)  
94 - month = (params[:month] ? params[:month].to_i : Date.today.month)  
95 - day = (params[:day] ? params[:day].to_i : Date.today.day)  
96 - @date = build_date(year, month, day) 93 + if params[:year].blank? && params[:year].blank? && params[:day].blank?
  94 + @date = Date.today
  95 + else
  96 + year = (params[:year] ? params[:year].to_i : Date.today.year)
  97 + month = (params[:month] ? params[:month].to_i : Date.today.month)
  98 + day = (params[:day] ? params[:day].to_i : 1)
  99 + @date = build_date(year, month, day)
  100 + end
97 date_range = (@date - 1.month).at_beginning_of_month..(@date + 1.month).at_end_of_month 101 date_range = (@date - 1.month).at_beginning_of_month..(@date + 1.month).at_end_of_month
98 102
99 @events = [] 103 @events = []
@@ -143,12 +147,16 @@ class SearchController &lt; PublicController @@ -143,12 +147,16 @@ class SearchController &lt; PublicController
143 render :partial => 'events/events' 147 render :partial => 'events/events'
144 end 148 end
145 149
  150 + def suggestions
  151 + render :text => find_suggestions(normalize_term(params[:term]), environment, params[:asset]).to_json
  152 + end
  153 +
146 ####################################################### 154 #######################################################
147 protected 155 protected
148 156
149 def load_query 157 def load_query
150 @asset = (params[:asset] || params[:action]).to_sym 158 @asset = (params[:asset] || params[:action]).to_sym
151 - @order ||= [@asset] 159 + @assets ||= [@asset]
152 @searches ||= {} 160 @searches ||= {}
153 161
154 @query = params[:query] || '' 162 @query = params[:query] || ''
@@ -169,13 +177,22 @@ class SearchController &lt; PublicController @@ -169,13 +177,22 @@ class SearchController &lt; PublicController
169 end 177 end
170 end 178 end
171 179
  180 + AVAILABLE_SEARCHES = ActiveSupport::OrderedHash[
  181 + :articles, _('Contents'),
  182 + :people, _('People'),
  183 + :communities, _('Communities'),
  184 + :enterprises, _('Enterprises'),
  185 + :products, _('Products and Services'),
  186 + :events, _('Events'),
  187 + ]
  188 +
172 def load_search_assets 189 def load_search_assets
173 - if SEARCHES.keys.include?(params[:action].to_sym) && environment.enabled?("disable_asset_#{params[:action]}") 190 + if AVAILABLE_SEARCHES.keys.include?(params[:action].to_sym) && environment.enabled?("disable_asset_#{params[:action]}")
174 render_not_found 191 render_not_found
175 return 192 return
176 end 193 end
177 194
178 - @enabled_searches = SEARCHES.select {|key, name| environment.disabled?("disable_asset_#{key}") } 195 + @enabled_searches = AVAILABLE_SEARCHES.select {|key, name| environment.disabled?("disable_asset_#{key}") }
179 @searching = {} 196 @searching = {}
180 @titles = {} 197 @titles = {}
181 @enabled_searches.each do |key, name| 198 @enabled_searches.each do |key, name|
@@ -185,11 +202,11 @@ class SearchController &lt; PublicController @@ -185,11 +202,11 @@ class SearchController &lt; PublicController
185 @names = @titles if @names.nil? 202 @names = @titles if @names.nil?
186 end 203 end
187 204
188 - def load_filter  
189 - @filter = 'more_recent'  
190 - if SEARCHES.keys.include?(@asset.to_sym)  
191 - available_filters = asset_class(@asset)::SEARCH_FILTERS  
192 - @filter = params[:filter] if available_filters.include?(params[:filter]) 205 + def load_order
  206 + @order = 'more_recent'
  207 + if AVAILABLE_SEARCHES.keys.include?(@asset.to_sym)
  208 + available_orders = asset_class(@asset)::SEARCH_FILTERS[:order]
  209 + @order = params[:order] if available_orders.include?(params[:order])
193 end 210 end
194 end 211 end
195 212
@@ -213,7 +230,7 @@ class SearchController &lt; PublicController @@ -213,7 +230,7 @@ class SearchController &lt; PublicController
213 end 230 end
214 231
215 def full_text_search 232 def full_text_search
216 - @searches[@asset] = find_by_contents(@asset, @scope, @query, paginate_options, {:category => @category, :filter => @filter}) 233 + @searches[@asset] = find_by_contents(@asset, environment, @scope, @query, paginate_options, {:category => @category, :filter => @order})
217 end 234 end
218 235
219 private 236 private
@@ -228,4 +245,14 @@ class SearchController &lt; PublicController @@ -228,4 +245,14 @@ class SearchController &lt; PublicController
228 20 245 20
229 end 246 end
230 247
  248 + def available_assets
  249 + assets = ActiveSupport::OrderedHash[
  250 + :articles, _('Contents'),
  251 + :enterprises, _('Enterprises'),
  252 + :people, _('People'),
  253 + :communities, _('Communities'),
  254 + :products, _('Products and Services'),
  255 + ]
  256 + end
  257 +
231 end 258 end
app/helpers/application_helper.rb
@@ -433,19 +433,19 @@ module ApplicationHelper @@ -433,19 +433,19 @@ module ApplicationHelper
433 end 433 end
434 434
435 def theme_site_title 435 def theme_site_title
436 - theme_include('site_title') 436 + @theme_site_title ||= theme_include 'site_title'
437 end 437 end
438 438
439 def theme_header 439 def theme_header
440 - theme_include('header') 440 + @theme_header ||= theme_include 'header'
441 end 441 end
442 442
443 def theme_footer 443 def theme_footer
444 - theme_include('footer') 444 + @theme_footer ||= theme_include 'footer'
445 end 445 end
446 446
447 def theme_extra_navigation 447 def theme_extra_navigation
448 - theme_include('navigation') 448 + @theme_extra_navigation ||= theme_include 'navigation'
449 end 449 end
450 450
451 def is_testing_theme 451 def is_testing_theme
@@ -674,13 +674,14 @@ module ApplicationHelper @@ -674,13 +674,14 @@ module ApplicationHelper
674 html.join "\n" 674 html.join "\n"
675 end 675 end
676 676
  677 + def theme_javascript_src
  678 + script = File.join theme_path, 'theme.js'
  679 + script if File.exists? File.join(Rails.root, 'public', script)
  680 + end
  681 +
677 def theme_javascript_ng 682 def theme_javascript_ng
678 - script = File.join(theme_path, 'theme.js')  
679 - if File.exists?(File.join(Rails.root, 'public', script))  
680 - javascript_include_tag script  
681 - else  
682 - nil  
683 - end 683 + script = theme_javascript_src
  684 + javascript_include_tag script if script
684 end 685 end
685 686
686 def file_field_or_thumbnail(label, image, i) 687 def file_field_or_thumbnail(label, image, i)
@@ -861,8 +862,9 @@ module ApplicationHelper @@ -861,8 +862,9 @@ module ApplicationHelper
861 end 862 end
862 863
863 def base_url 864 def base_url
864 - environment.top_url 865 + environment.top_url(request.scheme)
865 end 866 end
  867 + alias :top_url :base_url
866 868
867 def helper_for_article(article) 869 def helper_for_article(article)
868 article_helper = ActionView::Base.new 870 article_helper = ActionView::Base.new
@@ -907,13 +909,15 @@ module ApplicationHelper @@ -907,13 +909,15 @@ module ApplicationHelper
907 end 909 end
908 910
909 def page_title 911 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}" : '') 912 + CGI.escapeHTML(
  913 + (@page ? @page.title + ' - ' : '') +
  914 + (@topic ? @topic.title + ' - ' : '') +
  915 + (@section ? @section.title + ' - ' : '') +
  916 + (@toc ? _('Online Manual') + ' - ' : '') +
  917 + (controller.controller_name == 'chat' ? _('Chat') + ' - ' : '') +
  918 + (profile ? profile.short_name : environment.name) +
  919 + (@category ? " - #{@category.full_name}" : '')
  920 + )
917 end 921 end
918 922
919 # DEPRECATED. Do not use this. 923 # DEPRECATED. Do not use this.
@@ -942,9 +946,9 @@ module ApplicationHelper @@ -942,9 +946,9 @@ module ApplicationHelper
942 # from Article model for an ArticleBlock. 946 # from Article model for an ArticleBlock.
943 def reference_to_article(text, article, anchor=nil) 947 def reference_to_article(text, article, anchor=nil)
944 if article.profile.domains.empty? 948 if article.profile.domains.empty?
945 - href = "/#{article.url[:profile]}/" 949 + href = "#{Noosfero.root}/#{article.url[:profile]}/"
946 else 950 else
947 - href = "http://#{article.profile.domains.first.name}/" 951 + href = "http://#{article.profile.domains.first.name}#{Noosfero.root}/"
948 end 952 end
949 href += article.url[:page].join('/') 953 href += article.url[:page].join('/')
950 href += '#' + anchor if anchor 954 href += '#' + anchor if anchor
@@ -1285,11 +1289,13 @@ module ApplicationHelper @@ -1285,11 +1289,13 @@ module ApplicationHelper
1285 end 1289 end
1286 1290
1287 def delete_article_message(article) 1291 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 1292 + CGI.escapeHTML(
  1293 + if article.folder?
  1294 + _("Are you sure that you want to remove the folder \"%s\"? Note that all the items inside it will also be removed!") % article.name
  1295 + else
  1296 + _("Are you sure that you want to remove the item \"%s\"?") % article.name
  1297 + end
  1298 + )
1293 end 1299 end
1294 1300
1295 def expirable_link_to(expired, content, url, options = {}) 1301 def expirable_link_to(expired, content, url, options = {})
@@ -1301,8 +1307,19 @@ module ApplicationHelper @@ -1301,8 +1307,19 @@ module ApplicationHelper
1301 end 1307 end
1302 end 1308 end
1303 1309
1304 - def remove_content_button(action)  
1305 - @plugins.dispatch("content_remove_#{action.to_s}", @page).include?(true) 1310 + def content_remove_spread(content)
  1311 + !content.public? || content.folder? || (profile == user && user.communities.blank? && !environment.portal_enabled)
  1312 + end
  1313 +
  1314 + def remove_content_button(action, content)
  1315 + method_name = "content_remove_#{action.to_s}"
  1316 + plugin_condition = @plugins.dispatch(method_name, content).include?(true)
  1317 + begin
  1318 + core_condition = self.send(method_name, content)
  1319 + rescue NoMethodError
  1320 + core_condition = false
  1321 + end
  1322 + core_condition || plugin_condition
1306 end 1323 end
1307 1324
1308 def template_options(kind, field_name) 1325 def template_options(kind, field_name)
@@ -1310,10 +1327,8 @@ module ApplicationHelper @@ -1310,10 +1327,8 @@ module ApplicationHelper
1310 return '' if templates.count == 0 1327 return '' if templates.count == 0
1311 return hidden_field_tag("#{field_name}[template_id]", templates.first.id) if templates.count == 1 1328 return hidden_field_tag("#{field_name}[template_id]", templates.first.id) if templates.count == 1
1312 1329
1313 - counter = 0  
1314 radios = templates.map do |template| 1330 radios = templates.map do |template|
1315 - counter += 1  
1316 - content_tag('li', labelled_radio_button(link_to(template.name, template.url, :target => '_blank'), "#{field_name}[template_id]", template.id, counter==1)) 1331 + content_tag('li', labelled_radio_button(link_to(template.name, template.url, :target => '_blank'), "#{field_name}[template_id]", template.id, environment.is_default_template?(template)))
1317 end.join("\n") 1332 end.join("\n")
1318 1333
1319 content_tag('div', content_tag('label', _('Profile organization'), :for => 'template-options', :class => 'formlabel') + 1334 content_tag('div', content_tag('label', _('Profile organization'), :for => 'template-options', :class => 'formlabel') +
@@ -1377,7 +1392,7 @@ module ApplicationHelper @@ -1377,7 +1392,7 @@ module ApplicationHelper
1377 # are old things that do not support it we are keeping this hot spot. 1392 # are old things that do not support it we are keeping this hot spot.
1378 html = @plugins.pipeline(:parse_content, html, source).first 1393 html = @plugins.pipeline(:parse_content, html, source).first
1379 end 1394 end
1380 - html 1395 + html && html.html_safe
1381 end 1396 end
1382 1397
1383 def convert_macro(html, source) 1398 def convert_macro(html, source)
@@ -1407,6 +1422,43 @@ module ApplicationHelper @@ -1407,6 +1422,43 @@ module ApplicationHelper
1407 content_tag('ul', article.versions.map {|v| link_to("r#{v.version}", @page.url.merge(:version => v.version))}) 1422 content_tag('ul', article.versions.map {|v| link_to("r#{v.version}", @page.url.merge(:version => v.version))})
1408 end 1423 end
1409 1424
  1425 + def search_input_with_suggestions(name, asset, default, options = {})
  1426 + text_field_tag name, default, options.merge({:class => 'search-input-with-suggestions', 'data-asset' => asset})
  1427 + end
  1428 +
  1429 + def profile_suggestion_profile_connections(suggestion)
  1430 + profiles = suggestion.profile_connections.first(4).map do |profile|
  1431 + link_to(profile_image(profile, :icon, :title => profile.name), profile.url, :class => 'profile-suggestion-connection-icon')
  1432 + end
  1433 +
  1434 + controller_target = suggestion.suggestion_type == 'Person' ? :friends : :memberships
  1435 + profiles << link_to("<big> +#{suggestion.profile_connections.count - 4}</big>", :controller => controller_target, :action => :connections, :id => suggestion.suggestion_id) if suggestion.profile_connections.count > 4
  1436 +
  1437 + if profiles.present?
  1438 + content_tag(:div, profiles.join , :class => 'profile-connections')
  1439 + else
  1440 + ''
  1441 + end
  1442 + end
  1443 +
  1444 + def profile_suggestion_tag_connections(suggestion)
  1445 + tags = suggestion.tag_connections.first(4).map do |tag|
  1446 + tag.name + ', '
  1447 + end
  1448 + last_tag = tags.pop
  1449 + tags << last_tag.strip.chop if last_tag.present?
  1450 + title = tags.join
  1451 +
  1452 + controller_target = suggestion.suggestion_type == 'Person' ? :friends : :memberships
  1453 + tags << ' ' + link_to('...', {:controller => controller_target, :action => :connections, :id => suggestion.suggestion_id}, :class => 'more-tag-connections', :title => _('See all connections')) if suggestion.tag_connections.count > 4
  1454 +
  1455 + if tags.present?
  1456 + content_tag(:div, tags.join, :class => 'tag-connections', :title => title)
  1457 + else
  1458 + ''
  1459 + end
  1460 + end
  1461 +
1410 def labelled_colorpicker_field(human_name, object_name, method, options = {}) 1462 def labelled_colorpicker_field(human_name, object_name, method, options = {})
1411 options[:id] ||= 'text-field-' + FormsHelper.next_id_number 1463 options[:id] ||= 'text-field-' + FormsHelper.next_id_number
1412 content_tag('label', human_name, :for => options[:id], :class => 'formlabel') + 1464 content_tag('label', human_name, :for => options[:id], :class => 'formlabel') +
app/helpers/article_helper.rb
@@ -3,6 +3,12 @@ module ArticleHelper @@ -3,6 +3,12 @@ module ArticleHelper
3 include PrototypeHelper 3 include PrototypeHelper
4 include TokenHelper 4 include TokenHelper
5 5
  6 + def article_reported_version(article)
  7 + search_path = Rails.root.join('app', 'views', 'shared', 'reported_versions')
  8 + partial_path = File.join('shared', 'reported_versions', 'profile', partial_for_class_in_view_path(article.class, search_path))
  9 + render_to_string(:partial => partial_path, :locals => {:article => article})
  10 + end
  11 +
6 def custom_options_for_article(article, tokenized_children) 12 def custom_options_for_article(article, tokenized_children)
7 @article = article 13 @article = article
8 14
@@ -71,12 +77,59 @@ module ArticleHelper @@ -71,12 +77,59 @@ module ArticleHelper
71 content_tag('div', 77 content_tag('div',
72 radio_button(:article, :published, false) + 78 radio_button(:article, :published, false) +
73 content_tag('label', _('Private'), :for => 'article_published_false', :id => "label_private") 79 content_tag('label', _('Private'), :for => 'article_published_false', :id => "label_private")
74 - ) +  
75 - (article.profile.community? ? content_tag('div',  
76 - content_tag('label', _('Fill in the search field to add the exception users to see this content'), :id => "text-input-search-exception-users") +  
77 - token_input_field_tag(:q, 'search-article-privacy-exceptions', {:action => 'search_article_privacy_exceptions'},  
78 - {:focus => false, :hint_text => _('Type in a search term for a user'), :pre_populate => tokenized_children})) :  
79 - '')) 80 + ) +
  81 + privacity_exceptions(article, tokenized_children)
  82 + )
  83 + end
  84 +
  85 + def privacity_exceptions(article, tokenized_children)
  86 + content_tag('div',
  87 + content_tag('div',
  88 + (
  89 + if article.profile
  90 + add_option_to_followers(article, tokenized_children)
  91 + else
  92 + ''
  93 + end
  94 + )
  95 + ),
  96 + :style => "margin-left:10px"
  97 + )
  98 + end
  99 +
  100 + def add_option_to_followers(article, tokenized_children)
  101 + label_message = article.profile.organization? ? _('For all community members') : _('For all your friends')
  102 +
  103 + check_box(
  104 + :article,
  105 + :show_to_followers,
  106 + {:class => "custom_privacy_option"}
  107 + ) +
  108 + content_tag(
  109 + 'label',
  110 + label_message,
  111 + :for => 'article_show_to_followers',
  112 + :id => 'label_show_to_followers'
  113 + ) +
  114 + (article.profile.community? ?
  115 + content_tag(
  116 + 'div',
  117 + content_tag(
  118 + 'label',
  119 + _('Fill in the search field to add the exception users to see this content'),
  120 + :id => "text-input-search-exception-users"
  121 + ) +
  122 + token_input_field_tag(
  123 + :q,
  124 + 'search-article-privacy-exceptions',
  125 + {:action => 'search_article_privacy_exceptions'},
  126 + {
  127 + :focus => false,
  128 + :hint_text => _('Type in a search term for a user'),
  129 + :pre_populate => tokenized_children
  130 + }
  131 + )
  132 + ) : '')
80 end 133 end
81 134
82 def prepare_to_token_input(array) 135 def prepare_to_token_input(array)
app/helpers/boxes_helper.rb
@@ -170,49 +170,54 @@ module BoxesHelper @@ -170,49 +170,54 @@ module BoxesHelper
170 else 170 else
171 "before-block-#{block.id}" 171 "before-block-#{block.id}"
172 end 172 end
173 -  
174 - content_tag('div', '&nbsp;', :id => id, :class => 'block-target' ) + drop_receiving_element(id, :url => { :action => 'move_block', :target => id }, :accept => box.acceptable_blocks, :hoverclass => 'block-target-hover') 173 + if block.nil? or modifiable?(block)
  174 + content_tag('div', '&nbsp;', :id => id, :class => 'block-target' ) + drop_receiving_element(id, :url => { :action => 'move_block', :target => id }, :accept => box.acceptable_blocks, :hoverclass => 'block-target-hover')
  175 + else
  176 + ""
  177 + end
175 end 178 end
176 179
177 # makes the given block draggable so it can be moved away. 180 # makes the given block draggable so it can be moved away.
178 def block_handle(block) 181 def block_handle(block)
179 - draggable_element("block-#{block.id}", :revert => true) 182 + modifiable?(block) ? draggable_element("block-#{block.id}", :revert => true) : ""
180 end 183 end
181 184
182 def block_edit_buttons(block) 185 def block_edit_buttons(block)
183 buttons = [] 186 buttons = []
184 nowhere = 'javascript: return false;' 187 nowhere = 'javascript: return false;'
185 188
186 - if block.first?  
187 - buttons << icon_button('up-disabled', _("Can't move up anymore."), nowhere)  
188 - else  
189 - buttons << icon_button('up', _('Move block up'), { :action => 'move_block_up', :id => block.id }, { :method => 'post' })  
190 - end 189 + if modifiable?(block)
  190 + if block.first?
  191 + buttons << icon_button('up-disabled', _("Can't move up anymore."), nowhere)
  192 + else
  193 + buttons << icon_button('up', _('Move block up'), { :action => 'move_block_up', :id => block.id }, { :method => 'post' })
  194 + end
191 195
192 - if block.last?  
193 - buttons << icon_button('down-disabled', _("Can't move down anymore."), nowhere)  
194 - else  
195 - buttons << icon_button(:down, _('Move block down'), { :action => 'move_block_down' ,:id => block.id }, { :method => 'post'})  
196 - end 196 + if block.last?
  197 + buttons << icon_button('down-disabled', _("Can't move down anymore."), nowhere)
  198 + else
  199 + buttons << icon_button(:down, _('Move block down'), { :action => 'move_block_down' ,:id => block.id }, { :method => 'post'})
  200 + end
197 201
198 - holder = block.owner  
199 - # move to opposite side  
200 - # FIXME too much hardcoded stuff  
201 - if holder.layout_template == 'default'  
202 - if block.box.position == 2 # area 2, left side => move to right side  
203 - buttons << icon_button('right', _('Move to the opposite side'), { :action => 'move_block', :target => 'end-of-box-' + holder.boxes[2].id.to_s, :id => block.id }, :method => 'post' )  
204 - elsif block.box.position == 3 # area 3, right side => move to left side  
205 - buttons << icon_button('left', _('Move to the opposite side'), { :action => 'move_block', :target => 'end-of-box-' + holder.boxes[1].id.to_s, :id => block.id }, :method => 'post' ) 202 + holder = block.owner
  203 + # move to opposite side
  204 + # FIXME too much hardcoded stuff
  205 + if holder.layout_template == 'default'
  206 + if block.box.position == 2 # area 2, left side => move to right side
  207 + buttons << icon_button('right', _('Move to the opposite side'), { :action => 'move_block', :target => 'end-of-box-' + holder.boxes[2].id.to_s, :id => block.id }, :method => 'post' )
  208 + elsif block.box.position == 3 # area 3, right side => move to left side
  209 + buttons << icon_button('left', _('Move to the opposite side'), { :action => 'move_block', :target => 'end-of-box-' + holder.boxes[1].id.to_s, :id => block.id }, :method => 'post' )
  210 + end
206 end 211 end
207 - end  
208 212
209 - if block.editable?  
210 - buttons << colorbox_icon_button(:edit, _('Edit'), { :action => 'edit', :id => block.id })  
211 - end 213 + if block.editable?
  214 + buttons << colorbox_icon_button(:edit, _('Edit'), { :action => 'edit', :id => block.id })
  215 + end
212 216
213 - if !block.main?  
214 - buttons << icon_button(:delete, _('Remove block'), { :action => 'remove', :id => block.id }, { :method => 'post', :confirm => _('Are you sure you want to remove this block?')})  
215 - buttons << icon_button(:clone, _('Clone'), { :action => 'clone_block', :id => block.id }, { :method => 'post' }) 217 + if !block.main?
  218 + buttons << icon_button(:delete, _('Remove block'), { :action => 'remove', :id => block.id }, { :method => 'post', :confirm => _('Are you sure you want to remove this block?')})
  219 + buttons << icon_button(:clone, _('Clone'), { :action => 'clone_block', :id => block.id }, { :method => 'post' })
  220 + end
216 end 221 end
217 222
218 if block.respond_to?(:help) 223 if block.respond_to?(:help)
@@ -248,5 +253,7 @@ module BoxesHelper @@ -248,5 +253,7 @@ module BoxesHelper
248 classes 253 classes
249 end 254 end
250 255
251 - 256 + def modifiable?(block)
  257 + return !block.fixed || environment.admins.include?(user)
  258 + end
252 end 259 end
app/helpers/cms_helper.rb
@@ -40,12 +40,8 @@ module CmsHelper @@ -40,12 +40,8 @@ module CmsHelper
40 end 40 end
41 end 41 end
42 42
43 - def display_spread_button(profile, article)  
44 - if profile.person?  
45 - expirable_button article, :spread, _('Spread this'), :action => 'publish', :id => article.id  
46 - elsif profile.community? && environment.portal_community  
47 - expirable_button article, :spread, _('Spread this'), :action => 'publish_on_portal_community', :id => article.id  
48 - end 43 + def display_spread_button(article)
  44 + expirable_button article, :spread, _('Spread this'), {:action => 'publish', :id => article.id}, {:class => 'colorbox'}
49 end 45 end
50 46
51 def display_delete_button(article) 47 def display_delete_button(article)
app/helpers/content_viewer_helper.rb
@@ -10,7 +10,7 @@ module ContentViewerHelper @@ -10,7 +10,7 @@ module ContentViewerHelper
10 end 10 end
11 11
12 def number_of_comments(article) 12 def number_of_comments(article)
13 - display_number_of_comments(article.comments_count - article.spam_comments_count) 13 + display_number_of_comments(article.comments_count - article.spam_comments_count.to_i)
14 end 14 end
15 15
16 def article_title(article, args = {}) 16 def article_title(article, args = {})
@@ -45,7 +45,7 @@ module ContentViewerHelper @@ -45,7 +45,7 @@ module ContentViewerHelper
45 { article.environment.locales[translation.language] => { :href => url_for(translation.url) } } 45 { article.environment.locales[translation.language] => { :href => url_for(translation.url) } }
46 end 46 end
47 content_tag(:div, link_to(_('Translations'), '#', 47 content_tag(:div, link_to(_('Translations'), '#',
48 - :onmouseover => "toggleSubmenu(this, '#{_('Translations')}', #{links.to_json}); return false", 48 + :onmouseover => "toggleSubmenu(this, '#{_('Translations')}', #{CGI::escape_html(links.to_json)}); return false",
49 :class => 'article-translations-menu simplemenu-trigger up'), 49 :class => 'article-translations-menu simplemenu-trigger up'),
50 :class => 'article-translations') 50 :class => 'article-translations')
51 end 51 end
app/helpers/forms_helper.rb
@@ -265,7 +265,7 @@ module FormsHelper @@ -265,7 +265,7 @@ module FormsHelper
265 ) 265 )
266 end 266 end
267 267
268 - def select_profile_folder(label_text, field_id, profile, default_value='', html_options = {}, js_options = {}, find_options = {}) 268 + def select_profile_folder(label_text, field_id, profile, default_value='', html_options = {}, js_options = {}, find_options = {}, extra_options = {})
269 if find_options.empty? 269 if find_options.empty?
270 folders = profile.folders 270 folders = profile.folders
271 else 271 else
@@ -276,7 +276,7 @@ module FormsHelper @@ -276,7 +276,7 @@ module FormsHelper
276 select_tag( 276 select_tag(
277 field_id, 277 field_id,
278 options_for_select( 278 options_for_select(
279 - [[profile.identifier, '']] + 279 + [[(extra_options[:root_label] || profile.identifier), '']] +
280 folders.collect {|f| [ profile.identifier + '/' + f.full_name, f.id.to_s ] }, 280 folders.collect {|f| [ profile.identifier + '/' + f.full_name, f.id.to_s ] },
281 default_value.to_s 281 default_value.to_s
282 ), 282 ),
app/helpers/layout_helper.rb
@@ -2,12 +2,31 @@ module LayoutHelper @@ -2,12 +2,31 @@ module LayoutHelper
2 2
3 def body_classes 3 def body_classes
4 # Identify the current controller and action for the CSS: 4 # Identify the current controller and action for the CSS:
  5 + (logged_in? ? " logged-in" : "") +
5 " controller-#{controller.controller_name}" + 6 " controller-#{controller.controller_name}" +
6 " action-#{controller.controller_name}-#{controller.action_name}" + 7 " action-#{controller.controller_name}-#{controller.action_name}" +
7 " template-#{@layout_template || if profile.blank? then 'default' else profile.layout_template end}" + 8 " template-#{@layout_template || if profile.blank? then 'default' else profile.layout_template end}" +
8 (!profile.nil? && profile.is_on_homepage?(request.path,@page) ? " profile-homepage" : "") 9 (!profile.nil? && profile.is_on_homepage?(request.path,@page) ? " profile-homepage" : "")
9 end 10 end
10 11
  12 + def html_tag_classes
  13 + [
  14 + body_classes, (
  15 + profile.blank? ? nil : [
  16 + 'profile-type-is-' + profile.class.name.downcase,
  17 + 'profile-name-is-' + profile.identifier,
  18 + ]
  19 + ), 'theme-' + current_theme,
  20 + @plugins.dispatch(:html_tag_classes).map do |content|
  21 + if content.respond_to?(:call)
  22 + instance_exec(&content)
  23 + else
  24 + content.html_safe
  25 + end
  26 + end
  27 + ].flatten.compact.join(' ')
  28 + end
  29 +
11 def noosfero_javascript 30 def noosfero_javascript
12 plugins_javascripts = @plugins.map { |plugin| [plugin.js_files].flatten.map { |js| plugin.class.public_path(js) } }.flatten 31 plugins_javascripts = @plugins.map { |plugin| [plugin.js_files].flatten.map { |js| plugin.class.public_path(js) } }.flatten
13 32
@@ -17,6 +36,8 @@ module LayoutHelper @@ -17,6 +36,8 @@ module LayoutHelper
17 unless plugins_javascripts.empty? 36 unless plugins_javascripts.empty?
18 output += javascript_include_tag plugins_javascripts, :cache => "cache/plugins-#{Digest::MD5.hexdigest plugins_javascripts.to_s}" 37 output += javascript_include_tag plugins_javascripts, :cache => "cache/plugins-#{Digest::MD5.hexdigest plugins_javascripts.to_s}"
19 end 38 end
  39 + output += theme_javascript_ng.to_s
  40 +
20 output 41 output
21 end 42 end
22 43
@@ -27,6 +48,7 @@ module LayoutHelper @@ -27,6 +48,7 @@ module LayoutHelper
27 'thickbox', 48 'thickbox',
28 'lightbox', 49 'lightbox',
29 'colorbox', 50 'colorbox',
  51 + 'selectordie',
30 'inputosaurus', 52 'inputosaurus',
31 pngfix_stylesheet_path, 53 pngfix_stylesheet_path,
32 ] + tokeninput_stylesheets 54 ] + tokeninput_stylesheets
@@ -84,6 +106,10 @@ module LayoutHelper @@ -84,6 +106,10 @@ module LayoutHelper
84 theme_path + '/style.css' 106 theme_path + '/style.css'
85 end 107 end
86 108
  109 + def layout_template
  110 + if profile then profile.layout_template else environment.layout_template end
  111 + end
  112 +
87 def addthis_javascript 113 def addthis_javascript
88 if NOOSFERO_CONF['addthis_enabled'] 114 if NOOSFERO_CONF['addthis_enabled']
89 '<script src="https://s7.addthis.com/js/152/addthis_widget.js"></script>' 115 '<script src="https://s7.addthis.com/js/152/addthis_widget.js"></script>'
@@ -91,7 +117,7 @@ module LayoutHelper @@ -91,7 +117,7 @@ module LayoutHelper
91 end 117 end
92 118
93 def meta_description_tag(article=nil) 119 def meta_description_tag(article=nil)
94 - article ? truncate(strip_tags(article.body.to_s), :length => 200) : environment.name 120 + article ? CGI.escapeHTML(truncate(strip_tags(article.body.to_s), :length => 200)) : environment.name
95 end 121 end
96 end 122 end
97 123
app/helpers/person_notifier_helper.rb
@@ -1,15 +0,0 @@ @@ -1,15 +0,0 @@
1 -module PersonNotifierHelper  
2 -  
3 - include ApplicationHelper  
4 -  
5 - private  
6 -  
7 - def path_to_image(source)  
8 - top_url + source  
9 - end  
10 -  
11 - def top_url  
12 - top_url = @profile.environment ? @profile.environment.top_url : ''  
13 - end  
14 -  
15 -end  
app/helpers/profile_helper.rb
1 module ProfileHelper 1 module ProfileHelper
2 2
3 COMMON_CATEGORIES = ActiveSupport::OrderedHash.new 3 COMMON_CATEGORIES = ActiveSupport::OrderedHash.new
4 - COMMON_CATEGORIES[:content] = [:blogs, :image_galleries, :events, :tags] 4 + COMMON_CATEGORIES[:content] = [:blogs, :image_galleries, :events, :article_tags]
5 COMMON_CATEGORIES[:interests] = [:interests] 5 COMMON_CATEGORIES[:interests] = [:interests]
6 COMMON_CATEGORIES[:general] = nil 6 COMMON_CATEGORIES[:general] = nil
7 7
@@ -41,6 +41,8 @@ module ProfileHelper @@ -41,6 +41,8 @@ module ProfileHelper
41 :birth_date => _('Date of birth'), 41 :birth_date => _('Date of birth'),
42 :created_at => _('Profile created at'), 42 :created_at => _('Profile created at'),
43 :members_count => _('Members'), 43 :members_count => _('Members'),
  44 + :privacy_setting => _('Privacy setting'),
  45 + :article_tags => _('Tags')
44 } 46 }
45 47
46 EXCEPTION = { 48 EXCEPTION = {
@@ -63,7 +65,7 @@ module ProfileHelper @@ -63,7 +65,7 @@ module ProfileHelper
63 65
64 def title(field, entry = nil) 66 def title(field, entry = nil)
65 return self.send("#{field}_custom_title", entry) if MULTIPLE[kind].include?(field) && entry.present? 67 return self.send("#{field}_custom_title", entry) if MULTIPLE[kind].include?(field) && entry.present?
66 - CUSTOM_LABELS[field.to_sym] || field.to_s.humanize 68 + CUSTOM_LABELS[field.to_sym] || _(field.to_s.humanize)
67 end 69 end
68 70
69 def display_field(field) 71 def display_field(field)
@@ -73,14 +75,18 @@ module ProfileHelper @@ -73,14 +75,18 @@ module ProfileHelper
73 return '' 75 return ''
74 end 76 end
75 value = begin profile.send(field) rescue nil end 77 value = begin profile.send(field) rescue nil end
76 - if !value.blank? 78 + return '' if value.blank?
  79 + if value.kind_of?(Hash)
  80 + content = self.send("treat_#{field}", value)
  81 + content_tag('tr', content_tag('td', title(field), :class => 'field-name') + content_tag('td', content))
  82 + else
77 entries = multiple ? value : [] << value 83 entries = multiple ? value : [] << value
78 entries.map do |entry| 84 entries.map do |entry|
79 content = self.send("treat_#{field}", entry) 85 content = self.send("treat_#{field}", entry)
80 - content_tag('tr', content_tag('td', title(field, entry), :class => 'field-name') + content_tag('td', content)) 86 + unless content.blank?
  87 + content_tag('tr', content_tag('td', title(field, entry), :class => 'field-name') + content_tag('td', content))
  88 + end
81 end.join("\n") 89 end.join("\n")
82 - else  
83 - ''  
84 end 90 end
85 end 91 end
86 92
@@ -142,7 +148,7 @@ module ProfileHelper @@ -142,7 +148,7 @@ module ProfileHelper
142 link_to events.published.count, :controller => 'events', :action => 'events' 148 link_to events.published.count, :controller => 'events', :action => 'events'
143 end 149 end
144 150
145 - def treat_tags(tags) 151 + def treat_article_tags(tags)
146 tag_cloud @tags, :id, { :action => 'tags' }, :max_size => 18, :min_size => 10 152 tag_cloud @tags, :id, { :action => 'tags' }, :max_size => 18, :min_size => 10
147 end 153 end
148 154
app/helpers/role_helper.rb
1 module RoleHelper 1 module RoleHelper
  2 +
  3 + def role_available_permissions(role)
  4 + role.kind == "Environment" ? ['Environment', 'Profile'] : [role.kind]
  5 + end
  6 +
2 end 7 end
app/helpers/search_helper.rb
@@ -5,22 +5,31 @@ module SearchHelper @@ -5,22 +5,31 @@ module SearchHelper
5 BLOCKS_SEARCH_LIMIT = 24 5 BLOCKS_SEARCH_LIMIT = 24
6 MULTIPLE_SEARCH_LIMIT = 8 6 MULTIPLE_SEARCH_LIMIT = 8
7 7
8 - SEARCHES = ActiveSupport::OrderedHash[  
9 - :articles, _('Contents'),  
10 - :enterprises, _('Enterprises'),  
11 - :people, _('People'),  
12 - :communities, _('Communities'),  
13 - :products, _('Products and Services'),  
14 - :events, _('Events'),  
15 - ] 8 + FILTERS_TRANSLATIONS = {
  9 + :order => _('Order'),
  10 + :display => _('Display')
  11 + }
16 12
17 - FILTER_TRANSLATION = {  
18 - 'more_popular' => _('More popular'),  
19 - 'more_active' => _('More active'),  
20 - 'more_recent' => _('More recent'),  
21 - 'more_comments' => _('More comments') 13 + FILTERS_OPTIONS_TRANSLATION = {
  14 + :order => {
  15 + 'more_popular' => _('More popular'),
  16 + 'more_active' => _('More active'),
  17 + 'more_recent' => _('More recent'),
  18 + 'more_comments' => _('More comments')
  19 + },
  20 + :display => {
  21 + 'map' => _('Map'),
  22 + 'full' => _('Full'),
  23 + 'compact' => _('Compact')
  24 + }
22 } 25 }
23 26
  27 + COMMON_PROFILE_LIST_BLOCK = [
  28 + :enterprises,
  29 + :people,
  30 + :communities
  31 + ]
  32 +
24 # FIXME remove it after search_controler refactored 33 # FIXME remove it after search_controler refactored
25 include EventsHelper 34 include EventsHelper
26 35
@@ -50,7 +59,7 @@ module SearchHelper @@ -50,7 +59,7 @@ module SearchHelper
50 end 59 end
51 60
52 def display?(asset, mode) 61 def display?(asset, mode)
53 - defined?(asset_class(asset)::SEARCH_DISPLAYS) && asset_class(asset)::SEARCH_DISPLAYS.include?(mode.to_s) 62 + defined?(asset_class(asset)::SEARCH_FILTERS[:display]) && asset_class(asset)::SEARCH_FILTERS[:display].include?(mode.to_s)
54 end 63 end
55 64
56 def display_results(searches=nil, asset=nil) 65 def display_results(searches=nil, asset=nil)
@@ -87,6 +96,16 @@ module SearchHelper @@ -87,6 +96,16 @@ module SearchHelper
87 end 96 end
88 end 97 end
89 98
  99 + def select_filter(name, options, default = nil)
  100 + if options.size <= 1
  101 + return
  102 + else
  103 + options = options.map {|option| [FILTERS_OPTIONS_TRANSLATION[name][option], option]}
  104 + options = options_for_select(options, :selected => (params[name] || default))
  105 + select_tag(name, options)
  106 + end
  107 + end
  108 +
90 def display_selector(asset, display, float = 'right') 109 def display_selector(asset, display, float = 'right')
91 display = nil if display.blank? 110 display = nil if display.blank?
92 display ||= asset_class(asset).default_search_display 111 display ||= asset_class(asset).default_search_display
@@ -94,43 +113,39 @@ module SearchHelper @@ -94,43 +113,39 @@ module SearchHelper
94 compact_link = display?(asset, :compact) ? (display == 'compact' ? _('Compact') : link_to(_('Compact'), params.merge(:display => 'compact'))) : nil 113 compact_link = display?(asset, :compact) ? (display == 'compact' ? _('Compact') : link_to(_('Compact'), params.merge(:display => 'compact'))) : nil
95 map_link = display?(asset, :map) ? (display == 'map' ? _('Map') : link_to(_('Map'), params.merge(:display => 'map'))) : nil 114 map_link = display?(asset, :map) ? (display == 'map' ? _('Map') : link_to(_('Map'), params.merge(:display => 'map'))) : nil
96 full_link = display?(asset, :full) ? (display == 'full' ? _('Full') : link_to(_('Full'), params.merge(:display => 'full'))) : nil 115 full_link = display?(asset, :full) ? (display == 'full' ? _('Full') : link_to(_('Full'), params.merge(:display => 'full'))) : nil
97 - content_tag('div', 116 + content_tag('div',
98 content_tag('strong', _('Display')) + ': ' + [compact_link, map_link, full_link].compact.join(' | ').html_safe, 117 content_tag('strong', _('Display')) + ': ' + [compact_link, map_link, full_link].compact.join(' | ').html_safe,
99 :class => 'search-customize-options' 118 :class => 'search-customize-options'
100 ) 119 )
101 end 120 end
102 end 121 end
103 122
104 - def filter_selector(asset, filter, float = 'right') 123 + def filters(asset)
  124 + return if !asset
105 klass = asset_class(asset) 125 klass = asset_class(asset)
106 - if klass::SEARCH_FILTERS.count > 1  
107 - options = options_for_select(klass::SEARCH_FILTERS.map {|f| [FILTER_TRANSLATION[f], f]}, filter)  
108 - url_params = url_for(params.merge(:filter => 'FILTER'))  
109 - onchange = "document.location.href = '#{url_params}'.replace('FILTER', this.value)"  
110 - select_field = select_tag(:filter, options, :onchange => onchange)  
111 - content_tag('div',  
112 - content_tag('strong', _('Filter')) + ': ' + select_field,  
113 - :class => "search-customize-options"  
114 - )  
115 - end 126 + content_tag('div', klass::SEARCH_FILTERS.map do |name, options|
  127 + default = klass.respond_to?("default_search_#{name}") ? klass.send("default_search_#{name}".to_s) : nil
  128 + select_filter(name, options, default)
  129 + end.join("\n"), :id => 'search-filters')
  130 + end
  131 +
  132 + def assets_menu(selected)
  133 + assets = @enabled_searches.keys
  134 + # Events is a search asset but do not have a good interface for
  135 + #TODO searching. When this is solved we may add it back again to the assets
  136 + # menu.
  137 + assets.delete(:events)
  138 + content_tag('ul',
  139 + assets.map do |asset|
  140 + options = {}
  141 + options.merge!(:class => 'selected') if selected.to_s == asset.to_s
  142 + content_tag('li', asset_link(asset), options)
  143 + end.join("\n"),
  144 + :id => 'assets-menu')
116 end 145 end
117 146
118 - def filter_title(asset, filter)  
119 - {  
120 - 'articles_more_recent' => _('More recent contents from network'),  
121 - 'articles_more_popular' => _('More viewed contents from network'),  
122 - 'articles_more_comments' => _('Most commented contents from network'),  
123 - 'people_more_recent' => _('More recent people from network'),  
124 - 'people_more_active' => _('More active people from network'),  
125 - 'people_more_popular' => _('More popular people from network'),  
126 - 'communities_more_recent' => _('More recent communities from network'),  
127 - 'communities_more_active' => _('More active communities from network'),  
128 - 'communities_more_popular' => _('More popular communities from network'),  
129 - 'enterprises_more_recent' => _('More recent enterprises from network'),  
130 - 'enterprises_more_active' => _('More active enterprises from network'),  
131 - 'enterprises_more_popular' => _('More popular enterprises from network'),  
132 - 'products_more_recent' => _('Highlights'),  
133 - }[asset.to_s + '_' + filter].to_s 147 + def asset_link(asset)
  148 + link_to(@enabled_searches[asset], "/search/#{asset}")
134 end 149 end
135 150
136 end 151 end
app/helpers/search_term_helper.rb 0 → 100644
@@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
  1 +module SearchTermHelper
  2 + def register_search_term(term, total, indexed, context, asset='all')
  3 + normalized_term = normalize_term(term)
  4 + if normalized_term.present?
  5 + search_term = SearchTerm.find_or_create(normalized_term, context, asset)
  6 + SearchTermOccurrence.create!(:search_term => search_term, :total => total, :indexed => indexed)
  7 + end
  8 + end
  9 +
  10 + #FIXME For some reason the job is created but nothing is ran.
  11 + #handle_asynchronously :register_search_term
  12 +
  13 + #TODO Think smarter criteria to normalize search terms properly
  14 + def normalize_term(search_term)
  15 + search_term ||= ''
  16 + search_term.downcase
  17 + end
  18 +end
app/helpers/sweeper_helper.rb
@@ -56,12 +56,12 @@ module SweeperHelper @@ -56,12 +56,12 @@ module SweeperHelper
56 if profile 56 if profile
57 profile.blocks.each {|block| 57 profile.blocks.each {|block|
58 conditions = block.class.expire_on 58 conditions = block.class.expire_on
59 - blocks_to_expire << block unless (conditions[:profile] & causes).empty? 59 + blocks_to_expire << block unless (conditions[:profile] & causes).blank?
60 } 60 }
61 end 61 end
62 environment.blocks.each {|block| 62 environment.blocks.each {|block|
63 conditions = block.class.expire_on 63 conditions = block.class.expire_on
64 - blocks_to_expire << block unless (conditions[:environment] & causes).empty? 64 + blocks_to_expire << block unless (conditions[:environment] & causes).blank?
65 } 65 }
66 66
67 blocks_to_expire.uniq! 67 blocks_to_expire.uniq!
app/helpers/tinymce_helper.rb
@@ -11,7 +11,7 @@ module TinymceHelper @@ -11,7 +11,7 @@ module TinymceHelper
11 end 11 end
12 12
13 def tinymce_init_js options = {} 13 def tinymce_init_js options = {}
14 - options.merge! :document_base_url => environment.top_url, 14 + options.merge! :document_base_url => top_url,
15 :content_css => "/stylesheets/tinymce.css,#{macro_css_files}", 15 :content_css => "/stylesheets/tinymce.css,#{macro_css_files}",
16 :plugins => %w[compat3x advlist autolink lists link image charmap print preview hr anchor pagebreak 16 :plugins => %w[compat3x advlist autolink lists link image charmap print preview hr anchor pagebreak
17 searchreplace wordcount visualblocks visualchars code fullscreen 17 searchreplace wordcount visualblocks visualchars code fullscreen
app/helpers/token_helper.rb
@@ -12,6 +12,7 @@ module TokenHelper @@ -12,6 +12,7 @@ module TokenHelper
12 options[:search_delay] ||= 1000 12 options[:search_delay] ||= 1000
13 options[:prevent_duplicates] ||= true 13 options[:prevent_duplicates] ||= true
14 options[:backspace_delete_item] ||= false 14 options[:backspace_delete_item] ||= false
  15 + options[:zindex] ||= 999
15 options[:focus] ||= false 16 options[:focus] ||= false
16 options[:avoid_enter] ||= true 17 options[:avoid_enter] ||= true
17 options[:on_result] ||= 'null' 18 options[:on_result] ||= 'null'
@@ -31,6 +32,7 @@ module TokenHelper @@ -31,6 +32,7 @@ module TokenHelper
31 searchDelay: #{options[:search_delay].to_json}, 32 searchDelay: #{options[:search_delay].to_json},
32 preventDuplicates: #{options[:prevent_duplicates].to_json}, 33 preventDuplicates: #{options[:prevent_duplicates].to_json},
33 backspaceDeleteItem: #{options[:backspace_delete_item].to_json}, 34 backspaceDeleteItem: #{options[:backspace_delete_item].to_json},
  35 + zindex: #{options[:zindex].to_json},
34 queryParam: #{options[:query_param].to_json}, 36 queryParam: #{options[:query_param].to_json},
35 tokenLimit: #{options[:token_limit].to_json}, 37 tokenLimit: #{options[:token_limit].to_json},
36 onResult: #{options[:on_result]}, 38 onResult: #{options[:on_result]},
app/mailers/user_mailer.rb
@@ -41,6 +41,23 @@ class UserMailer &lt; ActionMailer::Base @@ -41,6 +41,23 @@ class UserMailer &lt; ActionMailer::Base
41 ) 41 )
42 end 42 end
43 43
  44 + def profiles_suggestions_email(user)
  45 + @recipient = user.name
  46 + @environment = user.environment.name
  47 + @url = user.environment.top_url
  48 + @people_suggestions_url = user.people_suggestions_url
  49 + @people_suggestions = user.suggested_people.sample(3)
  50 + @communities_suggestions_url = user.communities_suggestions_url
  51 + @communities_suggestions = user.suggested_communities.sample(3)
  52 +
  53 + mail(
  54 + content_type: 'text/html',
  55 + to: user.email,
  56 + from: "#{user.environment.name} <#{user.environment.contact_email}>",
  57 + subject: _("[%s] What about grow up your network?") % user.environment.name
  58 + )
  59 + end
  60 +
44 class Job < Struct.new(:user, :method) 61 class Job < Struct.new(:user, :method)
45 def perform 62 def perform
46 UserMailer.send(method, user).deliver 63 UserMailer.send(method, user).deliver
app/models/add_friend.rb
@@ -14,6 +14,11 @@ class AddFriend &lt; Task @@ -14,6 +14,11 @@ class AddFriend &lt; Task
14 alias :friend :target 14 alias :friend :target
15 alias :friend= :target= 15 alias :friend= :target=
16 16
  17 + after_create do |task|
  18 + TaskMailer.invitation_notification(task).deliver unless task.friend
  19 + remove_from_suggestion_list(task)
  20 + end
  21 +
17 def perform 22 def perform
18 target.add_friend(requestor, group_for_friend) 23 target.add_friend(requestor, group_for_friend)
19 requestor.add_friend(target, group_for_person) 24 requestor.add_friend(target, group_for_person)
@@ -48,4 +53,8 @@ class AddFriend &lt; Task @@ -48,4 +53,8 @@ class AddFriend &lt; Task
48 {:type => :profile_image, :profile => requestor, :url => requestor.url} 53 {:type => :profile_image, :profile => requestor, :url => requestor.url}
49 end 54 end
50 55
  56 + def remove_from_suggestion_list(task)
  57 + suggestion = task.requestor.profile_suggestions.find_by_suggestion_id task.target.id
  58 + suggestion.disable if suggestion
  59 + end
51 end 60 end
app/models/add_member.rb
@@ -10,6 +10,10 @@ class AddMember &lt; Task @@ -10,6 +10,10 @@ class AddMember &lt; Task
10 10
11 settings_items :roles 11 settings_items :roles
12 12
  13 + after_create do |task|
  14 + remove_from_suggestion_list(task)
  15 + end
  16 +
13 def perform 17 def perform
14 if !self.roles or (self.roles.uniq.compact.length == 1 and self.roles.uniq.compact.first.to_i.zero?) 18 if !self.roles or (self.roles.uniq.compact.length == 1 and self.roles.uniq.compact.first.to_i.zero?)
15 self.roles = [Profile::Roles.member(organization.environment.id).id] 19 self.roles = [Profile::Roles.member(organization.environment.id).id]
@@ -46,4 +50,9 @@ class AddMember &lt; Task @@ -46,4 +50,9 @@ class AddMember &lt; Task
46 _('You will need login to %{system} in order to accept or reject %{requestor} as a member of %{organization}.') % { :system => target.environment.name, :requestor => requestor.name, :organization => organization.name } 50 _('You will need login to %{system} in order to accept or reject %{requestor} as a member of %{organization}.') % { :system => target.environment.name, :requestor => requestor.name, :organization => organization.name }
47 end 51 end
48 52
  53 + def remove_from_suggestion_list(task)
  54 + suggestion = task.requestor.profile_suggestions.find_by_suggestion_id task.target.id
  55 + suggestion.disable if suggestion
  56 + end
  57 +
49 end 58 end
app/models/approve_article.rb
@@ -22,6 +22,7 @@ class ApproveArticle &lt; Task @@ -22,6 +22,7 @@ class ApproveArticle &lt; Task
22 end 22 end
23 23
24 settings_items :closing_statment, :article_parent_id, :highlighted 24 settings_items :closing_statment, :article_parent_id, :highlighted
  25 + settings_items :create_link, :type => :boolean, :default => false
25 26
26 def article_parent 27 def article_parent
27 Article.find_by_id article_parent_id.to_i 28 Article.find_by_id article_parent_id.to_i
@@ -48,7 +49,11 @@ class ApproveArticle &lt; Task @@ -48,7 +49,11 @@ class ApproveArticle &lt; Task
48 end 49 end
49 50
50 def perform 51 def perform
51 - article.copy!(:name => name, :abstract => abstract, :body => body, :profile => target, :reference_article => article, :parent => article_parent, :highlighted => highlighted, :source => article.source, :last_changed_by_id => article.last_changed_by_id, :created_by_id => article.created_by_id) 52 + if create_link
  53 + LinkArticle.create!(:reference_article => article, :profile => target, :parent => article_parent, :highlighted => highlighted)
  54 + else
  55 + article.copy!(:name => name, :abstract => abstract, :body => body, :profile => target, :reference_article => article, :parent => article_parent, :highlighted => highlighted, :source => article.source, :last_changed_by_id => article.last_changed_by_id, :created_by_id => article.created_by_id)
  56 + end
52 end 57 end
53 58
54 def title 59 def title
app/models/article.rb
@@ -2,25 +2,29 @@ require &#39;hpricot&#39; @@ -2,25 +2,29 @@ require &#39;hpricot&#39;
2 2
3 class Article < ActiveRecord::Base 3 class Article < ActiveRecord::Base
4 4
5 - attr_accessible :name, :body, :abstract, :profile, :tag_list, :parent, :allow_members_to_edit, :translation_of_id, :language, :license_id, :parent_id, :display_posts_in_current_language, :category_ids, :posts_per_page, :moderate_comments, :accept_comments, :feed, :published, :source, :highlighted, :notify_comments, :display_hits, :slug, :external_feed_builder, :display_versions, :external_link, :image_builder 5 + attr_accessible :name, :body, :abstract, :profile, :tag_list, :parent,
  6 + :allow_members_to_edit, :translation_of_id, :language,
  7 + :license_id, :parent_id, :display_posts_in_current_language,
  8 + :category_ids, :posts_per_page, :moderate_comments,
  9 + :accept_comments, :feed, :published, :source,
  10 + :highlighted, :notify_comments, :display_hits, :slug,
  11 + :external_feed_builder, :display_versions, :external_link,
  12 + :image_builder, :show_to_followers
6 13
7 acts_as_having_image 14 acts_as_having_image
8 15
9 SEARCHABLE_FIELDS = { 16 SEARCHABLE_FIELDS = {
10 - :name => 10,  
11 - :abstract => 3,  
12 - :body => 2,  
13 - :slug => 1,  
14 - :filename => 1, 17 + :name => {:label => _('Name'), :weight => 10},
  18 + :abstract => {:label => _('Abstract'), :weight => 3},
  19 + :body => {:label => _('Content'), :weight => 2},
  20 + :slug => {:label => _('Slug'), :weight => 1},
  21 + :filename => {:label => _('Filename'), :weight => 1},
15 } 22 }
16 23
17 - SEARCH_FILTERS = %w[  
18 - more_recent  
19 - more_popular  
20 - more_comments  
21 - ]  
22 -  
23 - SEARCH_DISPLAYS = %w[full] 24 + SEARCH_FILTERS = {
  25 + :order => %w[more_recent more_popular more_comments],
  26 + :display => %w[full]
  27 + }
24 28
25 def self.default_search_display 29 def self.default_search_display
26 'full' 30 'full'
@@ -103,6 +107,11 @@ class Article &lt; ActiveRecord::Base @@ -103,6 +107,11 @@ class Article &lt; ActiveRecord::Base
103 self.activity.destroy if self.activity 107 self.activity.destroy if self.activity
104 end 108 end
105 109
  110 + after_destroy :destroy_link_article
  111 + def destroy_link_article
  112 + Article.where(:reference_article_id => self.id, :type => LinkArticle).destroy_all
  113 + end
  114 +
106 xss_terminate :only => [ :name ], :on => 'validation', :with => 'white_list' 115 xss_terminate :only => [ :name ], :on => 'validation', :with => 'white_list'
107 116
108 scope :in_category, lambda { |category| 117 scope :in_category, lambda { |category|
@@ -285,13 +294,6 @@ class Article &lt; ActiveRecord::Base @@ -285,13 +294,6 @@ class Article &lt; ActiveRecord::Base
285 end 294 end
286 end 295 end
287 296
288 - def reported_version(options = {})  
289 - article = self  
290 - search_path = Rails.root.join('app', 'views', 'shared', 'reported_versions')  
291 - partial_path = File.join('shared', 'reported_versions', partial_for_class_in_view_path(article.class, search_path))  
292 - lambda { render_to_string(:partial => partial_path, :locals => {:article => article}) }  
293 - end  
294 -  
295 # returns the data of the article. Must be overriden in each subclass to 297 # returns the data of the article. Must be overriden in each subclass to
296 # provide the correct content for the article. 298 # provide the correct content for the article.
297 def data 299 def data
@@ -340,7 +342,7 @@ class Article &lt; ActiveRecord::Base @@ -340,7 +342,7 @@ class Article &lt; ActiveRecord::Base
340 def belongs_to_blog? 342 def belongs_to_blog?
341 self.parent and self.parent.blog? 343 self.parent and self.parent.blog?
342 end 344 end
343 - 345 +
344 def belongs_to_forum? 346 def belongs_to_forum?
345 self.parent and self.parent.forum? 347 self.parent and self.parent.forum?
346 end 348 end
@@ -452,6 +454,7 @@ class Article &lt; ActiveRecord::Base @@ -452,6 +454,7 @@ class Article &lt; ActiveRecord::Base
452 if self.parent && !self.parent.published? 454 if self.parent && !self.parent.published?
453 return false 455 return false
454 end 456 end
  457 +
455 true 458 true
456 else 459 else
457 false 460 false
@@ -471,7 +474,9 @@ class Article &lt; ActiveRecord::Base @@ -471,7 +474,9 @@ class Article &lt; ActiveRecord::Base
471 scope :no_folders, lambda {|profile|{:conditions => ['articles.type NOT IN (?)', profile.folder_types]}} 474 scope :no_folders, lambda {|profile|{:conditions => ['articles.type NOT IN (?)', profile.folder_types]}}
472 scope :galleries, :conditions => [ "articles.type IN ('Gallery')" ] 475 scope :galleries, :conditions => [ "articles.type IN ('Gallery')" ]
473 scope :images, :conditions => { :is_image => true } 476 scope :images, :conditions => { :is_image => true }
  477 + scope :no_images, :conditions => { :is_image => false }
474 scope :text_articles, :conditions => [ 'articles.type IN (?)', text_article_types ] 478 scope :text_articles, :conditions => [ 'articles.type IN (?)', text_article_types ]
  479 + scope :files, :conditions => { :type => 'UploadedFile' }
475 scope :with_types, lambda { |types| { :conditions => [ 'articles.type IN (?)', types ] } } 480 scope :with_types, lambda { |types| { :conditions => [ 'articles.type IN (?)', types ] } }
476 481
477 scope :more_popular, :order => 'hits DESC' 482 scope :more_popular, :order => 'hits DESC'
@@ -483,14 +488,17 @@ class Article &lt; ActiveRecord::Base @@ -483,14 +488,17 @@ class Article &lt; ActiveRecord::Base
483 {:conditions => [" articles.published = ? OR 488 {:conditions => [" articles.published = ? OR
484 articles.last_changed_by_id = ? OR 489 articles.last_changed_by_id = ? OR
485 articles.profile_id = ? OR 490 articles.profile_id = ? OR
486 - ?",  
487 - true, user.id, user.id, user.has_permission?(:view_private_content, profile)] } 491 + ? OR articles.show_to_followers = ? AND ?",
  492 + true, user.id, user.id, user.has_permission?(:view_private_content, profile),
  493 + true, user.follows?(profile)]}
488 end 494 end
489 495
  496 +
490 def display_unpublished_article_to?(user) 497 def display_unpublished_article_to?(user)
491 user == author || allow_view_private_content?(user) || user == profile || 498 user == author || allow_view_private_content?(user) || user == profile ||
492 user.is_admin?(profile.environment) || user.is_admin?(profile) || 499 user.is_admin?(profile.environment) || user.is_admin?(profile) ||
493 - article_privacy_exceptions.include?(user) 500 + article_privacy_exceptions.include?(user) ||
  501 + (self.show_to_followers && user.follows?(profile))
494 end 502 end
495 503
496 def display_to?(user = nil) 504 def display_to?(user = nil)
@@ -519,7 +527,10 @@ class Article &lt; ActiveRecord::Base @@ -519,7 +527,10 @@ class Article &lt; ActiveRecord::Base
519 end 527 end
520 528
521 alias :allow_delete? :allow_post_content? 529 alias :allow_delete? :allow_post_content?
522 - alias :allow_spread? :allow_post_content? 530 +
  531 + def allow_spread?(user = nil)
  532 + user && public?
  533 + end
523 534
524 def allow_create?(user) 535 def allow_create?(user)
525 allow_post_content?(user) || allow_publish_content?(user) 536 allow_post_content?(user) || allow_publish_content?(user)
app/models/block.rb
1 class Block < ActiveRecord::Base 1 class Block < ActiveRecord::Base
2 2
3 - attr_accessible :title, :display, :limit, :box_id, :posts_per_page, :visualization_format, :language, :display_user, :box 3 + attr_accessible :title, :display, :limit, :box_id, :posts_per_page, :visualization_format, :language, :display_user, :box, :fixed
4 4
5 # to be able to generate HTML 5 # to be able to generate HTML
6 include ActionView::Helpers::UrlHelper 6 include ActionView::Helpers::UrlHelper
@@ -64,7 +64,7 @@ class Block &lt; ActiveRecord::Base @@ -64,7 +64,7 @@ class Block &lt; ActiveRecord::Base
64 end 64 end
65 65
66 def display_to_user?(user) 66 def display_to_user?(user)
67 - display_user == 'all' || (user.nil? && display_user == 'not_logged') || (user && display_user == 'logged') 67 + display_user == 'all' || (user.nil? && display_user == 'not_logged') || (user && display_user == 'logged') || (user && display_user == 'followers' && user.follows?(owner))
68 end 68 end
69 69
70 def display_always(context) 70 def display_always(context)
@@ -75,7 +75,7 @@ class Block &lt; ActiveRecord::Base @@ -75,7 +75,7 @@ class Block &lt; ActiveRecord::Base
75 if context[:article] 75 if context[:article]
76 return context[:article] == owner.home_page 76 return context[:article] == owner.home_page
77 else 77 else
78 - return context[:request_path] == '/' 78 + return home_page_path?(context[:request_path])
79 end 79 end
80 end 80 end
81 81
@@ -83,7 +83,7 @@ class Block &lt; ActiveRecord::Base @@ -83,7 +83,7 @@ class Block &lt; ActiveRecord::Base
83 if context[:article] 83 if context[:article]
84 return context[:article] != owner.home_page 84 return context[:article] != owner.home_page
85 else 85 else
86 - return context[:request_path] != '/' + (owner.kind_of?(Profile) ? owner.identifier : '') 86 + return !home_page_path?(context[:request_path])
87 end 87 end
88 end 88 end
89 89
@@ -110,11 +110,14 @@ class Block &lt; ActiveRecord::Base @@ -110,11 +110,14 @@ class Block &lt; ActiveRecord::Base
110 # * <tt>'all'</tt>: the block is always displayed 110 # * <tt>'all'</tt>: the block is always displayed
111 settings_items :language, :type => :string, :default => 'all' 111 settings_items :language, :type => :string, :default => 'all'
112 112
  113 + # The block can be configured to be fixed. Only can be edited by environment admins
  114 + settings_items :fixed, :type => :boolean, :default => false
  115 +
113 # returns the description of the block, used when the user sees a list of 116 # returns the description of the block, used when the user sees a list of
114 # blocks to choose one to include in the design. 117 # blocks to choose one to include in the design.
115 # 118 #
116 # Must be redefined in subclasses to match the description of each block 119 # Must be redefined in subclasses to match the description of each block
117 - # type. 120 + # type.
118 def self.description 121 def self.description
119 '(dummy)' 122 '(dummy)'
120 end 123 end
@@ -124,13 +127,13 @@ class Block &lt; ActiveRecord::Base @@ -124,13 +127,13 @@ class Block &lt; ActiveRecord::Base
124 # This method can return several types of objects: 127 # This method can return several types of objects:
125 # 128 #
126 # * <tt>String</tt>: if the string starts with <tt>http://</tt> or <tt>https://</tt>, then it is assumed to be address of an IFRAME. Otherwise it's is used as regular HTML. 129 # * <tt>String</tt>: if the string starts with <tt>http://</tt> or <tt>https://</tt>, then it is assumed to be address of an IFRAME. Otherwise it's is used as regular HTML.
127 - # * <tt>Hash</tt>: the hash is used to build an URL that is used as the address for a IFRAME. 130 + # * <tt>Hash</tt>: the hash is used to build an URL that is used as the address for a IFRAME.
128 # * <tt>Proc</tt>: the Proc is evaluated in the scope of BoxesHelper. The 131 # * <tt>Proc</tt>: the Proc is evaluated in the scope of BoxesHelper. The
129 # block can then use <tt>render</tt>, <tt>link_to</tt>, etc. 132 # block can then use <tt>render</tt>, <tt>link_to</tt>, etc.
130 # 133 #
131 # The method can also return <tt>nil</tt>, which means "no content". 134 # The method can also return <tt>nil</tt>, which means "no content".
132 # 135 #
133 - # See BoxesHelper#extract_block_content for implementation details. 136 + # See BoxesHelper#extract_block_content for implementation details.
134 def content(args={}) 137 def content(args={})
135 "This is block number %d" % self.id 138 "This is block number %d" % self.id
136 end 139 end
@@ -221,6 +224,7 @@ class Block &lt; ActiveRecord::Base @@ -221,6 +224,7 @@ class Block &lt; ActiveRecord::Base
221 'all' => _('All users'), 224 'all' => _('All users'),
222 'logged' => _('Logged'), 225 'logged' => _('Logged'),
223 'not_logged' => _('Not logged'), 226 'not_logged' => _('Not logged'),
  227 + 'followers' => owner.class != Environment && owner.organization? ? _('Members') : _('Friends')
224 } 228 }
225 end 229 end
226 230
@@ -239,4 +243,21 @@ class Block &lt; ActiveRecord::Base @@ -239,4 +243,21 @@ class Block &lt; ActiveRecord::Base
239 self.position = block.position 243 self.position = block.position
240 end 244 end
241 245
  246 + private
  247 +
  248 + def home_page_path
  249 + home_page_url = Noosfero.root('/')
  250 +
  251 + if owner.kind_of?(Profile)
  252 + home_page_url += "profile/" if owner.home_page.nil?
  253 + home_page_url += owner.identifier
  254 + end
  255 +
  256 + return home_page_url
  257 + end
  258 +
  259 + def home_page_path? path
  260 + return path == home_page_path || path == (home_page_path + '/')
  261 + end
  262 +
242 end 263 end
app/models/blog.rb
@@ -53,7 +53,7 @@ class Blog &lt; Folder @@ -53,7 +53,7 @@ class Blog &lt; Folder
53 def prepare_external_feed 53 def prepare_external_feed
54 unless self.external_feed_data.nil? 54 unless self.external_feed_data.nil?
55 if self.external_feed(true) && self.external_feed.id == self.external_feed_data[:id].to_i 55 if self.external_feed(true) && self.external_feed.id == self.external_feed_data[:id].to_i
56 - self.external_feed.attributes = self.external_feed_data 56 + self.external_feed.attributes = self.external_feed_data.except(:id)
57 else 57 else
58 self.build_external_feed(self.external_feed_data, :without_protection => true) 58 self.build_external_feed(self.external_feed_data, :without_protection => true)
59 end 59 end
app/models/category.rb
@@ -3,10 +3,10 @@ class Category &lt; ActiveRecord::Base @@ -3,10 +3,10 @@ class Category &lt; ActiveRecord::Base
3 attr_accessible :name, :parent_id, :display_color, :display_in_menu, :image_builder, :environment, :parent 3 attr_accessible :name, :parent_id, :display_color, :display_in_menu, :image_builder, :environment, :parent
4 4
5 SEARCHABLE_FIELDS = { 5 SEARCHABLE_FIELDS = {
6 - :name => 10,  
7 - :acronym => 5,  
8 - :abbreviation => 5,  
9 - :slug => 1, 6 + :name => {:label => _('Name'), :weight => 10},
  7 + :acronym => {:label => _('Acronym'), :weight => 5},
  8 + :abbreviation => {:label => _('Abbreviation'), :weight => 5},
  9 + :slug => {:label => _('Slug'), :weight => 1},
10 } 10 }
11 11
12 validates_exclusion_of :slug, :in => [ 'index' ], :message => N_('{fn} cannot be like that.').fix_i18n 12 validates_exclusion_of :slug, :in => [ 'index' ], :message => N_('{fn} cannot be like that.').fix_i18n
app/models/certifier.rb
@@ -3,9 +3,9 @@ class Certifier &lt; ActiveRecord::Base @@ -3,9 +3,9 @@ class Certifier &lt; ActiveRecord::Base
3 attr_accessible :name, :environment 3 attr_accessible :name, :environment
4 4
5 SEARCHABLE_FIELDS = { 5 SEARCHABLE_FIELDS = {
6 - :name => 10,  
7 - :description => 3,  
8 - :link => 1, 6 + :name => {:label => _('Name'), :weight => 10},
  7 + :description => {:label => _('Description'), :weight => 3},
  8 + :link => {:label => _('Link'), :weight => 1},
9 } 9 }
10 10
11 belongs_to :environment 11 belongs_to :environment
app/models/comment.rb
1 class Comment < ActiveRecord::Base 1 class Comment < ActiveRecord::Base
2 2
3 SEARCHABLE_FIELDS = { 3 SEARCHABLE_FIELDS = {
4 - :title => 10,  
5 - :name => 4,  
6 - :body => 2, 4 + :title => {:label => _('Title'), :weight => 10},
  5 + :name => {:label => _('Name'), :weight => 4},
  6 + :body => {:label => _('Content'), :weight => 2},
7 } 7 }
8 8
9 attr_accessible :body, :author, :name, :email, :title, :reply_of_id, :source 9 attr_accessible :body, :author, :name, :email, :title, :reply_of_id, :source
app/models/communities_block.rb
@@ -14,19 +14,17 @@ class CommunitiesBlock &lt; ProfileListBlock @@ -14,19 +14,17 @@ class CommunitiesBlock &lt; ProfileListBlock
14 _('This block displays the communities in which the user is a member.') 14 _('This block displays the communities in which the user is a member.')
15 end 15 end
16 16
  17 + def suggestions
  18 + return nil unless owner.kind_of?(Profile)
  19 + owner.profile_suggestions.of_community.enabled.limit(3).includes(:suggestion)
  20 + end
  21 +
17 def footer 22 def footer
18 owner = self.owner 23 owner = self.owner
19 - case owner  
20 - when Profile  
21 - lambda do |context|  
22 - link_to s_('communities|View all'), :profile => owner.identifier, :controller => 'profile', :action => 'communities'  
23 - end  
24 - when Environment  
25 - lambda do |context|  
26 - link_to s_('communities|View all'), :controller => 'search', :action => 'communities'  
27 - end  
28 - else  
29 - '' 24 + suggestions = self.suggestions
  25 + return '' unless owner.kind_of?(Profile) || owner.kind_of?(Environment)
  26 + proc do
  27 + render :file => 'blocks/communities', :locals => { :owner => owner, :suggestions => suggestions }
30 end 28 end
31 end 29 end
32 30
app/models/community.rb
@@ -50,16 +50,6 @@ class Community &lt; Organization @@ -50,16 +50,6 @@ class Community &lt; Organization
50 super + FIELDS 50 super + FIELDS
51 end 51 end
52 52
53 - validate :presence_of_required_fieds  
54 -  
55 - def presence_of_required_fieds  
56 - self.required_fields.each do |field|  
57 - if self.send(field).blank?  
58 - self.errors.add_on_blank(field)  
59 - end  
60 - end  
61 - end  
62 -  
63 def active_fields 53 def active_fields
64 environment ? environment.active_community_fields : [] 54 environment ? environment.active_community_fields : []
65 end 55 end
@@ -78,7 +68,7 @@ class Community &lt; Organization @@ -78,7 +68,7 @@ class Community &lt; Organization
78 end 68 end
79 69
80 def default_template 70 def default_template
81 - environment.community_template 71 + environment.community_default_template
82 end 72 end
83 73
84 def news(limit = 30, highlight = false) 74 def news(limit = 30, highlight = false)
app/models/create_enterprise.rb
@@ -73,7 +73,13 @@ class CreateEnterprise &lt; Task @@ -73,7 +73,13 @@ class CreateEnterprise &lt; Task
73 73
74 # sets the associated region for the enterprise creation 74 # sets the associated region for the enterprise creation
75 def region=(value) 75 def region=(value)
76 - raise ArgumentError.new("Region expected, but got #{value.class}") unless value.kind_of?(Region) 76 + unless value.kind_of?(Region)
  77 + begin
  78 + value = Region.find(value)
  79 + rescue
  80 + raise ArgumentError.new("Could not find any region with the id #{value}")
  81 + end
  82 + end
77 83
78 @region = value 84 @region = value
79 self.region_id = value.id 85 self.region_id = value.id
app/models/domain.rb
@@ -92,4 +92,11 @@ class Domain &lt; ActiveRecord::Base @@ -92,4 +92,11 @@ class Domain &lt; ActiveRecord::Base
92 @hosting = {} 92 @hosting = {}
93 end 93 end
94 94
  95 + # Detects a domain's custom text domain chain if available based on a domain
  96 + # served on multitenancy configuration or a registered domain.
  97 + def self.custom_locale(domainname)
  98 + domain = Noosfero::MultiTenancy.mapping[domainname] || domainname[/(.*?)\./,1]
  99 + FastGettext.translation_repositories.keys.include?(domain) ? domain : FastGettext.default_text_domain
  100 + end
  101 +
95 end 102 end
app/models/enterprise.rb
@@ -4,7 +4,10 @@ class Enterprise &lt; Organization @@ -4,7 +4,10 @@ class Enterprise &lt; Organization
4 4
5 attr_accessible :business_name, :address_reference, :district, :tag_list, :organization_website, :historic_and_current_context, :activities_short_description, :products_per_catalog_page 5 attr_accessible :business_name, :address_reference, :district, :tag_list, :organization_website, :historic_and_current_context, :activities_short_description, :products_per_catalog_page
6 6
7 - SEARCH_DISPLAYS += %w[map full] 7 + SEARCH_FILTERS = {
  8 + :order => %w[more_recent more_popular more_active],
  9 + :display => %w[compact full map]
  10 + }
8 11
9 def self.type_name 12 def self.type_name
10 _('Enterprise') 13 _('Enterprise')
@@ -59,16 +62,6 @@ class Enterprise &lt; Organization @@ -59,16 +62,6 @@ class Enterprise &lt; Organization
59 super + FIELDS 62 super + FIELDS
60 end 63 end
61 64
62 - validate :presence_of_required_fieds  
63 -  
64 - def presence_of_required_fieds  
65 - self.required_fields.each do |field|  
66 - if self.send(field).blank?  
67 - self.errors.add_on_blank(field)  
68 - end  
69 - end  
70 - end  
71 -  
72 def active_fields 65 def active_fields
73 environment ? environment.active_enterprise_fields : [] 66 environment ? environment.active_enterprise_fields : []
74 end 67 end
@@ -107,7 +100,12 @@ class Enterprise &lt; Organization @@ -107,7 +100,12 @@ class Enterprise &lt; Organization
107 self.tasks.where(:type => 'EnterpriseActivation').first 100 self.tasks.where(:type => 'EnterpriseActivation').first
108 end 101 end
109 102
110 - def enable(owner) 103 + def enable(owner = nil)
  104 + if owner.nil?
  105 + self.visible = true
  106 + return self.save
  107 + end
  108 +
111 return if enabled 109 return if enabled
112 # must be set first for the following to work 110 # must be set first for the following to work
113 self.enabled = true 111 self.enabled = true
@@ -169,7 +167,7 @@ class Enterprise &lt; Organization @@ -169,7 +167,7 @@ class Enterprise &lt; Organization
169 end 167 end
170 168
171 def default_template 169 def default_template
172 - environment.enterprise_template 170 + environment.enterprise_default_template
173 end 171 end
174 172
175 def template_with_inactive_enterprise 173 def template_with_inactive_enterprise
app/models/environment.rb
@@ -10,6 +10,7 @@ class Environment &lt; ActiveRecord::Base @@ -10,6 +10,7 @@ class Environment &lt; ActiveRecord::Base
10 self.partial_updates = false 10 self.partial_updates = false
11 11
12 has_many :tasks, :dependent => :destroy, :as => 'target' 12 has_many :tasks, :dependent => :destroy, :as => 'target'
  13 + has_many :search_terms, :as => :context
13 14
14 IDENTIFY_SCRIPTS = /(php[0-9s]?|[sp]htm[l]?|pl|py|cgi|rb)/ 15 IDENTIFY_SCRIPTS = /(php[0-9s]?|[sp]htm[l]?|pl|py|cgi|rb)/
15 16
@@ -85,7 +86,9 @@ class Environment &lt; ActiveRecord::Base @@ -85,7 +86,9 @@ class Environment &lt; ActiveRecord::Base
85 end 86 end
86 87
87 def admins 88 def admins
88 - Person.members_of(self).all(:conditions => ['role_assignments.role_id = ?', Environment::Roles.admin(self).id]) 89 + admin_role = Environment::Roles.admin(self)
  90 + return [] if admin_role.blank?
  91 + Person.members_of(self).all(:conditions => ['role_assignments.role_id = ?', admin_role.id])
89 end 92 end
90 93
91 # returns the available features for a Environment, in the form of a 94 # returns the available features for a Environment, in the form of a
@@ -155,7 +158,8 @@ class Environment &lt; ActiveRecord::Base @@ -155,7 +158,8 @@ class Environment &lt; ActiveRecord::Base
155 'site_homepage' => _('Redirects the user to the environment homepage.'), 158 'site_homepage' => _('Redirects the user to the environment homepage.'),
156 'user_profile_page' => _('Redirects the user to his profile page.'), 159 'user_profile_page' => _('Redirects the user to his profile page.'),
157 'user_homepage' => _('Redirects the user to his homepage.'), 160 'user_homepage' => _('Redirects the user to his homepage.'),
158 - 'user_control_panel' => _('Redirects the user to his control panel.') 161 + 'user_control_panel' => _('Redirects the user to his control panel.'),
  162 + 'welcome_page' => _('Redirects the user to the environment welcome page.')
159 } 163 }
160 end 164 end
161 validates_inclusion_of :redirection_after_signup, :in => Environment.signup_redirection_options.keys, :allow_nil => true 165 validates_inclusion_of :redirection_after_signup, :in => Environment.signup_redirection_options.keys, :allow_nil => true
@@ -283,6 +287,7 @@ class Environment &lt; ActiveRecord::Base @@ -283,6 +287,7 @@ class Environment &lt; ActiveRecord::Base
283 www.flickr.com 287 www.flickr.com
284 www.gmodules.com 288 www.gmodules.com
285 www.youtube.com 289 www.youtube.com
  290 + openstreetmap.org
286 ] + ('a' .. 'z').map{|i| "#{i}.yimg.com"} 291 ] + ('a' .. 'z').map{|i| "#{i}.yimg.com"}
287 292
288 settings_items :enabled_plugins, :type => Array, :default => Noosfero::Plugin.available_plugin_names 293 settings_items :enabled_plugins, :type => Array, :default => Noosfero::Plugin.available_plugin_names
@@ -656,8 +661,8 @@ class Environment &lt; ActiveRecord::Base @@ -656,8 +661,8 @@ class Environment &lt; ActiveRecord::Base
656 { :controller => 'admin_panel', :action => 'index' } 661 { :controller => 'admin_panel', :action => 'index' }
657 end 662 end
658 663
659 - def top_url  
660 - url = 'http://' 664 + def top_url(scheme = 'http')
  665 + url = scheme + '://'
661 url << (Noosfero.url_options.key?(:host) ? Noosfero.url_options[:host] : default_hostname) 666 url << (Noosfero.url_options.key?(:host) ? Noosfero.url_options[:host] : default_hostname)
662 url << ':' << Noosfero.url_options[:port].to_s if Noosfero.url_options.key?(:port) 667 url << ':' << Noosfero.url_options[:port].to_s if Noosfero.url_options.key?(:port)
663 url << Noosfero.root('') 668 url << Noosfero.root('')
@@ -728,31 +733,50 @@ class Environment &lt; ActiveRecord::Base @@ -728,31 +733,50 @@ class Environment &lt; ActiveRecord::Base
728 ] 733 ]
729 end 734 end
730 735
731 - def community_template 736 + def is_default_template?(template)
  737 + is_default = template == community_default_template
  738 + is_default = is_default || template == person_default_template
  739 + is_default = is_default || template == enterprise_default_template
  740 + is_default
  741 + end
  742 +
  743 + def community_templates
  744 + self.communities.templates
  745 + end
  746 +
  747 + def community_default_template
732 template = Community.find_by_id settings[:community_template_id] 748 template = Community.find_by_id settings[:community_template_id]
733 - template if template && template.is_template 749 + template if template && template.is_template?
  750 + end
  751 +
  752 + def community_default_template=(value)
  753 + settings[:community_template_id] = value.kind_of?(Community) ? value.id : value
734 end 754 end
735 755
736 - def community_template=(value)  
737 - settings[:community_template_id] = value.id 756 + def person_templates
  757 + self.people.templates
738 end 758 end
739 759
740 - def person_template 760 + def person_default_template
741 template = Person.find_by_id settings[:person_template_id] 761 template = Person.find_by_id settings[:person_template_id]
742 - template if template && template.is_template 762 + template if template && template.is_template?
  763 + end
  764 +
  765 + def person_default_template=(value)
  766 + settings[:person_template_id] = value.kind_of?(Person) ? value.id : value
743 end 767 end
744 768
745 - def person_template=(value)  
746 - settings[:person_template_id] = value.id 769 + def enterprise_templates
  770 + self.enterprises.templates
747 end 771 end
748 772
749 - def enterprise_template 773 + def enterprise_default_template
750 template = Enterprise.find_by_id settings[:enterprise_template_id] 774 template = Enterprise.find_by_id settings[:enterprise_template_id]
751 - template if template && template.is_template 775 + template if template && template.is_template?
752 end 776 end
753 777
754 - def enterprise_template=(value)  
755 - settings[:enterprise_template_id] = value.id 778 + def enterprise_default_template=(value)
  779 + settings[:enterprise_template_id] = value.kind_of?(Enterprise) ? value.id : value
756 end 780 end
757 781
758 def inactive_enterprise_template 782 def inactive_enterprise_template
@@ -804,6 +828,10 @@ class Environment &lt; ActiveRecord::Base @@ -804,6 +828,10 @@ class Environment &lt; ActiveRecord::Base
804 "home-page-news/#{cache_key}-#{language}" 828 "home-page-news/#{cache_key}-#{language}"
805 end 829 end
806 830
  831 + def portal_enabled
  832 + portal_community && enabled?('use_portal_community')
  833 + end
  834 +
807 def notification_emails 835 def notification_emails
808 [contact_email].select(&:present?) + admins.map(&:email) 836 [contact_email].select(&:present?) + admins.map(&:email)
809 end 837 end
@@ -850,10 +878,10 @@ class Environment &lt; ActiveRecord::Base @@ -850,10 +878,10 @@ class Environment &lt; ActiveRecord::Base
850 person_template.visible = false 878 person_template.visible = false
851 person_template.save! 879 person_template.save!
852 880
853 - self.enterprise_template = enterprise_template 881 + self.enterprise_default_template = enterprise_template
854 self.inactive_enterprise_template = inactive_enterprise_template 882 self.inactive_enterprise_template = inactive_enterprise_template
855 - self.community_template = community_template  
856 - self.person_template = person_template 883 + self.community_default_template = community_template
  884 + self.person_default_template = person_template
857 self.save! 885 self.save!
858 end 886 end
859 887
@@ -917,6 +945,10 @@ class Environment &lt; ActiveRecord::Base @@ -917,6 +945,10 @@ class Environment &lt; ActiveRecord::Base
917 locales_list 945 locales_list
918 end 946 end
919 947
  948 + def has_license?
  949 + self.licenses.any?
  950 + end
  951 +
920 private 952 private
921 953
922 def default_language_available 954 def default_language_available
app/models/event.rb
@@ -19,7 +19,7 @@ class Event &lt; Article @@ -19,7 +19,7 @@ class Event &lt; Article
19 maybe_add_http(self.setting[:link]) 19 maybe_add_http(self.setting[:link])
20 end 20 end
21 21
22 - xss_terminate :only => [ :body, :link, :address ], :with => 'white_list', :on => 'validation' 22 + xss_terminate :only => [ :name, :body, :link, :address ], :with => 'white_list', :on => 'validation'
23 23
24 def initialize(*args) 24 def initialize(*args)
25 super(*args) 25 super(*args)
app/models/external_feed.rb
@@ -10,9 +10,10 @@ class ExternalFeed &lt; ActiveRecord::Base @@ -10,9 +10,10 @@ class ExternalFeed &lt; ActiveRecord::Base
10 { :conditions => ['(fetched_at is NULL) OR (fetched_at < ?)', Time.now - FeedUpdater.update_interval] } 10 { :conditions => ['(fetched_at is NULL) OR (fetched_at < ?)', Time.now - FeedUpdater.update_interval] }
11 } 11 }
12 12
13 - attr_accessible :address, :enabled 13 + attr_accessible :address, :enabled, :only_once
14 14
15 def add_item(title, link, date, content) 15 def add_item(title, link, date, content)
  16 + return if content.blank?
16 doc = Hpricot(content) 17 doc = Hpricot(content)
17 doc.search('*').each do |p| 18 doc.search('*').each do |p|
18 if p.instance_of? Hpricot::Elem 19 if p.instance_of? Hpricot::Elem
app/models/folder.rb
@@ -12,7 +12,7 @@ class Folder &lt; Article @@ -12,7 +12,7 @@ class Folder &lt; Article
12 12
13 acts_as_having_settings :field => :setting 13 acts_as_having_settings :field => :setting
14 14
15 - xss_terminate :only => [ :body ], :with => 'white_list', :on => 'validation' 15 + xss_terminate :only => [ :name, :body ], :with => 'white_list', :on => 'validation'
16 16
17 include WhiteListFilter 17 include WhiteListFilter
18 filter_iframes :body 18 filter_iframes :body
app/models/invitation.rb
@@ -51,7 +51,10 @@ class Invitation &lt; Task @@ -51,7 +51,10 @@ class Invitation &lt; Task
51 next if contact_to_invite == _("Firstname Lastname <friend@email.com>") 51 next if contact_to_invite == _("Firstname Lastname <friend@email.com>")
52 52
53 contact_to_invite.strip! 53 contact_to_invite.strip!
54 - if match = contact_to_invite.match(/(.*)<(.*)>/) and match[2].match(Noosfero::Constants::EMAIL_FORMAT) 54 + find_by_profile_id = false
  55 + if contact_to_invite.match(/^\d*$/)
  56 + find_by_profile_id = true
  57 + elsif match = contact_to_invite.match(/(.*)<(.*)>/) and match[2].match(Noosfero::Constants::EMAIL_FORMAT)
55 friend_name = match[1].strip 58 friend_name = match[1].strip
56 friend_email = match[2] 59 friend_email = match[2]
57 elsif match = contact_to_invite.strip.match(Noosfero::Constants::EMAIL_FORMAT) 60 elsif match = contact_to_invite.strip.match(Noosfero::Constants::EMAIL_FORMAT)
@@ -61,22 +64,24 @@ class Invitation &lt; Task @@ -61,22 +64,24 @@ class Invitation &lt; Task
61 next 64 next
62 end 65 end
63 66
64 - user = User.find_by_email(friend_email) 67 + begin
  68 + user = find_by_profile_id ? Person.find_by_id(contact_to_invite).user : User.find_by_email(friend_email)
  69 + rescue
  70 + user = nil
  71 + end
65 72
66 - task_args = if user.nil? 73 + task_args = if user.nil? && !find_by_profile_id
67 {:person => person, :friend_name => friend_name, :friend_email => friend_email, :message => message} 74 {:person => person, :friend_name => friend_name, :friend_email => friend_email, :message => message}
68 - elsif !user.person.is_a_friend?(person) 75 + elsif user.present? && !(user.person.is_a_friend?(person) && profile.person?)
69 {:person => person, :target => user.person} 76 {:person => person, :target => user.person}
70 end 77 end
71 78
72 - if !task_args.nil?  
73 - if profile.person?  
74 - InviteFriend.create(task_args)  
75 - elsif profile.community?  
76 - InviteMember.create(task_args.merge(:community_id => profile.id))  
77 - else  
78 - raise NotImplementedError, 'Don\'t know how to invite people to a %s' % profile.class.to_s  
79 - end 79 + if profile.person?
  80 + InviteFriend.create(task_args) if user.nil? || !user.person.is_a_friend?(person)
  81 + elsif profile.community?
  82 + InviteMember.create(task_args.merge(:community_id => profile.id)) if user.nil? || !user.person.is_member_of?(profile)
  83 + else
  84 + raise NotImplementedError, 'Don\'t know how to invite people to a %s' % profile.class.to_s
80 end 85 end
81 end 86 end
82 end 87 end
app/models/invite_friend.rb
1 class InviteFriend < Invitation 1 class InviteFriend < Invitation
2 2
3 settings_items :group_for_person, :group_for_friend 3 settings_items :group_for_person, :group_for_friend
  4 + before_create :check_for_invitation_existence
4 5
5 def perform 6 def perform
6 person.add_friend(friend, group_for_person) 7 person.add_friend(friend, group_for_person)
@@ -41,4 +42,11 @@ class InviteFriend &lt; Invitation @@ -41,4 +42,11 @@ class InviteFriend &lt; Invitation
41 ].join("\n\n") 42 ].join("\n\n")
42 end 43 end
43 44
  45 + private
  46 + def check_for_invitation_existence
  47 + if friend
  48 + friend.tasks.pending.of("InviteFriend").find(:all, :conditions => {:requestor_id => person.id, :target_id => friend.id}).blank?
  49 + end
  50 + end
  51 +
44 end 52 end
app/models/invite_member.rb
@@ -2,6 +2,7 @@ class InviteMember &lt; Invitation @@ -2,6 +2,7 @@ class InviteMember &lt; Invitation
2 2
3 settings_items :community_id, :type => :integer 3 settings_items :community_id, :type => :integer
4 validates_presence_of :community_id 4 validates_presence_of :community_id
  5 + before_create :check_for_invitation_existence
5 6
6 def community 7 def community
7 Community.find(community_id) 8 Community.find(community_id)
@@ -39,6 +40,14 @@ class InviteMember &lt; Invitation @@ -39,6 +40,14 @@ class InviteMember &lt; Invitation
39 _('%{requestor} invited you to join %{community}.') % {:requestor => requestor.name, :community => community.name} 40 _('%{requestor} invited you to join %{community}.') % {:requestor => requestor.name, :community => community.name}
40 end 41 end
41 42
  43 + def target_notification_message
  44 + if friend
  45 + _('%{requestor} is inviting you to join "%{community}" on %{system}.') % { :system => target.environment.name, :requestor => requestor.name, :community => community.name }
  46 + else
  47 + super
  48 + end
  49 + end
  50 +
42 def expanded_message 51 def expanded_message
43 super.gsub /<community>/, community.name 52 super.gsub /<community>/, community.name
44 end 53 end
@@ -53,4 +62,11 @@ class InviteMember &lt; Invitation @@ -53,4 +62,11 @@ class InviteMember &lt; Invitation
53 ].join("\n\n") 62 ].join("\n\n")
54 end 63 end
55 64
  65 + private
  66 + def check_for_invitation_existence
  67 + if friend
  68 + friend.tasks.pending.of("InviteMember").find(:all, :conditions => {:requestor_id => person.id}).select { |t| t.data[:community_id] == community_id }.blank?
  69 + end
  70 + end
  71 +
56 end 72 end
app/models/license.rb
@@ -3,8 +3,8 @@ class License &lt; ActiveRecord::Base @@ -3,8 +3,8 @@ class License &lt; ActiveRecord::Base
3 attr_accessible :name, :url 3 attr_accessible :name, :url
4 4
5 SEARCHABLE_FIELDS = { 5 SEARCHABLE_FIELDS = {
6 - :name => 10,  
7 - :url => 5, 6 + :name => {:label => _('Name'), :weight => 10},
  7 + :url => {:label => _('URL'), :weight => 5},
8 } 8 }
9 9
10 belongs_to :environment 10 belongs_to :environment
app/models/link_article.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +class LinkArticle < Article
  2 +
  3 + attr_accessible :reference_article
  4 +
  5 + def self.short_description
  6 + "Article link"
  7 + end
  8 +
  9 + delegate :name, :to => :reference_article
  10 + delegate :body, :to => :reference_article
  11 + delegate :abstract, :to => :reference_article
  12 + delegate :url, :to => :reference_article
  13 +
  14 +end
app/models/national_region.rb
1 class NationalRegion < ActiveRecord::Base 1 class NationalRegion < ActiveRecord::Base
2 2
3 SEARCHABLE_FIELDS = { 3 SEARCHABLE_FIELDS = {
4 - :name => 1,  
5 - :national_region_code => 1, 4 + :name => {:label => _('Name'), :weight => 1},
  5 + :national_region_code => {:label => _('Region Code'), :weight => 1},
6 } 6 }
7 7
8 def self.search_city(city_name, like = false, state = nil) 8 def self.search_city(city_name, like = false, state = nil)
app/models/organization.rb
@@ -3,10 +3,11 @@ class Organization &lt; Profile @@ -3,10 +3,11 @@ class Organization &lt; Profile
3 3
4 attr_accessible :moderated_articles, :foundation_year, :contact_person, :acronym, :legal_form, :economic_activity, :management_information, :cnpj, :display_name, :enable_contact_us 4 attr_accessible :moderated_articles, :foundation_year, :contact_person, :acronym, :legal_form, :economic_activity, :management_information, :cnpj, :display_name, :enable_contact_us
5 5
6 - SEARCH_FILTERS += %w[  
7 - more_popular  
8 - more_active  
9 - ] 6 + SEARCH_FILTERS = {
  7 + :order => %w[more_recent more_popular more_active],
  8 + :display => %w[compact]
  9 + }
  10 +
10 11
11 settings_items :closed, :type => :boolean, :default => false 12 settings_items :closed, :type => :boolean, :default => false
12 def closed? 13 def closed?
@@ -30,6 +31,16 @@ class Organization &lt; Profile @@ -30,6 +31,16 @@ class Organization &lt; Profile
30 31
31 scope :more_popular, :order => 'members_count DESC' 32 scope :more_popular, :order => 'members_count DESC'
32 33
  34 + validate :presence_of_required_fieds, :unless => :is_template
  35 +
  36 + def presence_of_required_fieds
  37 + self.required_fields.each do |field|
  38 + if self.send(field).blank?
  39 + self.errors.add_on_blank(field)
  40 + end
  41 + end
  42 + end
  43 +
33 def validation_methodology 44 def validation_methodology
34 self.validation_info ? self.validation_info.validation_methodology : nil 45 self.validation_info ? self.validation_info.validation_methodology : nil
35 end 46 end
@@ -166,4 +177,8 @@ class Organization &lt; Profile @@ -166,4 +177,8 @@ class Organization &lt; Profile
166 self.visible = false 177 self.visible = false
167 save! 178 save!
168 end 179 end
  180 +
  181 + def allow_invitation_from?(person)
  182 + (followed_by?(person) && self.allow_members_to_invite) || person.has_permission?('invite-members', self)
  183 + end
169 end 184 end
app/models/person.rb
@@ -3,10 +3,11 @@ class Person &lt; Profile @@ -3,10 +3,11 @@ class Person &lt; Profile
3 3
4 attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone, :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference, :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization_website 4 attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone, :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference, :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization_website
5 5
6 - SEARCH_FILTERS += %w[  
7 - more_popular  
8 - more_active  
9 - ] 6 + SEARCH_FILTERS = {
  7 + :order => %w[more_recent more_popular more_active],
  8 + :display => %w[compact]
  9 + }
  10 +
10 11
11 def self.type_name 12 def self.type_name
12 _('Person') 13 _('Person')
@@ -21,10 +22,34 @@ class Person &lt; Profile @@ -21,10 +22,34 @@ class Person &lt; Profile
21 { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] } 22 { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] }
22 } 23 }
23 24
24 - def has_permission_with_plugins?(permission, profile)  
25 - permissions = [has_permission_without_plugins?(permission, profile)] 25 + scope :not_members_of, lambda { |resources|
  26 + resources = [resources] if !resources.kind_of?(Array)
  27 + conditions = resources.map {|resource| "role_assignments.resource_type = '#{resource.class.base_class.name}' AND role_assignments.resource_id = #{resource.id || -1}"}.join(' OR ')
  28 + { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "role_assignments" ON "role_assignments"."accessor_id" = "profiles"."id" AND "role_assignments"."accessor_type" = (\'Profile\') WHERE "profiles"."type" IN (\'Person\') AND (%s))' % conditions] }
  29 + }
  30 +
  31 + scope :by_role, lambda { |roles|
  32 + roles = [roles] unless roles.kind_of?(Array)
  33 + { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.role_id IN (?)',
  34 +roles] }
  35 + }
  36 +
  37 + scope :not_friends_of, lambda { |resources|
  38 + resources = Array(resources)
  39 + { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "friendships" ON "friendships"."person_id" = "profiles"."id" WHERE "friendships"."friend_id" IN (%s))' % resources.map(&:id)] }
  40 + }
  41 +
  42 + def has_permission_with_admin?(permission, resource)
  43 + return true if resource.blank? || resource.admins.include?(self)
  44 + return true if resource.kind_of?(Profile) && resource.environment.admins.include?(self)
  45 + has_permission_without_admin?(permission, resource)
  46 + end
  47 + alias_method_chain :has_permission?, :admin
  48 +
  49 + def has_permission_with_plugins?(permission, resource)
  50 + permissions = [has_permission_without_plugins?(permission, resource)]
26 permissions += plugins.map do |plugin| 51 permissions += plugins.map do |plugin|
27 - plugin.has_permission?(self, permission, profile) 52 + plugin.has_permission?(self, permission, resource)
28 end 53 end
29 permissions.include?(true) 54 permissions.include?(true)
30 end 55 end
@@ -39,9 +64,9 @@ class Person &lt; Profile @@ -39,9 +64,9 @@ class Person &lt; Profile
39 ScopeTool.union *scopes 64 ScopeTool.union *scopes
40 end 65 end
41 66
42 - def memberships_by_role(role)  
43 - memberships.where('role_assignments.role_id = ?', role.id)  
44 - end 67 + def memberships_by_role(role)
  68 + memberships.where('role_assignments.role_id = ?', role.id)
  69 + end
45 70
46 has_many :friendships, :dependent => :destroy 71 has_many :friendships, :dependent => :destroy
47 has_many :friends, :class_name => 'Person', :through => :friendships 72 has_many :friends, :class_name => 'Person', :through => :friendships
@@ -59,6 +84,10 @@ class Person &lt; Profile @@ -59,6 +84,10 @@ class Person &lt; Profile
59 has_and_belongs_to_many :acepted_forums, :class_name => 'Forum', :join_table => 'terms_forum_people' 84 has_and_belongs_to_many :acepted_forums, :class_name => 'Forum', :join_table => 'terms_forum_people'
60 has_and_belongs_to_many :articles_with_access, :class_name => 'Article', :join_table => 'article_privacy_exceptions' 85 has_and_belongs_to_many :articles_with_access, :class_name => 'Article', :join_table => 'article_privacy_exceptions'
61 86
  87 + has_many :profile_suggestions, :foreign_key => :person_id, :order => 'score DESC', :dependent => :destroy
  88 + has_many :suggested_people, :through => :profile_suggestions, :source => :suggestion, :conditions => ['profile_suggestions.suggestion_type = ? AND profile_suggestions.enabled = ?', 'Person', true]
  89 + has_many :suggested_communities, :through => :profile_suggestions, :source => :suggestion, :conditions => ['profile_suggestions.suggestion_type = ? AND profile_suggestions.enabled = ?', 'Community', true]
  90 +
62 scope :more_popular, :order => 'friends_count DESC' 91 scope :more_popular, :order => 'friends_count DESC'
63 92
64 scope :abusers, :joins => :abuse_complaints, :conditions => ['tasks.status = 3'], :select => 'DISTINCT profiles.*' 93 scope :abusers, :joins => :abuse_complaints, :conditions => ['tasks.status = 3'], :select => 'DISTINCT profiles.*'
@@ -74,6 +103,10 @@ class Person &lt; Profile @@ -74,6 +103,10 @@ class Person &lt; Profile
74 103
75 belongs_to :user, :dependent => :delete 104 belongs_to :user, :dependent => :delete
76 105
  106 + def can_change_homepage?
  107 + !environment.enabled?('cant_change_homepage') || is_admin?
  108 + end
  109 +
77 def can_control_scrap?(scrap) 110 def can_control_scrap?(scrap)
78 begin 111 begin
79 !self.scraps(scrap).nil? 112 !self.scraps(scrap).nil?
@@ -108,12 +141,12 @@ class Person &lt; Profile @@ -108,12 +141,12 @@ class Person &lt; Profile
108 end 141 end
109 142
110 def add_friend(friend, group = nil) 143 def add_friend(friend, group = nil)
111 - unless self.is_a_friend?(friend) 144 + unless self.is_a_friend?(friend)
112 friendship = self.friendships.build 145 friendship = self.friendships.build
113 friendship.friend = friend 146 friendship.friend = friend
114 friendship.group = group 147 friendship.group = group
115 friendship.save 148 friendship.save
116 - end 149 + end
117 end 150 end
118 151
119 def already_request_friendship?(person) 152 def already_request_friendship?(person)
@@ -161,7 +194,7 @@ class Person &lt; Profile @@ -161,7 +194,7 @@ class Person &lt; Profile
161 FIELDS 194 FIELDS
162 end 195 end
163 196
164 - validate :presence_of_required_fields 197 + validate :presence_of_required_fields, :unless => :is_template
165 198
166 def presence_of_required_fields 199 def presence_of_required_fields
167 self.required_fields.each do |field| 200 self.required_fields.each do |field|
@@ -300,7 +333,7 @@ class Person &lt; Profile @@ -300,7 +333,7 @@ class Person &lt; Profile
300 end 333 end
301 334
302 def default_template 335 def default_template
303 - environment.person_template 336 + environment.person_default_template
304 end 337 end
305 338
306 def apply_type_specific_template(template) 339 def apply_type_specific_template(template)
@@ -487,6 +520,15 @@ class Person &lt; Profile @@ -487,6 +520,15 @@ class Person &lt; Profile
487 person.notifier.reschedule_next_notification_mail 520 person.notifier.reschedule_next_notification_mail
488 end 521 end
489 522
  523 + def remove_suggestion(profile)
  524 + suggestion = profile_suggestions.find_by_suggestion_id profile.id
  525 + suggestion.disable if suggestion
  526 + end
  527 +
  528 + def allow_invitation_from?(person)
  529 + person.has_permission?(:manage_friends, self)
  530 + end
  531 +
490 protected 532 protected
491 533
492 def followed_by?(profile) 534 def followed_by?(profile)
app/models/person_notifier.rb
@@ -67,7 +67,7 @@ class PersonNotifier @@ -67,7 +67,7 @@ class PersonNotifier
67 67
68 class Mailer < ActionMailer::Base 68 class Mailer < ActionMailer::Base
69 69
70 - add_template_helper(PersonNotifierHelper) 70 + add_template_helper(ApplicationHelper)
71 71
72 def session 72 def session
73 {:theme => nil} 73 {:theme => nil}
app/models/product.rb
1 class Product < ActiveRecord::Base 1 class Product < ActiveRecord::Base
2 2
3 SEARCHABLE_FIELDS = { 3 SEARCHABLE_FIELDS = {
4 - :name => 10,  
5 - :description => 1, 4 + :name => {:label => _('Name'), :weight => 10},
  5 + :description => {:label => _('Description'), :weight => 1},
6 } 6 }
7 7
8 - SEARCH_FILTERS = %w[  
9 - more_recent  
10 - ]  
11 -  
12 - SEARCH_DISPLAYS = %w[map full] 8 + SEARCH_FILTERS = {
  9 + :order => %w[more_recent],
  10 + :display => %w[full map]
  11 + }
13 12
14 - attr_accessible :name, :product_category, :highlighted, :price, :enterprise, :image_builder, :description, :available, :qualifiers, :unit_id, :discount, :inputs 13 + attr_accessible :name, :product_category, :highlighted, :price, :enterprise, :image_builder, :description, :available, :qualifiers, :unit_id, :discount, :inputs, :qualifiers_list
15 14
16 def self.default_search_display 15 def self.default_search_display
17 'full' 16 'full'
app/models/profile.rb
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 # which by default is the one returned by Environment:default. 3 # which by default is the one returned by Environment:default.
4 class Profile < ActiveRecord::Base 4 class Profile < ActiveRecord::Base
5 5
6 - attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time, :redirection_after_login 6 + attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time, :redirection_after_login, :email_suggestions, :allow_members_to_invite, :invite_friends_only
7 7
8 # use for internationalizable human type names in search facets 8 # use for internationalizable human type names in search facets
9 # reimplement on subclasses 9 # reimplement on subclasses
@@ -12,16 +12,15 @@ class Profile &lt; ActiveRecord::Base @@ -12,16 +12,15 @@ class Profile &lt; ActiveRecord::Base
12 end 12 end
13 13
14 SEARCHABLE_FIELDS = { 14 SEARCHABLE_FIELDS = {
15 - :name => 10,  
16 - :identifier => 5,  
17 - :nickname => 2, 15 + :name => {:label => _('Name'), :weight => 10},
  16 + :identifier => {:label => _('Username'), :weight => 5},
  17 + :nickname => {:label => _('Nickname'), :weight => 2},
18 } 18 }
19 19
20 - SEARCH_FILTERS = %w[  
21 - more_recent  
22 - ]  
23 -  
24 - SEARCH_DISPLAYS = %w[compact] 20 + SEARCH_FILTERS = {
  21 + :order => %w[more_recent],
  22 + :display => %w[compact]
  23 + }
25 24
26 def self.default_search_display 25 def self.default_search_display
27 'compact' 26 'compact'
@@ -97,7 +96,7 @@ class Profile &lt; ActiveRecord::Base @@ -97,7 +96,7 @@ class Profile &lt; ActiveRecord::Base
97 end 96 end
98 97
99 def members_by_name 98 def members_by_name
100 - members.order(:name) 99 + members.order('profiles.name')
101 end 100 end
102 101
103 class << self 102 class << self
@@ -108,8 +107,8 @@ class Profile &lt; ActiveRecord::Base @@ -108,8 +107,8 @@ class Profile &lt; ActiveRecord::Base
108 alias_method_chain :count, :distinct 107 alias_method_chain :count, :distinct
109 end 108 end
110 109
111 - def members_by_role(role)  
112 - Person.members_of(self).all(:conditions => ['role_assignments.role_id = ?', role.id]) 110 + def members_by_role(roles)
  111 + Person.members_of(self).by_role(roles)
113 end 112 end
114 113
115 acts_as_having_boxes 114 acts_as_having_boxes
@@ -121,7 +120,9 @@ class Profile &lt; ActiveRecord::Base @@ -121,7 +120,9 @@ class Profile &lt; ActiveRecord::Base
121 end 120 end
122 121
123 scope :visible, :conditions => { :visible => true } 122 scope :visible, :conditions => { :visible => true }
  123 + scope :disabled, :conditions => { :visible => false }
124 scope :public, :conditions => { :visible => true, :public_profile => true } 124 scope :public, :conditions => { :visible => true, :public_profile => true }
  125 + scope :enabled, :conditions => { :enabled => true }
125 126
126 # Subclasses must override this method 127 # Subclasses must override this method
127 scope :more_popular 128 scope :more_popular
@@ -138,6 +139,17 @@ class Profile &lt; ActiveRecord::Base @@ -138,6 +139,17 @@ class Profile &lt; ActiveRecord::Base
138 139
139 has_many :comments_received, :class_name => 'Comment', :through => :articles, :source => :comments 140 has_many :comments_received, :class_name => 'Comment', :through => :articles, :source => :comments
140 141
  142 + # Although this should be a has_one relation, there are no non-silly names for
  143 + # a foreign key on article to reference the template to which it is
  144 + # welcome_page... =P
  145 + belongs_to :welcome_page, :class_name => 'Article', :dependent => :destroy
  146 +
  147 + def welcome_page_content
  148 + welcome_page && welcome_page.published ? welcome_page.body : nil
  149 + end
  150 +
  151 + has_many :search_terms, :as => :context
  152 +
141 def scraps(scrap=nil) 153 def scraps(scrap=nil)
142 scrap = scrap.is_a?(Scrap) ? scrap.id : scrap 154 scrap = scrap.is_a?(Scrap) ? scrap.id : scrap
143 scrap.nil? ? Scrap.all_scraps(self) : Scrap.all_scraps(self).find(scrap) 155 scrap.nil? ? Scrap.all_scraps(self) : Scrap.all_scraps(self).find(scrap)
@@ -153,6 +165,7 @@ class Profile &lt; ActiveRecord::Base @@ -153,6 +165,7 @@ class Profile &lt; ActiveRecord::Base
153 settings_items :public_content, :type => :boolean, :default => true 165 settings_items :public_content, :type => :boolean, :default => true
154 settings_items :description 166 settings_items :description
155 settings_items :fields_privacy, :type => :hash, :default => {} 167 settings_items :fields_privacy, :type => :hash, :default => {}
  168 + settings_items :email_suggestions, :type => :boolean, :default => false
156 169
157 validates_length_of :description, :maximum => 550, :allow_nil => true 170 validates_length_of :description, :maximum => 550, :allow_nil => true
158 171
@@ -217,6 +230,8 @@ class Profile &lt; ActiveRecord::Base @@ -217,6 +230,8 @@ class Profile &lt; ActiveRecord::Base
217 230
218 has_many :abuse_complaints, :foreign_key => 'requestor_id', :dependent => :destroy 231 has_many :abuse_complaints, :foreign_key => 'requestor_id', :dependent => :destroy
219 232
  233 + has_many :profile_suggestions, :foreign_key => :suggestion_id, :dependent => :destroy
  234 +
220 def top_level_categorization 235 def top_level_categorization
221 ret = {} 236 ret = {}
222 self.profile_categorizations.each do |c| 237 self.profile_categorizations.each do |c|
@@ -391,7 +406,7 @@ class Profile &lt; ActiveRecord::Base @@ -391,7 +406,7 @@ class Profile &lt; ActiveRecord::Base
391 end 406 end
392 407
393 xss_terminate :only => [ :name, :nickname, :address, :contact_phone, :description ], :on => 'validation' 408 xss_terminate :only => [ :name, :nickname, :address, :contact_phone, :description ], :on => 'validation'
394 - xss_terminate :only => [ :custom_footer, :custom_header ], :with => 'white_list', :on => 'validation' 409 + xss_terminate :only => [ :custom_footer, :custom_header ], :with => 'white_list'
395 410
396 include WhiteListFilter 411 include WhiteListFilter
397 filter_iframes :custom_header, :custom_footer 412 filter_iframes :custom_header, :custom_footer
@@ -512,6 +527,14 @@ class Profile &lt; ActiveRecord::Base @@ -512,6 +527,14 @@ class Profile &lt; ActiveRecord::Base
512 generate_url(:profile => identifier, :controller => 'profile', :action => 'index') 527 generate_url(:profile => identifier, :controller => 'profile', :action => 'index')
513 end 528 end
514 529
  530 + def people_suggestions_url
  531 + generate_url(:profile => identifier, :controller => 'friends', :action => 'suggest')
  532 + end
  533 +
  534 + def communities_suggestions_url
  535 + generate_url(:profile => identifier, :controller => 'memberships', :action => 'suggest')
  536 + end
  537 +
515 def generate_url(options) 538 def generate_url(options)
516 url_options.merge(options) 539 url_options.merge(options)
517 end 540 end
@@ -601,7 +624,7 @@ private :generate_url, :url_options @@ -601,7 +624,7 @@ private :generate_url, :url_options
601 end 624 end
602 625
603 def copy_article_tree(article, parent=nil) 626 def copy_article_tree(article, parent=nil)
604 - return if article.is_a?(RssFeed) 627 + return if !copy_article?(article)
605 original_article = self.articles.find_by_name(article.name) 628 original_article = self.articles.find_by_name(article.name)
606 if original_article 629 if original_article
607 num = 2 630 num = 2
@@ -621,6 +644,11 @@ private :generate_url, :url_options @@ -621,6 +644,11 @@ private :generate_url, :url_options
621 end 644 end
622 end 645 end
623 646
  647 + def copy_article?(article)
  648 + !article.is_a?(RssFeed) &&
  649 + !(is_template && article.slug=='welcome-page')
  650 + end
  651 +
624 # Adds a person as member of this Profile. 652 # Adds a person as member of this Profile.
625 def add_member(person) 653 def add_member(person)
626 if self.has_members? 654 if self.has_members?
@@ -630,6 +658,8 @@ private :generate_url, :url_options @@ -630,6 +658,8 @@ private :generate_url, :url_options
630 self.affiliate(person, Profile::Roles.admin(environment.id)) if members.count == 0 658 self.affiliate(person, Profile::Roles.admin(environment.id)) if members.count == 0
631 self.affiliate(person, Profile::Roles.member(environment.id)) 659 self.affiliate(person, Profile::Roles.member(environment.id))
632 end 660 end
  661 + person.tasks.pending.of("InviteMember").select { |t| t.data[:community_id] == self.id }.each { |invite| invite.cancel }
  662 + remove_from_suggestion_list person
633 else 663 else
634 raise _("%s can't have members") % self.class.name 664 raise _("%s can't have members") % self.class.name
635 end 665 end
@@ -767,7 +797,10 @@ private :generate_url, :url_options @@ -767,7 +797,10 @@ private :generate_url, :url_options
767 end 797 end
768 798
769 def admins 799 def admins
770 - self.members_by_role(Profile::Roles.admin(environment.id)) 800 + return [] if environment.blank?
  801 + admin_role = Profile::Roles.admin(environment.id)
  802 + return [] if admin_role.blank?
  803 + self.members_by_role(admin_role)
771 end 804 end
772 805
773 def enable_contact? 806 def enable_contact?
@@ -775,7 +808,7 @@ private :generate_url, :url_options @@ -775,7 +808,7 @@ private :generate_url, :url_options
775 end 808 end
776 809
777 include Noosfero::Plugin::HotSpot 810 include Noosfero::Plugin::HotSpot
778 - 811 +
779 def folder_types 812 def folder_types
780 types = Article.folder_types 813 types = Article.folder_types
781 plugins.dispatch(:content_types).each {|type| 814 plugins.dispatch(:content_types).each {|type|
@@ -899,6 +932,13 @@ private :generate_url, :url_options @@ -899,6 +932,13 @@ private :generate_url, :url_options
899 end 932 end
900 933
901 def disable 934 def disable
  935 + self.visible = false
  936 + self.save
  937 + end
  938 +
  939 + def enable
  940 + self.visible = true
  941 + self.save
902 end 942 end
903 943
904 def control_panel_settings_button 944 def control_panel_settings_button
@@ -958,4 +998,14 @@ private :generate_url, :url_options @@ -958,4 +998,14 @@ private :generate_url, :url_options
958 def preferred_login_redirection 998 def preferred_login_redirection
959 redirection_after_login.blank? ? environment.redirection_after_login : redirection_after_login 999 redirection_after_login.blank? ? environment.redirection_after_login : redirection_after_login
960 end 1000 end
  1001 +
  1002 + def remove_from_suggestion_list(person)
  1003 + suggestion = person.profile_suggestions.find_by_suggestion_id self.id
  1004 + suggestion.disable if suggestion
  1005 + end
  1006 +
  1007 + def allow_invitation_from(person)
  1008 + false
  1009 + end
  1010 +
961 end 1011 end
app/models/profile_suggestion.rb 0 → 100644
@@ -0,0 +1,290 @@ @@ -0,0 +1,290 @@
  1 +class ProfileSuggestion < ActiveRecord::Base
  2 + belongs_to :person
  3 + belongs_to :suggestion, :class_name => 'Profile', :foreign_key => :suggestion_id
  4 +
  5 + attr_accessible :person, :suggestion, :suggestion_type, :categories, :enabled
  6 +
  7 + has_many :suggestion_connections, :foreign_key => 'suggestion_id'
  8 + has_many :profile_connections, :through => :suggestion_connections, :source => :connection, :source_type => 'Profile'
  9 + has_many :tag_connections, :through => :suggestion_connections, :source => :connection, :source_type => 'ActsAsTaggableOn::Tag'
  10 +
  11 + before_create do |profile_suggestion|
  12 + profile_suggestion.suggestion_type = self.suggestion.class.to_s
  13 + end
  14 +
  15 + after_destroy do |profile_suggestion|
  16 + self.class.generate_profile_suggestions(profile_suggestion.person)
  17 + end
  18 +
  19 + acts_as_having_settings :field => :categories
  20 +
  21 + validate :must_be_a_valid_category, :on => :create
  22 + def must_be_a_valid_category
  23 + if categories.keys.map { |cat| self.respond_to?(cat)}.include?(false)
  24 + errors.add(:categories, 'Category must be valid')
  25 + end
  26 + end
  27 +
  28 + validates_uniqueness_of :suggestion_id, :scope => [ :person_id ]
  29 + scope :of_person, :conditions => { :suggestion_type => 'Person' }
  30 + scope :of_community, :conditions => { :suggestion_type => 'Community' }
  31 + scope :enabled, :conditions => { :enabled => true }
  32 +
  33 + # {:category_type => ['category-icon', 'category-label']}
  34 + CATEGORIES = {
  35 + :people_with_common_friends => ['menu-people', _('Friends in common')],
  36 + :people_with_common_communities => ['menu-community',_('Communities in common')],
  37 + :people_with_common_tags => ['edit', _('Tags in common')],
  38 + :communities_with_common_friends => ['menu-people', _('Friends in common')],
  39 + :communities_with_common_tags => ['edit', _('Tags in common')]
  40 + }
  41 +
  42 + def category_icon(category)
  43 + 'icon-' + ProfileSuggestion::CATEGORIES[category][0]
  44 + end
  45 +
  46 + def category_label(category)
  47 + ProfileSuggestion::CATEGORIES[category][1]
  48 + end
  49 +
  50 + RULES = {
  51 + :people_with_common_communities => {
  52 + :threshold => 2, :weight => 1, :connection => 'Profile'
  53 + },
  54 + :people_with_common_friends => {
  55 + :threshold => 2, :weight => 1, :connection => 'Profile'
  56 + },
  57 + :people_with_common_tags => {
  58 + :threshold => 2, :weight => 1, :connection => 'ActsAsTaggableOn::Tag'
  59 + },
  60 + :communities_with_common_friends => {
  61 + :threshold => 2, :weight => 1, :connection => 'Profile'
  62 + },
  63 + :communities_with_common_tags => {
  64 + :threshold => 2, :weight => 1, :connection => 'ActsAsTaggableOn::Tag'
  65 + }
  66 + }
  67 +
  68 + RULES.keys.each do |rule|
  69 + settings_items rule
  70 + attr_accessible rule
  71 + end
  72 +
  73 + # Number of suggestions by rule
  74 + N_SUGGESTIONS = 30
  75 +
  76 + # Minimum number of suggestions
  77 + MIN_LIMIT = 10
  78 +
  79 + def self.profile_id(rule)
  80 + "#{rule}_profile_id"
  81 + end
  82 +
  83 + def self.connections(rule)
  84 + "#{rule}_connections"
  85 + end
  86 +
  87 + def self.counter(rule)
  88 + "#{rule}_count"
  89 + end
  90 +
  91 + # If you are about to rewrite the following sql queries, think twice. After
  92 + # that make sure that whatever you are writing to replace it should be faster
  93 + # than how it is now. Yes, sqls are ugly but are fast! And fast is what we
  94 + # need here.
  95 + #
  96 + # The logic behind this code is to produce a table somewhat like this:
  97 + # profile_id | rule1_count | rule1_connections | rule2_count | rule2_connections | ... | score |
  98 + # 12 | 2 | {32,54} | 3 | {8,22,27} | ... | 13 |
  99 + # 13 | 4 | {3,12,32,54} | 2 | {11,24} | ... | 15 |
  100 + # 14 | | | 2 | {44,56} | ... | 17 |
  101 + # ...
  102 + # ...
  103 + #
  104 + # This table has the suggested profile id and the count and connections of
  105 + # each rule that made this profile be suggested. Each suggestion has a score
  106 + # associated based on the rules' counts and rules' weights.
  107 + #
  108 + # From this table, we can sort suggestions by the score and save a small
  109 + # amount of them in the database. At this moment we also register the
  110 + # connections of each suggestion.
  111 +
  112 + def self.calculate_suggestions(person)
  113 + suggested_profiles = all_suggestions(person)
  114 + return if suggested_profiles.nil?
  115 +
  116 + already_suggested_profiles = person.profile_suggestions.map(&:suggestion_id).join(',')
  117 + suggested_profiles = suggested_profiles.where("profiles.id NOT IN (#{already_suggested_profiles})") if already_suggested_profiles.present?
  118 + #TODO suggested_profiles = suggested_profiles.order('score DESC')
  119 + suggested_profiles = suggested_profiles.limit(N_SUGGESTIONS)
  120 + return if suggested_profiles.blank?
  121 +
  122 + suggested_profiles.each do |suggested_profile|
  123 + suggestion = person.profile_suggestions.find_or_initialize_by_suggestion_id(suggested_profile.id)
  124 + RULES.each do |rule, options|
  125 + begin
  126 + value = suggested_profile.send("#{rule}_count").to_i
  127 + rescue NoMethodError
  128 + next
  129 + end
  130 + connections = suggested_profile.send("#{rule}_connections")
  131 + if connections.present?
  132 + connections = connections[1..-2].split(',')
  133 + else
  134 + connections = []
  135 + end
  136 + suggestion.send("#{rule}=", value)
  137 + connections.each do |connection_id|
  138 + next if SuggestionConnection.where(:suggestion_id => suggestion.id, :connection_id => connection_id, :connection_type => options[:connection]).present?
  139 + SuggestionConnection.create!(:suggestion => suggestion, :connection_id => connection_id, :connection_type => options[:connection])
  140 + end
  141 + suggestion.score += value * options[:weight]
  142 + end
  143 + suggestion.save!
  144 + end
  145 + end
  146 +
  147 + def self.people_with_common_friends(person)
  148 + person_friends = person.friends.map(&:id)
  149 + rule = "people_with_common_friends"
  150 + return if person_friends.blank?
  151 + "SELECT person_id as #{profile_id(rule)},
  152 + array_agg(friend_id) as #{connections(rule)},
  153 + count(person_id) as #{counter(rule)}
  154 + FROM friendships WHERE friend_id IN (#{person_friends.join(',')})
  155 + AND person_id NOT IN (#{(person_friends << person.id).join(',')})
  156 + GROUP BY person_id"
  157 + end
  158 +
  159 + def self.people_with_common_communities(person)
  160 + person_communities = person.communities.map(&:id)
  161 + rule = "people_with_common_communities"
  162 + return if person_communities.blank?
  163 + "SELECT common_members.accessor_id as #{profile_id(rule)},
  164 + array_agg(common_members.resource_id) as #{connections(rule)},
  165 + count(common_members.accessor_id) as #{counter(rule)}
  166 + FROM
  167 + (SELECT DISTINCT accessor_id, resource_id FROM
  168 + role_assignments WHERE role_assignments.resource_id IN (#{person_communities.join(',')}) AND
  169 + role_assignments.accessor_id != #{person.id} AND role_assignments.resource_type = 'Profile' AND
  170 + role_assignments.accessor_type = 'Profile') AS common_members
  171 + GROUP BY common_members.accessor_id"
  172 + end
  173 +
  174 + def self.people_with_common_tags(person)
  175 + profile_tags = person.articles.select('tags.id').joins(:tags).map(&:id)
  176 + rule = "people_with_common_tags"
  177 + return if profile_tags.blank?
  178 + "SELECT results.profiles_id as #{profile_id(rule)},
  179 + array_agg(results.tags_id) as #{connections(rule)},
  180 + count(results.profiles_id) as #{counter(rule)}
  181 + FROM (
  182 + SELECT DISTINCT tags.id as tags_id, profiles.id as profiles_id FROM profiles
  183 + INNER JOIN articles ON articles.profile_id = profiles.id
  184 + INNER JOIN taggings ON taggings.taggable_id = articles.id AND taggings.context = ('tags') AND taggings.taggable_type = 'Article'
  185 + INNER JOIN tags ON tags.id = taggings.tag_id
  186 + WHERE (tags.id in (#{profile_tags.join(',')}) AND profiles.id != #{person.id})) AS results
  187 + GROUP BY results.profiles_id"
  188 + end
  189 +
  190 + def self.communities_with_common_friends(person)
  191 + person_friends = person.friends.map(&:id)
  192 + rule = "communities_with_common_friends"
  193 + return if person_friends.blank?
  194 + "SELECT common_communities.resource_id as #{profile_id(rule)},
  195 + array_agg(common_communities.accessor_id) as #{connections(rule)},
  196 + count(common_communities.resource_id) as #{counter(rule)}
  197 + FROM
  198 + (SELECT DISTINCT accessor_id, resource_id FROM
  199 + role_assignments WHERE role_assignments.accessor_id IN (#{person_friends.join(',')}) AND
  200 + role_assignments.accessor_id != #{person.id} AND role_assignments.resource_type = 'Profile' AND
  201 + role_assignments.accessor_type = 'Profile') AS common_communities
  202 + GROUP BY common_communities.resource_id"
  203 + end
  204 +
  205 + def self.communities_with_common_tags(person)
  206 + profile_tags = person.articles.select('tags.id').joins(:tags).map(&:id)
  207 + rule = "communities_with_common_tags"
  208 + return if profile_tags.blank?
  209 + "SELECT results.profiles_id as #{profile_id(rule)},
  210 + array_agg(results.tags_id) as #{connections(rule)},
  211 + count(results.profiles_id) as #{counter(rule)}
  212 + FROM
  213 + (SELECT DISTINCT tags.id as tags_id, profiles.id AS profiles_id FROM profiles
  214 + INNER JOIN articles ON articles.profile_id = profiles.id
  215 + INNER JOIN taggings ON taggings.taggable_id = articles.id AND taggings.context = ('tags') AND taggings.taggable_type = 'Article'
  216 + INNER JOIN tags ON tags.id = taggings.tag_id
  217 + WHERE (tags.id IN (#{profile_tags.join(',')}) AND profiles.id != #{person.id})) AS results
  218 + GROUP BY results.profiles_id"
  219 + end
  220 +
  221 + def self.all_suggestions(person)
  222 + select_string = ["profiles.*"]
  223 + suggestions_join = []
  224 + where_string = []
  225 + valid_rules = []
  226 + previous_rule = nil
  227 + join_column = nil
  228 + RULES.each do |rule, options|
  229 + rule_select = self.send(rule, person)
  230 + next if !rule_select.present?
  231 +
  232 + valid_rules << rule
  233 + select_string << "suggestions.#{counter(rule)} as #{counter(rule)}, suggestions.#{connections(rule)} as #{connections(rule)}"
  234 + where_string << "#{counter(rule)} >= #{options[:threshold]}"
  235 + rule_select = "
  236 + (SELECT profiles.id as #{profile_id(rule)},
  237 + #{rule}_sub.#{counter(rule)} as #{counter(rule)},
  238 + #{rule}_sub.#{connections(rule)} as #{connections(rule)}
  239 + FROM profiles
  240 + LEFT OUTER JOIN (#{rule_select}) as #{rule}_sub
  241 + ON profiles.id = #{rule}_sub.#{profile_id(rule)}) AS #{rule}"
  242 +
  243 + if previous_rule.nil?
  244 + result = rule_select
  245 + else
  246 + result = "INNER JOIN #{rule_select}
  247 + ON #{previous_rule}.#{profile_id(previous_rule)} = #{rule}.#{profile_id(rule)}"
  248 + end
  249 + previous_rule = rule
  250 + suggestions_join << result
  251 + end
  252 +
  253 + return if valid_rules.blank?
  254 +
  255 + select_string = select_string.compact.join(',')
  256 + join_string = "INNER JOIN (SELECT * FROM #{suggestions_join.compact.join(' ')}) AS suggestions ON profiles.id = suggestions.#{profile_id(valid_rules.first)}"
  257 + where_string = where_string.compact.join(' OR ')
  258 +
  259 + person.environment.profiles.
  260 + select(select_string).
  261 + joins(join_string).
  262 + where(where_string)
  263 + end
  264 +
  265 + def disable
  266 + self.enabled = false
  267 + self.save!
  268 + self.class.generate_profile_suggestions(self.person)
  269 + end
  270 +
  271 + def self.generate_all_profile_suggestions
  272 + Delayed::Job.enqueue(ProfileSuggestion::GenerateAllJob.new) unless ProfileSuggestion::GenerateAllJob.exists?
  273 + end
  274 +
  275 + def self.generate_profile_suggestions(person, force = false)
  276 + return if person.profile_suggestions.enabled.count >= MIN_LIMIT && !force
  277 + Delayed::Job.enqueue ProfileSuggestionsJob.new(person.id) unless ProfileSuggestionsJob.exists?(person.id)
  278 + end
  279 +
  280 + class GenerateAllJob
  281 + def self.exists?
  282 + Delayed::Job.by_handler("--- !ruby/object:ProfileSuggestion::GenerateAllJob {}\n").count > 0
  283 + end
  284 +
  285 + def perform
  286 + Person.find_each {|person| ProfileSuggestion.generate_profile_suggestions(person) }
  287 + end
  288 + end
  289 +
  290 +end
app/models/qualifier.rb
@@ -3,7 +3,7 @@ class Qualifier &lt; ActiveRecord::Base @@ -3,7 +3,7 @@ class Qualifier &lt; ActiveRecord::Base
3 attr_accessible :name, :environment 3 attr_accessible :name, :environment
4 4
5 SEARCHABLE_FIELDS = { 5 SEARCHABLE_FIELDS = {
6 - :name => 1, 6 + :name => {:label => _('Name'), :weight => 1},
7 } 7 }
8 8
9 belongs_to :environment 9 belongs_to :environment
app/models/scrap.rb
@@ -3,7 +3,7 @@ class Scrap &lt; ActiveRecord::Base @@ -3,7 +3,7 @@ class Scrap &lt; ActiveRecord::Base
3 attr_accessible :content, :sender_id, :receiver_id, :scrap_id 3 attr_accessible :content, :sender_id, :receiver_id, :scrap_id
4 4
5 SEARCHABLE_FIELDS = { 5 SEARCHABLE_FIELDS = {
6 - :content => 1, 6 + :content => {:label => _('Content'), :weight => 1},
7 } 7 }
8 validates_presence_of :content 8 validates_presence_of :content
9 validates_presence_of :sender_id, :receiver_id 9 validates_presence_of :sender_id, :receiver_id
app/models/search_term.rb 0 → 100644
@@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
  1 +class SearchTerm < ActiveRecord::Base
  2 + validates_presence_of :term, :context
  3 + validates_uniqueness_of :term, :scope => [:context_id, :context_type, :asset]
  4 +
  5 + belongs_to :context, :polymorphic => true
  6 + has_many :occurrences, :class_name => 'SearchTermOccurrence'
  7 +
  8 + attr_accessible :term, :context, :asset
  9 +
  10 + def self.calculate_scores
  11 + os = occurrences_scores
  12 + find_each { |search_term| search_term.calculate_score(os) }
  13 + end
  14 +
  15 + def self.find_or_create(term, context, asset='all')
  16 + context.search_terms.where(:term => term, :asset => asset).first || context.search_terms.create!(:term => term, :asset=> asset)
  17 + end
  18 +
  19 + # Fast way of getting the occurrences score for each search_term. Ugly but fast!
  20 + #
  21 + # Each occurrence of a search_term has a score that is smaller the older the
  22 + # occurrence happened. We subtract the amount of time between now and the
  23 + # moment it happened from the total time any occurrence is valid to happen. E.g.:
  24 + # The expiration time is 100 days and an occurrence happened 3 days ago.
  25 + # Therefore the score is 97. Them we sum every score to get the total score
  26 + # for a search term.
  27 + def self.occurrences_scores
  28 + ActiveSupport::OrderedHash[*ActiveRecord::Base.connection.execute(
  29 + joins(:occurrences).
  30 + select("search_terms.id, sum(#{SearchTermOccurrence::EXPIRATION_TIME.to_i} - extract(epoch from (now() - search_term_occurrences.created_at))) as value").
  31 + where("search_term_occurrences.created_at > ?", DateTime.now - SearchTermOccurrence::EXPIRATION_TIME).
  32 + group("search_terms.id").
  33 + order('value DESC').
  34 + to_sql
  35 + ).map {|result| [result['id'].to_i, result['value'].to_i]}.flatten]
  36 + end
  37 +
  38 + def calculate_occurrence(occurrences_scores)
  39 + max_score = occurrences_scores.first[1]
  40 + (occurrences_scores[id]/max_score.to_f)*100
  41 + end
  42 +
  43 + def calculate_relevance(valid_occurrences)
  44 + indexed = valid_occurrences.last.indexed.to_f
  45 + return 0 if indexed == 0
  46 + total = valid_occurrences.last.total.to_f
  47 + (1 - indexed/total)*100
  48 + end
  49 +
  50 + def calculate_score(occurrences_scores)
  51 + valid_occurrences = occurrences.valid
  52 + if valid_occurrences.present?
  53 + # These scores vary from 1~100
  54 + self.occurrence_score = calculate_occurrence(occurrences_scores)
  55 + self.relevance_score = calculate_relevance(valid_occurrences)
  56 + else
  57 + self.occurrence_score = 0
  58 + self.relevance_score = 0
  59 + end
  60 + self.score = (occurrence_score * relevance_score)/100.0
  61 + self.save!
  62 + end
  63 +end
app/models/search_term_occurrence.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class SearchTermOccurrence < ActiveRecord::Base
  2 + belongs_to :search_term
  3 + validates_presence_of :search_term
  4 + attr_accessible :search_term, :created_at, :total, :indexed
  5 +
  6 + EXPIRATION_TIME = 1.year
  7 +
  8 + scope :valid, :conditions => ["search_term_occurrences.created_at > ?", DateTime.now - EXPIRATION_TIME]
  9 +end
app/models/suggestion_connection.rb 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +class SuggestionConnection < ActiveRecord::Base
  2 + attr_accessible :suggestion, :connection_type, :connection_id
  3 +
  4 + belongs_to :suggestion, :class_name => 'ProfileSuggestion', :foreign_key => 'suggestion_id'
  5 + belongs_to :connection, :polymorphic => true
  6 +end
app/models/task.rb
@@ -285,8 +285,9 @@ class Task &lt; ActiveRecord::Base @@ -285,8 +285,9 @@ class Task &lt; ActiveRecord::Base
285 # If 285 # If
286 def send_notification(action) 286 def send_notification(action)
287 if sends_email? 287 if sends_email?
288 - if self.requestor  
289 - TaskMailer.generic_message("task_#{action}", self) 288 + if self.requestor && !self.requestor.notification_emails.empty?
  289 + message = TaskMailer.generic_message("task_#{action}", self)
  290 + message.deliver if message
290 end 291 end
291 end 292 end
292 end 293 end
app/models/user.rb
@@ -11,6 +11,10 @@ class User &lt; ActiveRecord::Base @@ -11,6 +11,10 @@ class User &lt; ActiveRecord::Base
11 N_('Password confirmation') 11 N_('Password confirmation')
12 N_('Terms accepted') 12 N_('Terms accepted')
13 13
  14 + SEARCHABLE_FIELDS = {
  15 + :email => {:label => _('Email'), :weight => 5},
  16 + }
  17 +
14 def self.[](login) 18 def self.[](login)
15 self.find_by_login(login) 19 self.find_by_login(login)
16 end 20 end
@@ -201,6 +205,10 @@ class User &lt; ActiveRecord::Base @@ -201,6 +205,10 @@ class User &lt; ActiveRecord::Base
201 Digest::MD5.hexdigest(password) 205 Digest::MD5.hexdigest(password)
202 end 206 end
203 207
  208 + add_encryption_method :salted_md5 do |password, salt|
  209 + Digest::MD5.hexdigest(password+salt)
  210 + end
  211 +
204 add_encryption_method :clear do |password, salt| 212 add_encryption_method :clear do |password, salt|
205 password 213 password
206 end 214 end
@@ -350,6 +358,7 @@ class User &lt; ActiveRecord::Base @@ -350,6 +358,7 @@ class User &lt; ActiveRecord::Base
350 end 358 end
351 359
352 def delay_activation_check 360 def delay_activation_check
  361 + return if person.is_template?
353 Delayed::Job.enqueue(UserActivationJob.new(self.id), {:priority => 0, :run_at => 72.hours.from_now}) 362 Delayed::Job.enqueue(UserActivationJob.new(self.id), {:priority => 0, :run_at => 72.hours.from_now})
354 end 363 end
355 end 364 end
app/sweepers/role_assignment_sweeper.rb
@@ -13,7 +13,7 @@ class RoleAssignmentSweeper &lt; ActiveRecord::Observer @@ -13,7 +13,7 @@ class RoleAssignmentSweeper &lt; ActiveRecord::Observer
13 protected 13 protected
14 14
15 def expire_caches(role_assignment) 15 def expire_caches(role_assignment)
16 - expire_cache(role_assignment.accessor) 16 + expire_cache(role_assignment.accessor) if role_assignment.accessor.kind_of?(Profile)
17 expire_cache(role_assignment.resource) if role_assignment.resource.kind_of?(Profile) 17 expire_cache(role_assignment.resource) if role_assignment.resource.kind_of?(Profile)
18 end 18 end
19 19
app/views/account/signup.html.erb
1 -<% if @register_pending %>  
2 - <div id='thanks-for-signing'>  
3 - <% if environment.has_custom_welcome_screen? %>  
4 - <%= environment.settings[:signup_welcome_screen_body].html_safe %>  
5 - <% elsif environment.enabled?('admin_must_approve_new_users')%>  
6 - <h1><%= _("Welcome to %s!") % environment.name %></h1>  
7 - <h3><%= _("Thanks for signing up, we're thrilled to have you on our social network!") %></h3>  
8 - <p><%= _("Firstly, some tips for getting started:") %></p>  
9 - <% unless environment.enabled?('skip_new_user_email_confirmation') %>  
10 - <h4><%= _("Confirm your account and wait for admin approvement!") %></h4>  
11 - <p><%= _("You should receive a welcome email from us shortly. Please take a second to follow the link within to confirm your account.") %></p>  
12 - <p><%= _("You won't appear as %s until your account is confirmed and approved.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>  
13 - <% else %>  
14 - <h4><%= _("Wait for admin approvement!") %></h4>  
15 - <p><%= _("The administrators will evaluate your signup request for approvement.") %></p>  
16 - <p><%= _("You won't appear as %s until your account is approved.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>  
17 - <% end %>  
18 - <h4><%= _("What to do next?") %></h4>  
19 - <p><%= _("%s. Upload an avatar and let your friends find you easily :)") % link_to(_('Customize your profile'), {:controller => 'doc', :section => 'user', :topic => 'editing-person-info'}, :target => '_blank') %></p>  
20 - <p><%= _("Learn the guidelines. Read the %s for more details on how to use this social network!") % link_to(_('Documentation'), {:controller => 'doc'}, :target => '_blank') %></p>  
21 - <p><%= _("%s your Gmail, Yahoo and Hotmail contacts!") % link_to(_('Invite and find'), {:controller => 'doc', :section => 'user', :topic => 'invite-contacts'}, :target => '_blank') %></p>  
22 - <p><%= _("Start exploring and have fun!") %></p>  
23 - <% else %>  
24 - <h1><%= _("Welcome to %s!") % environment.name %></h1>  
25 - <h3><%= _("Thanks for signing up, we're thrilled to have you on our social network!") %></h3>  
26 - <p><%= _("Firstly, some tips for getting started:") %></p>  
27 - <h4><%= _("Confirm your account!") %></h4>  
28 - <p><%= _("You should receive a welcome email from us shortly. Please take a second to follow the link within to confirm your account.") %></p>  
29 - <p><%= _("You won't appear as %s until your account is confirmed.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>  
30 - <h4><%= _("What to do next?") %></h4>  
31 - <p><%= _("%s. Upload an avatar and let your friends find you easily :)") % link_to(_('Customize your profile'), {:controller => 'doc', :section => 'user', :topic => 'editing-person-info'}, :target => '_blank') %></p>  
32 - <p><%= _("Learn the guidelines. Read the %s for more details on how to use this social network!") % link_to(_('Documentation'), {:controller => 'doc'}, :target => '_blank') %></p>  
33 - <p><%= _("%s your Gmail, Yahoo and Hotmail contacts!") % link_to(_('Invite and find'), {:controller => 'doc', :section => 'user', :topic => 'invite-contacts'}, :target => '_blank') %></p>  
34 - <p><%= _("Start exploring and have fun!") %></p>  
35 - <% end %>  
36 - </div>  
37 -<% else %>  
38 - <h1><%= _('Sign up for %s!') % environment.name %></h1>  
39 - <%= render :partial => 'signup_form' %>  
40 -<% end %> 1 +<h1><%= _('Sign up for %s!') % environment.name %></h1>
  2 +<%= render :partial => 'signup_form' %>
app/views/admin_panel/_signup_welcome_screen.html.erb
1 <div class='description'> 1 <div class='description'>
2 - <%= _('This text will be showed as a welcome message to users after signup') %><br/><br/> 2 + <%= _('If you enable this feature on the "Features" section of the Administration Panel, this text will be shown as a welcome message to users after signup.') %>
3 </div> 3 </div>
4 -  
5 <%= labelled_form_field(_('Body'), text_area(:environment, :signup_welcome_screen_body, :cols => 40, :style => 'width: 100%', :class => 'mceEditor')) %> 4 <%= labelled_form_field(_('Body'), text_area(:environment, :signup_welcome_screen_body, :cols => 40, :style => 'width: 100%', :class => 'mceEditor')) %>
  5 +
  6 +<div class='description'>
  7 + <%= _('If this content is left blank, the following page will be displayed to the user:') %>
  8 +</div>
  9 +
  10 +<%= render :file => 'home/welcome', :locals => {:default_message => true} %>
app/views/admin_panel/index.html.erb
@@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
20 <tr><td><%= link_to _('Users'), :controller => 'users' %></td></tr> 20 <tr><td><%= link_to _('Users'), :controller => 'users' %></td></tr>
21 <tr><td><%= link_to _('Profile templates'), :controller => 'templates' %></td></tr> 21 <tr><td><%= link_to _('Profile templates'), :controller => 'templates' %></td></tr>
22 <tr><td><%= link_to _('Fields'), :controller => 'features', :action => 'manage_fields' %></td></tr> 22 <tr><td><%= link_to _('Fields'), :controller => 'features', :action => 'manage_fields' %></td></tr>
  23 + <tr><td><%= link_to _('Manage organizations status'), :action => 'manage_organizations_status' %></td></tr>
23 </table> 24 </table>
24 25
25 26
app/views/admin_panel/manage_organizations_status.html.erb 0 → 100644
@@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
  1 +<h1><%= _('Manage organizations') %></h1>
  2 +
  3 +<%= form_tag( { :action => 'manage_organizations_status' }, :method => 'get', :class => 'users-search' ) do %>
  4 +
  5 + <div class="search-field">
  6 + <span class="formfield">
  7 + <%= text_field_tag 'q', @q, :title => _("Find profiles"), :style=>"width:85%" %>
  8 + </span>
  9 +
  10 + <%= submit_button(:search, _('Search')) %>
  11 + </div>
  12 +
  13 + <div class="environment-users-results-header">
  14 + <div id='environment-users-filter-title'><%= @title %></div>
  15 +
  16 + <div id="environment-users-filter-filter">
  17 + <strong><%= _("Filter by: ") %></strong>
  18 +
  19 + <select id="profile_filter_select">
  20 + <%= options_for_select([['Any', 'any'],["Disabled profiles", "disabled"], ["Enabled profiles", "enabled"]], @filter) %>
  21 + </select>
  22 + </div>
  23 + <div style="clear: both"></div>
  24 + </div>
  25 +
  26 + <table>
  27 + <colgroup>
  28 + <col width="80%">
  29 + <col width="20%">
  30 + </colgroup>
  31 +
  32 + <tr>
  33 + <th><%= _('Member') %></th>
  34 + <th><%= _('Actions') %></th>
  35 + </tr>
  36 +
  37 + <% @collection.each do |p| %>
  38 + <tr title="<%= p.name %>">
  39 + <td><%= link_to_profile p.short_name, p.identifier, :title => p.name %> </td>
  40 +
  41 + <td class='actions'>
  42 + <div class="members-buttons-cell">
  43 + <% if p.visible %>
  44 + <%= button_without_text :'deactivate-user', _('Deactivate'), {:controller => "profile_editor", :action => 'deactivate_profile', :profile => p.identifier, :id => p.id}, :confirm => _("Do you want to deactivate this profile ?") %>
  45 + <% else %>
  46 + <%= button_without_text :'activate-user', _('Activate'), {:controller => "profile_editor", :action => 'activate_profile', :profile => p.identifier, :id => p.id}, :confirm => _("Do you want to activate this profile ?") %>
  47 + <% end %>
  48 + <%= button_without_text :'delete', _('Remove'), {:controller => "profile_editor", :action => 'destroy_profile', :profile => p.identifier, :id => p.id, :return_to => "/admin/admin_panel/manage_organizations_status"}, :method => :post, :confirm => _("Do you want to deactivate this profile ?") %>
  49 + </div>
  50 + </td>
  51 + </tr>
  52 + <% end %>
  53 + </table>
  54 +
  55 +<% end %>
  56 +
  57 +<%= pagination_links @collection, {:param_name => 'npage', :page_links => true} %>
  58 +
  59 +<% button_bar do %>
  60 + <%= button :back, _('Back'), :controller => 'admin_panel' %>
  61 +<% end %>
  62 +
  63 +<script type="text/javascript">
  64 + jQuery(document).ready(function(){
  65 + jQuery("#profile_filter_select").change(function(){
  66 + document.location.href = '/admin/admin_panel/manage_organizations_status?filter='+this.value;
  67 + });
  68 + });
  69 +</script>
0 \ No newline at end of file 70 \ No newline at end of file
app/views/admin_panel/site_info.html.erb
@@ -10,9 +10,12 @@ @@ -10,9 +10,12 @@
10 :content => (render :partial => 'site_info', :locals => {:f => f})} %> 10 :content => (render :partial => 'site_info', :locals => {:f => f})} %>
11 <% tabs << {:title => _('Terms of use'), :id => 'terms-of-use', 11 <% tabs << {:title => _('Terms of use'), :id => 'terms-of-use',
12 :content => (render :partial => 'terms_of_use', :locals => {:f => f})} %> 12 :content => (render :partial => 'terms_of_use', :locals => {:f => f})} %>
13 - <% tabs << {:title => _('Signup welcome text'), :id => 'signup-welcome-text', 13 + <% #TODO I renamed the labels of signup-welcome-text and signup-welcome-page
  14 + # so texts more meaningful but I'm not rewriting every variable or reference to
  15 + # this. I leave this task to whoever thinks this is too annoying.%>
  16 + <% tabs << {:title => _('Signup welcome email'), :id => 'signup-welcome-text',
14 :content => (render :partial => 'signup_welcome_text', :locals => {:f => f})} %> 17 :content => (render :partial => 'signup_welcome_text', :locals => {:f => f})} %>
15 - <% tabs << {:title => _('Signup welcome message'), :id => 'signup-welcome-message', 18 + <% tabs << {:title => _('Signup welcome page'), :id => 'signup-welcome-message',
16 :content => (render :partial => 'signup_welcome_screen', :locals => {:f => f}) }%> 19 :content => (render :partial => 'signup_welcome_screen', :locals => {:f => f}) }%>
17 <%= render_tabs(tabs) %> 20 <%= render_tabs(tabs) %>
18 <% button_bar do %> 21 <% button_bar do %>
app/views/blocks/communities.html.erb 0 → 100644
@@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
  1 +<% if owner.kind_of?(Profile) %>
  2 + <%= link_to s_('communities|View all'), {:profile => owner.identifier, :controller => 'profile', :action => 'communities'}, :class => 'view-all' %>
  3 +<% elsif owner.kind_of?(Environment) %>
  4 + <%= link_to s_('communities|View all'), {:controller => 'search', :action => 'communities'}, :class => 'view-all' %>
  5 +<% end %>
  6 +
  7 +<% if user && user == profile && suggestions && !suggestions.empty? %>
  8 + <div class='suggestions-block common-profile-list-block'>
  9 + <h4 class='block-subtitle'><%= _('Some suggestions for you') %></h4>
  10 + <div class='profiles-suggestions'>
  11 + <%= render :partial => 'shared/profile_suggestions_list', :locals => { :suggestions => suggestions, :collection => :communities_suggestions, :per_page => 3 } %>
  12 + </div>
  13 + <div class='more-suggestions'>
  14 + <%= link_to _('See all suggestions'), profile.communities_suggestions_url %>
  15 + </div>
  16 + </div>
  17 +<% end %>
app/views/blocks/my_network.html.erb
1 <%= block_title(title) %> 1 <%= block_title(title) %>
2 2
3 -<%= render_profile_actions owner.class %>  
4 -  
5 <ul> 3 <ul>
6 <li><%= link_to(_('Homepage'), owner.url, :class => 'url') %></li> 4 <li><%= link_to(_('Homepage'), owner.url, :class => 'url') %></li>
7 <li><%= link_to(_('View profile'), owner.public_profile_url) %></li> 5 <li><%= link_to(_('View profile'), owner.public_profile_url) %></li>
@@ -11,5 +9,5 @@ @@ -11,5 +9,5 @@
11 </ul> 9 </ul>
12 10
13 <div class="my-network-actions"> 11 <div class="my-network-actions">
14 - <%= render 'blocks/profile_info_actions/' + owner.class.name.underscore %> 12 + <%= render_profile_actions owner.class %>
15 </div> 13 </div>
app/views/box_organizer/edit.html.erb
@@ -5,6 +5,12 @@ @@ -5,6 +5,12 @@
5 5
6 <%= labelled_form_field(_('Custom title for this block: '), text_field(:block, :title, :maxlength => 20)) %> 6 <%= labelled_form_field(_('Custom title for this block: '), text_field(:block, :title, :maxlength => 20)) %>
7 7
  8 + <% if environment.admins.include?(user) %>
  9 + <div class="fixed_block">
  10 + <%= labelled_check_box(_("Fixed"), "block[fixed]", value = "1", checked = @block.fixed) %>
  11 + </div>
  12 + <% end %>
  13 +
8 <%= render :partial => partial_for_class(@block.class) %> 14 <%= render :partial => partial_for_class(@block.class) %>
9 15
10 <div class="display"> 16 <div class="display">
app/views/cms/_drag_and_drop_note.html.erb
1 <p> 1 <p>
2 <em> 2 <em>
3 <%= _('Drag images to add them to the text.') %> 3 <%= _('Drag images to add them to the text.') %>
4 - <%= _('Drag file names to the text to add links.') %> 4 + <%= _('Click on file names to add links to the text.') %>
5 </em> 5 </em>
6 </p> 6 </p>
app/views/cms/_general_fields.html.erb
1 <%= select_profile_folder(_('Parent folder:'), 'article[parent_id]', profile, @article.parent_id) %> 1 <%= select_profile_folder(_('Parent folder:'), 'article[parent_id]', profile, @article.parent_id) %>
2 -<%= labelled_form_field(_('License'), select(:article, :license_id, options_for_select_with_title([[_('None'), nil]] + profile.environment.licenses.map {|license| [license.name, license.id]}, @article.license ? @article.license.id : nil))) %> 2 +<% if profile.environment.has_license? %>
  3 + <%= labelled_form_field(_('License'), select(:article, :license_id, options_for_select_with_title([[_('None'), nil]] + profile.environment.licenses.map {|license| [license.name, license.id]}, @article.license ? @article.license.id : nil))) %>
  4 +<% end %>
app/views/cms/_link_article.html.erb 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +<div>
  2 + <%= labelled_form_field(_('Title'), @article.name) %>
  3 + <%= labelled_form_field(_('Reference'), link_to(url_for @article.view_url)) %>
  4 +
  5 + <%= render :partial => 'general_fields' %>
  6 +</div>
app/views/cms/_media_new_folder.html.erb 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +<%= form_tag({:action => 'new', :profile => profile.identifier}, :id => 'new-folder-dialog', :title => _('Create new folder'), :style => 'display: none;') do %>
  2 + <%= select_profile_folder(
  3 + _('Choose parent folder:'),
  4 + :parent_id, profile, default_folder, {}, {},
  5 + "type='Folder' or type='Gallery'")
  6 + %>
  7 +
  8 + <%= labelled_radio_button _('Gallery'), :folder_type, 'Gallery', true %>
  9 + <%= labelled_radio_button _('Folder'), :folder_type, 'Folder', false %>
  10 +
  11 + <%= labelled_form_field _('Name:'), text_field_tag(:new_folder, nil, 'data-url' => url_for({:action => 'new', :profile => profile.identifier})) %>
  12 + <% button_bar do %>
  13 + <%= submit_button(:newfolder, _('Create')) %>
  14 + <% end %>
  15 +<% end %>
app/views/cms/_published_media_items.html.erb 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +<% file_types.each do |key, header| %>
  2 + <% display = @recent_files[key].present? ? '' : 'none' %>
  3 + <div class='<%= key.to_s %>' style='display: <%= display%>;'>
  4 + <div class='section-title'>
  5 + <h3><%= header %></h3>
  6 + <% if @recent_files[key].total_pages > 1 %>
  7 + <%= link_to(_('View all'), {:controller => 'cms', :action => 'view_all_media', :profile => profile.identifier, :key => key}, :class => 'view-all colorbox', 'data-key' => key) %>
  8 + <% end %>
  9 + </div>
  10 + <%= render :partial => "cms/media_panel/list_published_media_items", :locals => { key: key, show_pagination_links: false } %>
  11 + </div>
  12 +<% end %>
app/views/cms/_text_editor_sidebar.html.erb
  1 +<% default_folder = content_id_to_str default_folder_for_image_upload(profile) %>
  2 +
1 <div class='text-editor-sidebar'> 3 <div class='text-editor-sidebar'>
2 <span class='button-add' data-value='<%= _('Add to the text') %>'></span> 4 <span class='button-add' data-value='<%= _('Add to the text') %>'></span>
3 <span class='button-zoom' data-value='<%= _('Zoom in') %>'></span> 5 <span class='button-zoom' data-value='<%= _('Zoom in') %>'></span>
4 <span class='button-close' data-value='<%= _('Close') %>'></span> 6 <span class='button-close' data-value='<%= _('Close') %>'></span>
5 7
  8 + <div class='header'><strong><%= _('Insert media') %></strong><%= button('vertical-toggle', _('Show/Hide'), '#') %></div>
  9 +
6 <%= render(:partial => 'textile_quick_reference') if @article.is_a?(TextileArticle) %> 10 <%= render(:partial => 'textile_quick_reference') if @article.is_a?(TextileArticle) %>
7 <div class='text-editor-sidebar-box' id='media-upload-box'> 11 <div class='text-editor-sidebar-box' id='media-upload-box'>
8 - <div class='header'><strong><%= _('Insert media') %></strong></div>  
9 <div id='media-upload-form'> 12 <div id='media-upload-form'>
10 <%= form_tag({ :action => 'media_upload' }, :multipart => true) do %> 13 <%= form_tag({ :action => 'media_upload' }, :multipart => true) do %>
11 <div class='formfield'> 14 <div class='formfield'>
12 - <% default_folder = content_id_to_str default_folder_for_image_upload(profile) %>  
13 <%= select_profile_folder( 15 <%= select_profile_folder(
14 _('Choose folder to upload files:'), 16 _('Choose folder to upload files:'),
15 :parent_id, profile, default_folder, {}, {}, 17 :parent_id, profile, default_folder, {}, {},
16 "type='Folder' or type='Gallery'" 18 "type='Folder' or type='Gallery'"
17 ) %> 19 ) %>
18 </div> 20 </div>
19 - <p><%= file_field_tag('file1') %></p>  
20 - <p><%= file_field_tag('file2') %></p>  
21 - <p><%= file_field_tag('file3') %></p>  
22 - <% button_bar do %>  
23 - <%= submit_button(:save, _('Upload')) %>  
24 - <% end %> 21 + <%= button(:newfolder, _('New folder'), '#', :id => 'new-folder-button') %>
  22 + <p><%= file_field_tag('file', :multiple => true) %></p>
25 <% end %> 23 <% end %>
26 </div> 24 </div>
27 - <div id='media-upload-results' style='display: none'>  
28 - <%= render :partial => 'drag_and_drop_note' %>  
29 - <div class='items'>  
30 - </div>  
31 - <p><%= link_to(_('Upload more files ...'), '#', :id => 'media-upload-more-files')%></p> 25 + <div class='hide-and-show-uploads'>
  26 + <%= link_to(_('Hide all uploads'), nil, :id => 'hide-uploads', :style => 'display: none;', 'data-bootstraped' => false) %>
  27 + <%= link_to(_('Show all uploads'), nil, :id => 'show-uploads', :style => 'display: none;') %>
32 </div> 28 </div>
33 </div> 29 </div>
34 - <div id='media-search-box' class='text-editor-sidebar-box'>  
35 - <div class='header'><strong><%= _('Media search') %></strong></div>  
36 - <p>  
37 - <%= form_tag({ :action => 'search' }) do %>  
38 - <span class='formfield'>  
39 - <input name='q' type='text' id='media-search-query' style='width: 250px;'/>  
40 - </span>  
41 - <%= submit_button :search, _('Search'), :id => 'media-search-button' %>  
42 - <% end %>  
43 - </p>  
44 - <div id='media-search-results' style='display: none'>  
45 - <%= render :partial => 'drag_and_drop_note' %>  
46 - <div class='items'>  
47 - </div> 30 +
  31 + <div id='published-media' class='text-editor-sidebar-box' data-url='<%= url_for({:controller => 'cms', :action => 'published_media_items', :profile => profile.identifier}) %>'>
  32 + <%= select_profile_folder(nil, :parent_id, profile, 'recent-media', {}, {},
  33 + "type='Folder' or type='Gallery'", {:root_label => _('Recent media')}) %>
  34 + <%= labelled_form_field _('Search'), text_field_tag('q') %>
  35 + <%= render :partial => 'drag_and_drop_note' %>
  36 + <div class='items'>
  37 + <%= render :partial => 'published_media_items' %>
48 </div> 38 </div>
49 </div> 39 </div>
50 </div> 40 </div>
51 41
  42 +<script id="template-upload" type="text/x-tmpl">
  43 + <div id="file-{%= o.id %}" class="upload" title="{%= o.name %}">
  44 + <div class="file-name">{%=o.name%}</div>
  45 + <div class="percentage"></div>
  46 + <div class="progress"><div class="bar" style="width: 0%;"></div></div>
  47 + </div>
  48 +</script>
52 49
  50 +<%= render :partial => 'media_new_folder', :locals => {:default_folder => default_folder} %>
  51 +<%= javascript_include_tag 'jquery.fileupload.js', 'tmpl.js', 'media-panel.js' %>
app/views/cms/_textile_quick_reference.html.erb
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 <pre># <%= _('first item') %> 15 <pre># <%= _('first item') %>
16 # <%= _('second item') %></pre> 16 # <%= _('second item') %></pre>
17 <p><%= h(_('For code, use HTML tags <pre> and <code>, and indent the code inside them:')) %> 17 <p><%= h(_('For code, use HTML tags <pre> and <code>, and indent the code inside them:')) %>
  18 + </p>
18 <pre> 19 <pre>
19 &lt;pre&gt; 20 &lt;pre&gt;
20 &lt;code&gt; 21 &lt;code&gt;
app/views/cms/edit.html.erb
1 <%= error_messages_for 'article' %> 1 <%= error_messages_for 'article' %>
2 2
3 -<div class='<%= (environment.enabled?('media_panel') ? 'with_media_panel' : 'no_media_panel') %>'> 3 +<% show_media_panel = environment.enabled?('media_panel') && [TinyMceArticle, TextileArticle, Event, EnterpriseHomepage].any?{|klass| @article.kind_of?(klass)} %>
  4 +
  5 +<div class='<%= (show_media_panel ? 'with_media_panel' : 'no_media_panel') %>'>
4 <%= labelled_form_for 'article', :html => { :multipart => true, :class => @type } do |f| %> 6 <%= labelled_form_for 'article', :html => { :multipart => true, :class => @type } do |f| %>
5 7
6 <%= hidden_field_tag("type", @type) if @type %> 8 <%= hidden_field_tag("type", @type) if @type %>
@@ -66,7 +68,7 @@ @@ -66,7 +68,7 @@
66 <% end %> 68 <% end %>
67 </div> 69 </div>
68 70
69 -<% if environment.enabled?('media_panel') && [TinyMceArticle, TextileArticle, Event, EnterpriseHomepage].any?{|klass| @article.kind_of?(klass)} %> 71 +<% if show_media_panel %>
70 <%= render :partial => 'text_editor_sidebar' %> 72 <%= render :partial => 'text_editor_sidebar' %>
71 <% end %> 73 <% end %>
72 74
app/views/cms/media_panel/_generic.html.erb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +<div class="item file <%= icon_for_article(@file) %>" data-item="div">
  2 + <div>
  3 + <a class="add-to-text" href="<%= url_for(@file.url) %>" title="<%= @file.title %>"><%= @file.title %></a>
  4 + </div>
  5 +</div>
app/views/cms/media_panel/_image.html.erb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +<div class="item image" data-item="span" title="<%= @file.name %>">
  2 + <span>
  3 + <img src="<%= @file.public_filename(:uploaded) %>"/>
  4 + </span>
  5 + <div class="controls image-controls">
  6 + <a class="button icon-add add-to-text" href="#"><span><%= _('Add to the text') %></span></a>
  7 + <a class="button icon-zoom zoom" href="#" title="<%= _('Zoom in') %>"><span><%= _('Zoom in') %></span></a>
  8 + </div>
  9 +</div>
app/views/cms/media_panel/_list_published_media_items.html.erb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +<% @recent_files[key].each do |file| %>
  2 + <% @file = file %>
  3 + <%= render :partial => "cms/media_panel/#{key.to_s.singularize}" %>
  4 +<% end %>
  5 +<%= pagination_links @recent_files[key] if show_pagination_links %>