Commit 6946395bf26bd2438ff2f85deb063936651d3b58

Authored by Victor Costa
2 parents babac538 c456fe22

Merge branch 'master' into rails3_AI3033-serpro_integration

Showing 1715 changed files with 110187 additions and 159269 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 1715 files displayed.

1 --ignore-dir=log 1 --ignore-dir=log
2 --ignore-dir=tmp 2 --ignore-dir=tmp
3 --ignore-dir=pkg 3 --ignore-dir=pkg
  4 +--ignore-dir=public/javascripts/cache
  5 +--ignore-dir=public/stylesheets/cache
@@ -1,251 +0,0 @@ @@ -1,251 +0,0 @@
1 -If you are not listed here, but should be, please write to the noosfero mailing  
2 -list: http://listas.softwarelivre.org/cgi-bin/mailman/listinfo/noosfero-dev  
3 -(this list requires subscription to post, but since you are an author of  
4 -noosfero, that's not a problem).  
5 -  
6 -Developers  
7 -==========  
8 -  
9 -Ábner Silva de Oliveira <abner.oliveira@serpro.gov.br>  
10 -Alan Freihof Tygel <alantygel@gmail.com>  
11 -alcampelo <alcampelo@alcampelo.(none)>  
12 -Alessandro Palmeira <alessandro.palmeira@gmail.com>  
13 -Alessandro Palmeira + Caio C. Salgado <alessandro.palmeira@gmail.com>  
14 -Alessandro Palmeira + Caio Salgado <alessandro.palmeira@gmail.com>  
15 -Alessandro Palmeira + Caio Salgado <caio.csalgado@gmail.com>  
16 -Alessandro Palmeira + Caio Salgado + Diego Araújo + João M. M. da Silva <diegoamc90@gmail.com>  
17 -Alessandro Palmeira + Carlos Morais <alessandro.palmeira@gmail.com>  
18 -Alessandro Palmeira + Daniel Alves <alessandro.palmeira@gmail.com>  
19 -Alessandro Palmeira + Daniel Alves + Diego Araújo <diegoamc90@gmail.com>  
20 -Alessandro Palmeira + Daniel Alves + Diego Araújo + Guilherme Rojas <danpaulalves@gmail.com>  
21 -Alessandro Palmeira + Diego Araujo <alessandro.palmeira@gmail.com>  
22 -Alessandro Palmeira + Diego Araújo <alessandro.palmeira@gmail.com>  
23 -Alessandro Palmeira + Diego Araujo + Daniela Feitosa <alessandro.palmeira@gmail.com>  
24 -Alessandro Palmeira + Diego Araujo <diegoamc90@gmail.com>  
25 -Alessandro Palmeira + Diego Araújo <diegoamc90@gmail.com>  
26 -Alessandro Palmeira + Diego Araujo + Eduardo Morais <alessandro.palmeira@gmail.com>  
27 -Alessandro Palmeira + Diego Araújo + João M. M. da Silva <alessandro.palmeira@gmail.com>  
28 -Alessandro Palmeira + Diego Araújo + João M. M. da Silva <diegoamc90@gmail.com>  
29 -Alessandro Palmeira + Diego Araujo + João M. M. da Silva + Paulo Meirelles <alessandro.palmeira@gmail.com>  
30 -Alessandro Palmeira + Diego Araújo + Pedro Leal <diegoamc90@gmail.com>  
31 -Alessandro Palmeira + Diego Araújo + Pedro Leal + João M. M. da Silva <diegoamc90@gmail.com>  
32 -Alessandro Palmeira + Diego Araujo + Rafael Manzo <alessandro.palmeira@gmail.com>  
33 -Alessandro Palmeira + Eduardo Morais <alessandro.palmeira@gmail.com>  
34 -Alessandro Palmeira + Guilherme Rojas <alessandro.palmeira@gmail.com>  
35 -Alessandro Palmeira + Jefferson Fernandes <alessandro.palmeira@gmail.com>  
36 -Alessandro Palmeira + João M. M. da Silva <alessandro.palmeira@gmail.com>  
37 -Alessandro Palmeira + Joao M. M. da Silva + Diego Araujo <alessandro.palmeira@gmail.com>  
38 -Alessandro Palmeira + João M. M. da Silva + Renan Teruo <alessandro.palmeira@gmail.com>  
39 -Alessandro Palmeira + João M. M. Silva <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>  
42 -Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com>  
43 -Ana Losnak <analosnak@gmail.com>  
44 -Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br>  
45 -Antonio Terceiro + Paulo Meirelles <terceiro@colivre.coop.br>  
46 -Antonio Terceiro <terceiro@colivre.coop.br>  
47 -Arthur Del Esposte <arthurmde@gmail.com>  
48 -Arthur Del Esposte <arthurmde@yahoo.com.br>  
49 -Aurelio A. Heckert <aurelio@colivre.coop.br>  
50 -Braulio Bhavamitra <brauliobo@gmail.com>  
51 -Bráulio Bhavamitra <brauliobo@gmail.com>  
52 -Braulio Bhavamitra <braulio@eita.org.br>  
53 -Caio <caio.csalgado@gmail.com>  
54 -Caio + Diego + Pedro + João <caio.csalgado@gmail.com>  
55 -Caio Formiga <caio.formiga@gmail.com>  
56 -Caio, Pedro <caio.csalgado@gmail.com>  
57 -Caio Salgado + Alessandro Palmeira <caio.csalgado@gmail.com>  
58 -Caio Salgado <caio.csalgado@gmail.com>  
59 -Caio Salgado + Carlos Morais + Diego Araújo + Pedro Leal <diegoamc90@gmail.com>  
60 -Caio Salgado + Diego Araujo <caio.csalgado@gmail.com>  
61 -Caio Salgado + Diego Araújo <caio.csalgado@gmail.com>  
62 -Caio Salgado + Diego Araújo <diegoamc90@gmail.com>  
63 -Caio Salgado + Diego Araújo + Jefferson Fernandes <caio.csalgado@gmail.com>  
64 -Caio Salgado + Diego Araújo + João M. M. da Silva <caio.csalgado@gmail.com>  
65 -Caio Salgado + Diego Araújo + Pedro Leal <caio.csalgado@gmail.com>  
66 -Caio Salgado + Diego Araújo + Pedro Leal <diegoamc90@gmail.com>  
67 -Caio Salgado + Diego Araújo + Rafael Manzo <diegoamc90@gmail.com>  
68 -Caio Salgado + Jefferson Fernandes <caio.csalgado@gmail.com>  
69 -Caio Salgado + Jefferson Fernandes <jeffs.fernandes@gmail.com>  
70 -Caio Salgado + Rafael Manzo <caio.csalgado@gmail.com>  
71 -Caio Salgado + Renan Teruo <caio.csalgado@gmail.com>  
72 -Caio Salgado + Renan Teruo <caio.salgado@gmail.com>  
73 -Caio Salgado + Renan Teruo + Jefferson Fernandes <jeffs.fernandes@gmail.com>  
74 -Caio Salgado + Renan Teruo <renanteruoc@gmail.com>  
75 -Caio SBA <caio@colivre.coop.br>  
76 -Caio Tiago Oliveira <caiotiago@colivre.coop.br>  
77 -Carlos Andre de Souza <carlos.andre.souza@msn.com>  
78 -Carlos Morais <carlos88morais@gmail.com>  
79 -Carlos Morais + Diego Araújo <diegoamc90@gmail.com>  
80 -Carlos Morais + Eduardo Morais <carlos88morais@gmail.com>  
81 -Carlos Morais + Paulo Meirelles <carlos88morais@gmail.com>  
82 -Carlos Morais + Pedro Leal <carlos88morais@gmail.com>  
83 -Daniela Feitosa <dani@dohko.(none)>  
84 -Daniel Alves + Diego Araújo <danpaulalves@gmail.com>  
85 -Daniel Alves + Diego Araújo <diegoamc90@gmail.com>  
86 -Daniel Alves + Diego Araújo + Guilherme Rojas <danpaulalves@gmail.com>  
87 -Daniel Alves + Diego Araújo + Guilherme Rojas <diegoamc90@gmail.com>  
88 -Daniel Alves + Diego Araújo + Guilherme Rojas <guilhermehrojas@gmail.com>  
89 -Daniel Alves + Guilherme Rojas <danpaulalves@gmail.com>  
90 -Daniel Alves + Rafael Manzo <rr.manzo@gmail.com>  
91 -Daniela Soares Feitosa <danielafeitosa@colivre.coop.br>  
92 -Daniel Bucher <daniel.bucher88@gmail.com>  
93 -Daniel Cunha <daniel@colivre.coop.br>  
94 -David Carlos <ddavidcarlos1392@gmail.com>  
95 -diegoamc <diegoamc90@gmail.com>  
96 -Diego Araújo + Alessandro Palmeira <diegoamc90@gmail.com>  
97 -Diego Araújo + Alessandro Palmeira + João M. M. da Silva <diegoamc90@gmail.com>  
98 -Diego Araújo + Alessandro Palmeira + Rafael Manzo <rr.manzo@gmail.com>  
99 -Diego Araujo + Caio Salgado <diegoamc90@gmail.com>  
100 -Diego Araújo + Daniel Alves + Rafael Manzo <rr.manzo@gmail.com>  
101 -Diego Araújo <diegoamc90@gmail.com>  
102 -Diego Araújo + Eduardo Morais + Paulo Meirelles <diegoamc90@gmail.com>  
103 -Diego Araújo + Guilherme Rojas <diegoamc90@gmail.com>  
104 -Diego Araújo + Jefferson Fernandes <diegoamc90@gmail.com>  
105 -Diego Araujo + Jefferson Fernandes <jeffs.fernandes@gmail.com>  
106 -Diego Araújo + João Machini <diegoamc90@gmail.com>  
107 -Diego Araújo + João Machini <digoamc90@gmail.com>  
108 -Diego Araújo + João M. M. da Silva + Alessandro Palmeira <jaodsilv@linux.ime.usp.br>  
109 -Diego Araújo + João M. M. da Silva <diegoamc90@gmail.com>  
110 -Diego Araújo + João M. M. da Silva + João Machini <diegoamc90@gmail.com>  
111 -Diego Araújo + João M. M. da Silva + Pedro Leal <diegoamc90@gmail.com>  
112 -Diego Araújo + Paulo Meirelles <diegoamc90@gmail.com>  
113 -Diego Araújo + Pedro Leal <diegoamc90@gmail.com>  
114 -Diego Araujo + Rafael Manzo <diegoamc90@gmail.com>  
115 -Diego Araújo + Rafael Manzo <diegoamc90@gmail.com>  
116 -Diego Araújo + Renan Teruo + Alessandro Palmeira <diegoamc90@gmail.com>  
117 -Diego Araújo + Renan Teruo <diegoamc90@gmail.com>  
118 -Diego Araujo + Rodrigo Souto + Rafael Manzo <rr.manzo@gmail.com>  
119 -Diego + Jefferson <diegoamc90@gmail.com>  
120 -Diego Martinez <diegoamc90@gmail.com>  
121 -Diego Martinez <diego@diego-K55A.(none)>  
122 -Diego + Renan <renanteruoc@gmail.com>  
123 -Eduardo Tourinho Edington <eduardo.edington@serpro.gov.br>  
124 -Evandro Jr <evandrojr@gmail.com>  
125 -Evandro Junior <evandrojr@gmail.com>  
126 -Fabio Teixeira <fabio1079@gmail.com>  
127 -Fernanda Lopes <nanda.listas+psl@gmail.com>  
128 -Francisco Marcelo A. Lima Júnior <francisco.lima-junior@serpro.gov.br>  
129 -Francisco Marcelo de Araujo Lima Junior <79350259591@serpro-1457614.(none)>  
130 -Francisco Marcelo de Araújo Lima Júnior <francisco.lima-junior@serpro.gov.br>  
131 -Francisco Marcelo de Araújo Lima Júnior <maljunior@gmail.com>  
132 -Gabriela Navarro <navarro1703@gmail.com>  
133 -Grazieno Pellegrino <grazieno@gmail.com>  
134 -Gust <darksshades@hotmail.com>  
135 -Hugo Melo <hugo@riseup.net>  
136 -Isaac Canan <isaac@intelletto.com.br>  
137 -Italo Valcy <italo@dcc.ufba.br>  
138 -Jefferson Fernandes + Diego Araujo + Rafael Manzo <jeffs.fernandes@gmail.com>  
139 -Jefferson Fernandes + Joao M. M. da Silva <jeffs.fernandes@gmail.com>  
140 -Jefferson Fernandes + Joao M. M. Silva <jeffs.fernandes@gmail.com>  
141 -João da Silva + Eduardo Morais + Rafael Manzo <rr.manzo@gmail.com>  
142 -João da Silva <jaodsilv@linux.ime.usp.br>  
143 -João Marco Maciel da Silva + Rafael Manzo + Renan Teruo <jaodsilv@linux.ime.usp.br>  
144 -João M. M. da Silva + Alessandro Palmeira + Diego Araújo + Caio Salgado <jaodsilv@linux.ime.usp.br>  
145 -João M. M. da Silva + Alessandro Palmeira + Diego Araújo <jaodsilv@linux.ime.usp.br>  
146 -Joao M. M. da Silva + Alessandro Palmeira <jaodsilv@linux.ime.usp.br>  
147 -João M. M. da Silva + Alessandro Palmeira <jaodsilv@linux.ime.usp.br>  
148 -João M. M. da Silva + Alessandro Palmeira + João Machini <jaodsilv@linux.ime.usp.br>  
149 -João M. M. da Silva + Caio Salgado + Alessandro Palmeira <jaodsilv@linux.ime.usp.br>  
150 -João M. M. da Silva + Caio Salgado <jaodsilv@linux.ime.usp.br>  
151 -João M. M. da Silva + Carlos Morais <jaodsilv@linux.ime.usp.br>  
152 -João M. M. da Silva + Diego Araújo <diegoamc90@gmail.com>  
153 -João M. M. da Silva + Diego Araújo <jaodsilv@linux.ime.usp.br>  
154 -João M. M. da Silva + Diego Araújo + Pedro Leal <jaodsilv@linux.ime.usp.br>  
155 -João M. M. da Silva <jaodsilv@linux.ime.usp.br>  
156 -Joao M. M. da Silva + Jefferson Fernandes <jaodsilv@linux.ime.usp.br>  
157 -João M. M. da Silva + Jefferson Fernandes <jaodsilv@linux.ime.usp.br>  
158 -João M. M. da Silva + João M. Miranda <jaodsilv@linux.ime.usp.br>  
159 -João M. M. da Silva + Paulo Meirelles <jaodsilv@linux.ime.usp.br>  
160 -João M. M. da Silva + Pedro Leal <jaodsilv@linux.ime.usp.br>  
161 -João M. M. da Silva + Rafael Manzo + Diego Araújo <jaodsilv@linux.ime.usp.br>  
162 -João M. M. da Silva + Rafael Manzo <jaodsilv@linux.ime.usp.br>  
163 -João M. M. da Silva + Renan Teruo <jaodsilv@linux.ime.usp.br>  
164 -João M. M. Silva + Caio Salgado <jaodsilv@linux.ime.usp.br>  
165 -João M. M. Silva + Diego Araújo <jaodsilv@linux.ime.usp.br>  
166 -Joao M. M. Silva + Jefferson Fernandes <jaodsilv@linux.ime.usp.br>  
167 -João M. M. Silva + Paulo Meirelles <jaodsilv@linux.ime.usp.br>  
168 -João M. M. Silva + Rafael Manzo <jaodsilv@linux.ime.usp.br>  
169 -João M. M. Silva + Renan Teruo <jaodsilv@linux.ime.usp.br>  
170 -Joenio Costa <joenio@colivre.coop.br>  
171 -Josef Spillner <josef.spillner@tu-dresden.de>  
172 -Junior Silva <junior@bajor.localhost.localdomain>  
173 -Junior Silva <junior@sedeantigo.colivre.coop.br>  
174 -Junior Silva <juniorsilva1001@gmail.com>  
175 -Junior Silva <juniorsilva7@juniorsilva-Aspire-5750Z.(none)>  
176 -Junior Silva <juniorsilva@colivre.coop.br>  
177 -juniorsilva <juniorsilva@QonoS.localhost.localdomain>  
178 -Keilla Menezes <keilla@colivre.coop.br>  
179 -Larissa Reis <larissa@colivre.coop.br>  
180 -Larissa Reis <reiss.larissa@gmail.com>  
181 -Leandro Nunes dos Santos <81665687568@serpro-1541727.Home>  
182 -Leandro Nunes dos Santos <81665687568@serpro-1541727.(none)>  
183 -Leandro Nunes dos Santos <leandronunes@gmail.com>  
184 -Leandro Nunes dos Santos <leandro.santos@serpro.gov.br>  
185 -LinguÁgil 2010 <linguagil.bahia@gmail.com>  
186 -Lucas Melo <lucas@colivre.coop.br>  
187 -Lucas Melo <lucaspradomelo@gmail.com>  
188 -Luciano <lucianopcbr@gmail.com>  
189 -Luis David Aguilar Carlos <ludwig9003@gmail.com>  
190 -Luiz Fernando de Freitas Matos <luiz@luizff.matos@gmail.com>  
191 -Marcos Ramos <ms.ramos@outlook.com>  
192 -Martín Olivera <molivera@solar.org.ar>  
193 -Moises Machado <moises@colivre.coop.br>  
194 -Naíla Alves <naila@colivre.coop.br>  
195 -Nanda Lopes <nanda.listas+psl@gmail.com>  
196 -Paulo Meirelles + Alessandro Palmeira + João M. M. da Silva <paulo@softwarelivre.org>  
197 -Paulo Meirelles + Alessandro Palmeira <paulo@softwarelivre.org>  
198 -Paulo Meirelles + Carlos Morais <paulo@softwarelivre.org>  
199 -Paulo Meirelles + Diego Araújo <paulo@softwarelivre.org>  
200 -Paulo Meirelles + João M. M. da Silva <paulo@softwarelivre.org>  
201 -Paulo Meirelles <paulo@softwarelivre.org>  
202 -Paulo Meirelles + Rafael Manzo <paulo@softwarelivre.org>  
203 -Rafael Gomes <rafaelgomes@techfree.com.br>  
204 -Rafael Manzo + Alessandro Palmeira <rr.manzo@gmail.com>  
205 -Rafael Manzo + Daniel Alves <danpaulalves@gmail.com>  
206 -Rafael Manzo + Diego Araújo <rr.manzo@gmail.com>  
207 -Rafael Manzo + João M. M. Silva <rr.manzo@gmail.com>  
208 -Rafael Manzo + Paulo Meirelles <rr.manzo@gmail.com>  
209 -Rafael Martins <rmmartins@gmail.com>  
210 -Rafael Reggiani Manzo + Caio Salgado + Jefferson Fernandes <rr.manzo@gmail.com>  
211 -Rafael Reggiani Manzo + Diego Araujo <diegoamc90@gmail.com>  
212 -Rafael Reggiani Manzo + Diego Araujo <rr.manzo@gmail.com>  
213 -Rafael Reggiani Manzo + Diego Araújo <rr.manzo@gmail.com>  
214 -Rafael Reggiani Manzo + João M. M. da Silva <rr.manzo@gmail.com>  
215 -Rafael Reggiani Manzo <rr.manzo@gmail.com>  
216 -Raphaël Rousseau <raph@r4f.org>  
217 -Raquel Lira <raquel.lira@gmail.com>  
218 -Renan Teruo + Caio Salgado <renanteruoc@gmail.com>  
219 -Renan Teruoc + Diego Araujo <renanteruoc@gmail.com>  
220 -Renan Teruo + Diego Araujo <renanteruoc@gmail.com>  
221 -Renan Teruo + Diego Araújo <renanteruoc@gmail.com>  
222 -Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com>  
223 -Renan Teruo + Rafael Manzo <renanteruoc@gmail.com>  
224 -Rodrigo Souto + Ana Losnak + Daniel Bucher + Caio Almeida + Leandro Nunes + Daniela Feitosa + Mariel Zasso <noosfero-br@listas.softwarelivre.org>  
225 -Rodrigo Souto <diguliu@gmail.com>  
226 -Rodrigo Souto <rodrigo@colivre.coop.br>  
227 -Ronny Kursawe <kursawe.ronny@googlemail.com>  
228 -root <root@debian.sdr.serpro>  
229 -Samuel R. C. Vale <srcvale@holoscopio.com>  
230 -Valessio Brito <contato@valessiobrito.com.br>  
231 -Valessio Brito <contato@valessiobrito.info>  
232 -Valessio Brito <valessio@gmail.com>  
233 -vfcosta <vfcosta@gmail.com>  
234 -Victor Carvalho <victorhugodf.ac@gmail.com>  
235 -Victor Costa <vfcosta@gmail.com>  
236 -Victor Hugo Alves de Carvalho <victorhugodf.ac@gmail.com>  
237 -Vinicius Cubas Brand <viniciuscb@gmail.com>  
238 -Visita <visita@debian.(none)>  
239 -Yann Lugrin <yann.lugrin@liquid-concept.ch>  
240 -  
241 -Ideas, specifications and incentive  
242 -===================================  
243 -Daniel Tygel <dtygel@fbes.org.br>  
244 -Guilherme Rocha <guilherme@gf7.com.br>  
245 -Raphael Rousseau <raph@r4f.org>  
246 -Théo Bondolfi <move@cooperation.net>  
247 -Vicente Aguiar <vicenteaguiar@colivre.coop.br>  
248 -  
249 -Arts  
250 -===================================  
251 -Nara Oliveira <narananet@gmail.com>  
1 -If you are not listed here, but should be, please write to the noosfero mailing list: http://listas.softwarelivre.org/cgi-bin/mailman/listinfo/noosfero-dev (this list requires subscription to post, but since you are an author of noosfero, that's not a problem). 1 +If you are not listed here, but should be, please write to the noosfero mailing
  2 +list: http://listas.softwarelivre.org/cgi-bin/mailman/listinfo/noosfero-dev
  3 +(this list requires subscription to post, but since you are an author of
  4 +noosfero, that's not a problem).
2 5
3 Developers 6 Developers
4 ========== 7 ==========
5 8
  9 +Ábner Silva de Oliveira <abner.oliveira@serpro.gov.br>
6 Alan Freihof Tygel <alantygel@gmail.com> 10 Alan Freihof Tygel <alantygel@gmail.com>
  11 +alcampelo <alcampelo@alcampelo.(none)>
7 Alessandro Palmeira <alessandro.palmeira@gmail.com> 12 Alessandro Palmeira <alessandro.palmeira@gmail.com>
8 Alessandro Palmeira + Caio C. Salgado <alessandro.palmeira@gmail.com> 13 Alessandro Palmeira + Caio C. Salgado <alessandro.palmeira@gmail.com>
9 Alessandro Palmeira + Caio Salgado <alessandro.palmeira@gmail.com> 14 Alessandro Palmeira + Caio Salgado <alessandro.palmeira@gmail.com>
@@ -35,9 +40,14 @@ Alessandro Palmeira + João M. M. Silva &lt;alessandro.palmeira@gmail.com&gt; @@ -35,9 +40,14 @@ Alessandro Palmeira + João M. M. Silva &lt;alessandro.palmeira@gmail.com&gt;
35 Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com> 40 Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com>
36 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>
37 Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com> 42 Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com>
  43 +analosnak <analosnak@gmail.com>
  44 +Ana Losnak <analosnak@gmail.com>
  45 +Andre Bernardes <andrebsguedes@gmail.com>
38 Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br> 46 Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br>
39 Antonio Terceiro + Paulo Meirelles <terceiro@colivre.coop.br> 47 Antonio Terceiro + Paulo Meirelles <terceiro@colivre.coop.br>
40 Antonio Terceiro <terceiro@colivre.coop.br> 48 Antonio Terceiro <terceiro@colivre.coop.br>
  49 +Arthur Del Esposte <arthurmde@gmail.com>
  50 +Arthur Del Esposte <arthurmde@yahoo.com.br>
41 Aurelio A. Heckert <aurelio@colivre.coop.br> 51 Aurelio A. Heckert <aurelio@colivre.coop.br>
42 Braulio Bhavamitra <brauliobo@gmail.com> 52 Braulio Bhavamitra <brauliobo@gmail.com>
43 Bráulio Bhavamitra <brauliobo@gmail.com> 53 Bráulio Bhavamitra <brauliobo@gmail.com>
@@ -65,6 +75,8 @@ Caio Salgado + Renan Teruo &lt;caio.salgado@gmail.com&gt; @@ -65,6 +75,8 @@ Caio Salgado + Renan Teruo &lt;caio.salgado@gmail.com&gt;
65 Caio Salgado + Renan Teruo + Jefferson Fernandes <jeffs.fernandes@gmail.com> 75 Caio Salgado + Renan Teruo + Jefferson Fernandes <jeffs.fernandes@gmail.com>
66 Caio Salgado + Renan Teruo <renanteruoc@gmail.com> 76 Caio Salgado + Renan Teruo <renanteruoc@gmail.com>
67 Caio SBA <caio@colivre.coop.br> 77 Caio SBA <caio@colivre.coop.br>
  78 +Caio Tiago Oliveira <caiotiago@colivre.coop.br>
  79 +Carlos Andre de Souza <carlos.andre.souza@msn.com>
68 Carlos Morais <carlos88morais@gmail.com> 80 Carlos Morais <carlos88morais@gmail.com>
69 Carlos Morais + Diego Araújo <diegoamc90@gmail.com> 81 Carlos Morais + Diego Araújo <diegoamc90@gmail.com>
70 Carlos Morais + Eduardo Morais <carlos88morais@gmail.com> 82 Carlos Morais + Eduardo Morais <carlos88morais@gmail.com>
@@ -78,7 +90,9 @@ Daniel Alves + Diego Araújo + Guilherme Rojas &lt;guilhermehrojas@gmail.com&gt; @@ -78,7 +90,9 @@ Daniel Alves + Diego Araújo + Guilherme Rojas &lt;guilhermehrojas@gmail.com&gt;
78 Daniel Alves + Guilherme Rojas <danpaulalves@gmail.com> 90 Daniel Alves + Guilherme Rojas <danpaulalves@gmail.com>
79 Daniel Alves + Rafael Manzo <rr.manzo@gmail.com> 91 Daniel Alves + Rafael Manzo <rr.manzo@gmail.com>
80 Daniela Soares Feitosa <danielafeitosa@colivre.coop.br> 92 Daniela Soares Feitosa <danielafeitosa@colivre.coop.br>
  93 +Daniel Bucher <daniel.bucher88@gmail.com>
81 Daniel Cunha <daniel@colivre.coop.br> 94 Daniel Cunha <daniel@colivre.coop.br>
  95 +David Carlos <ddavidcarlos1392@gmail.com>
82 diegoamc <diegoamc90@gmail.com> 96 diegoamc <diegoamc90@gmail.com>
83 Diego Araújo + Alessandro Palmeira <diegoamc90@gmail.com> 97 Diego Araújo + Alessandro Palmeira <diegoamc90@gmail.com>
84 Diego Araújo + Alessandro Palmeira + João M. M. da Silva <diegoamc90@gmail.com> 98 Diego Araújo + Alessandro Palmeira + João M. M. da Silva <diegoamc90@gmail.com>
@@ -105,17 +119,27 @@ Diego Araújo + Renan Teruo &lt;diegoamc90@gmail.com&gt; @@ -105,17 +119,27 @@ Diego Araújo + Renan Teruo &lt;diegoamc90@gmail.com&gt;
105 Diego Araujo + Rodrigo Souto + Rafael Manzo <rr.manzo@gmail.com> 119 Diego Araujo + Rodrigo Souto + Rafael Manzo <rr.manzo@gmail.com>
106 Diego + Jefferson <diegoamc90@gmail.com> 120 Diego + Jefferson <diegoamc90@gmail.com>
107 Diego Martinez <diegoamc90@gmail.com> 121 Diego Martinez <diegoamc90@gmail.com>
108 -Diego Martinez <diego@diego-K55A.(none)>  
109 Diego + Renan <renanteruoc@gmail.com> 122 Diego + Renan <renanteruoc@gmail.com>
  123 +Eduardo Tourinho Edington <eduardo.edington@serpro.gov.br>
  124 +Evandro Jr <evandrojr@gmail.com>
  125 +Evandro Junior <evandrojr@gmail.com>
  126 +Fabio Teixeira <fabio1079@gmail.com>
110 Fernanda Lopes <nanda.listas+psl@gmail.com> 127 Fernanda Lopes <nanda.listas+psl@gmail.com>
111 Francisco Marcelo A. Lima Júnior <francisco.lima-junior@serpro.gov.br> 128 Francisco Marcelo A. Lima Júnior <francisco.lima-junior@serpro.gov.br>
112 Francisco Marcelo de Araujo Lima Junior <79350259591@serpro-1457614.(none)> 129 Francisco Marcelo de Araujo Lima Junior <79350259591@serpro-1457614.(none)>
  130 +Francisco Marcelo de Araújo Lima Júnior <francisco.lima-junior@serpro.gov.br>
  131 +Francisco Marcelo de Araújo Lima Júnior <maljunior@gmail.com>
  132 +Gabriela Navarro <navarro1703@gmail.com>
113 Grazieno Pellegrino <grazieno@gmail.com> 133 Grazieno Pellegrino <grazieno@gmail.com>
  134 +Gust <darksshades@hotmail.com>
  135 +Hebert Douglas <hebertdougl@gmail.com>
  136 +Hugo Melo <hugo@riseup.net>
114 Isaac Canan <isaac@intelletto.com.br> 137 Isaac Canan <isaac@intelletto.com.br>
115 Italo Valcy <italo@dcc.ufba.br> 138 Italo Valcy <italo@dcc.ufba.br>
116 Jefferson Fernandes + Diego Araujo + Rafael Manzo <jeffs.fernandes@gmail.com> 139 Jefferson Fernandes + Diego Araujo + Rafael Manzo <jeffs.fernandes@gmail.com>
117 Jefferson Fernandes + Joao M. M. da Silva <jeffs.fernandes@gmail.com> 140 Jefferson Fernandes + Joao M. M. da Silva <jeffs.fernandes@gmail.com>
118 Jefferson Fernandes + Joao M. M. Silva <jeffs.fernandes@gmail.com> 141 Jefferson Fernandes + Joao M. M. Silva <jeffs.fernandes@gmail.com>
  142 +João da Silva + Eduardo Morais + Rafael Manzo <rr.manzo@gmail.com>
119 João da Silva <jaodsilv@linux.ime.usp.br> 143 João da Silva <jaodsilv@linux.ime.usp.br>
120 João Marco Maciel da Silva + Rafael Manzo + Renan Teruo <jaodsilv@linux.ime.usp.br> 144 João Marco Maciel da Silva + Rafael Manzo + Renan Teruo <jaodsilv@linux.ime.usp.br>
121 João M. M. da Silva + Alessandro Palmeira + Diego Araújo + Caio Salgado <jaodsilv@linux.ime.usp.br> 145 João M. M. da Silva + Alessandro Palmeira + Diego Araújo + Caio Salgado <jaodsilv@linux.ime.usp.br>
@@ -146,21 +170,35 @@ João M. M. Silva + Rafael Manzo &lt;jaodsilv@linux.ime.usp.br&gt; @@ -146,21 +170,35 @@ João M. M. Silva + Rafael Manzo &lt;jaodsilv@linux.ime.usp.br&gt;
146 João M. M. Silva + Renan Teruo <jaodsilv@linux.ime.usp.br> 170 João M. M. Silva + Renan Teruo <jaodsilv@linux.ime.usp.br>
147 Joenio Costa <joenio@colivre.coop.br> 171 Joenio Costa <joenio@colivre.coop.br>
148 Josef Spillner <josef.spillner@tu-dresden.de> 172 Josef Spillner <josef.spillner@tu-dresden.de>
  173 +Jose Pedro <1jpsneto@gmail.com>
  174 +Junior Silva <junior@bajor.localhost.localdomain>
  175 +Junior Silva <junior@sedeantigo.colivre.coop.br>
149 Junior Silva <juniorsilva1001@gmail.com> 176 Junior Silva <juniorsilva1001@gmail.com>
150 Junior Silva <juniorsilva7@juniorsilva-Aspire-5750Z.(none)> 177 Junior Silva <juniorsilva7@juniorsilva-Aspire-5750Z.(none)>
  178 +Junior Silva <juniorsilva@colivre.coop.br>
  179 +juniorsilva <juniorsilva@QonoS.localhost.localdomain>
151 Keilla Menezes <keilla@colivre.coop.br> 180 Keilla Menezes <keilla@colivre.coop.br>
152 Larissa Reis <larissa@colivre.coop.br> 181 Larissa Reis <larissa@colivre.coop.br>
153 Larissa Reis <reiss.larissa@gmail.com> 182 Larissa Reis <reiss.larissa@gmail.com>
  183 +Leandro Alves <leandrosustenido@gmail.com>
  184 +Leandro Nunes dos Santos <81665687568@serpro-1541727.Home>
  185 +Leandro Nunes dos Santos <81665687568@serpro-1541727.(none)>
154 Leandro Nunes dos Santos <leandronunes@gmail.com> 186 Leandro Nunes dos Santos <leandronunes@gmail.com>
155 Leandro Nunes dos Santos <leandro.santos@serpro.gov.br> 187 Leandro Nunes dos Santos <leandro.santos@serpro.gov.br>
156 LinguÁgil 2010 <linguagil.bahia@gmail.com> 188 LinguÁgil 2010 <linguagil.bahia@gmail.com>
157 Lucas Melo <lucas@colivre.coop.br> 189 Lucas Melo <lucas@colivre.coop.br>
158 Lucas Melo <lucaspradomelo@gmail.com> 190 Lucas Melo <lucaspradomelo@gmail.com>
  191 +Luciano <lucianopcbr@gmail.com>
  192 +Luciano Prestes Cavalcanti <lucianopcbr@gmail.com>
159 Luis David Aguilar Carlos <ludwig9003@gmail.com> 193 Luis David Aguilar Carlos <ludwig9003@gmail.com>
  194 +Luiz Fernando de Freitas Matos <luiz@luizff.matos@gmail.com>
  195 +Marcos Ramos <ms.ramos@outlook.com>
160 Martín Olivera <molivera@solar.org.ar> 196 Martín Olivera <molivera@solar.org.ar>
  197 +Michal Čihař <michal@cihar.com>
161 Moises Machado <moises@colivre.coop.br> 198 Moises Machado <moises@colivre.coop.br>
162 Naíla Alves <naila@colivre.coop.br> 199 Naíla Alves <naila@colivre.coop.br>
163 Nanda Lopes <nanda.listas+psl@gmail.com> 200 Nanda Lopes <nanda.listas+psl@gmail.com>
  201 +Parley Martins <parleypachecomartins@gmail.com>
164 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>
165 Paulo Meirelles + Alessandro Palmeira <paulo@softwarelivre.org> 203 Paulo Meirelles + Alessandro Palmeira <paulo@softwarelivre.org>
166 Paulo Meirelles + Carlos Morais <paulo@softwarelivre.org> 204 Paulo Meirelles + Carlos Morais <paulo@softwarelivre.org>
@@ -183,20 +221,28 @@ Rafael Reggiani Manzo + João M. M. da Silva &lt;rr.manzo@gmail.com&gt; @@ -183,20 +221,28 @@ Rafael Reggiani Manzo + João M. M. da Silva &lt;rr.manzo@gmail.com&gt;
183 Rafael Reggiani Manzo <rr.manzo@gmail.com> 221 Rafael Reggiani Manzo <rr.manzo@gmail.com>
184 Raphaël Rousseau <raph@r4f.org> 222 Raphaël Rousseau <raph@r4f.org>
185 Raquel Lira <raquel.lira@gmail.com> 223 Raquel Lira <raquel.lira@gmail.com>
  224 +Raquel <rcordioli@gmail.com>
186 Renan Teruo + Caio Salgado <renanteruoc@gmail.com> 225 Renan Teruo + Caio Salgado <renanteruoc@gmail.com>
187 Renan Teruoc + Diego Araujo <renanteruoc@gmail.com> 226 Renan Teruoc + Diego Araujo <renanteruoc@gmail.com>
188 Renan Teruo + Diego Araujo <renanteruoc@gmail.com> 227 Renan Teruo + Diego Araujo <renanteruoc@gmail.com>
189 Renan Teruo + Diego Araújo <renanteruoc@gmail.com> 228 Renan Teruo + Diego Araújo <renanteruoc@gmail.com>
190 Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com> 229 Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com>
191 Renan Teruo + Rafael Manzo <renanteruoc@gmail.com> 230 Renan Teruo + Rafael Manzo <renanteruoc@gmail.com>
192 -Rodrigo Souto <diguliu@gmail.com> 231 +Rodrigo Souto + Ana Losnak + Daniel Bucher + Caio Almeida + Leandro Nunes + Daniela Feitosa + Mariel Zasso <noosfero-br@listas.softwarelivre.org>
193 Rodrigo Souto <rodrigo@colivre.coop.br> 232 Rodrigo Souto <rodrigo@colivre.coop.br>
194 Ronny Kursawe <kursawe.ronny@googlemail.com> 233 Ronny Kursawe <kursawe.ronny@googlemail.com>
195 root <root@debian.sdr.serpro> 234 root <root@debian.sdr.serpro>
196 Samuel R. C. Vale <srcvale@holoscopio.com> 235 Samuel R. C. Vale <srcvale@holoscopio.com>
  236 +Tallys Martins <tallysmartins@gmail.com>
  237 +tallys <tallys@tallys.(none)>
  238 +Thiago Zoroastro <thiago.zoroastro@bol.com.br>
  239 +Valessio Brito <contato@valessiobrito.com.br>
  240 +Valessio Brito <contato@valessiobrito.info>
197 Valessio Brito <valessio@gmail.com> 241 Valessio Brito <valessio@gmail.com>
198 vfcosta <vfcosta@gmail.com> 242 vfcosta <vfcosta@gmail.com>
  243 +Victor Carvalho <victorhugodf.ac@gmail.com>
199 Victor Costa <vfcosta@gmail.com> 244 Victor Costa <vfcosta@gmail.com>
  245 +Victor Hugo Alves de Carvalho <victorhugodf.ac@gmail.com>
200 Vinicius Cubas Brand <viniciuscb@gmail.com> 246 Vinicius Cubas Brand <viniciuscb@gmail.com>
201 Visita <visita@debian.(none)> 247 Visita <visita@debian.(none)>
202 Yann Lugrin <yann.lugrin@liquid-concept.ch> 248 Yann Lugrin <yann.lugrin@liquid-concept.ch>
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 -gem 'fast_gettext'  
4 -gem 'acts-as-taggable-on'  
5 -gem 'prototype-rails'  
6 -gem 'prototype_legacy_helper', '0.0.0', :path => 'vendor/prototype_legacy_helper'  
7 -gem 'rails_autolink'  
8 -gem 'pg'  
9 -gem 'rmagick'  
10 -gem 'RedCloth'  
11 -gem 'will_paginate'  
12 -gem 'ruby-feedparser'  
13 -gem 'daemons'  
14 -gem 'thin'  
15 -gem 'hpricot'  
16 -gem 'nokogiri' 2 +gem 'rails', '~> 3.2.21'
  3 +gem 'minitest', '~> 3.2.0'
  4 +gem 'fast_gettext', '~> 0.6.8'
  5 +gem 'acts-as-taggable-on', '~> 3.0.2'
  6 +gem 'prototype-rails', '~> 3.2.1'
  7 +gem 'prototype_legacy_helper', '0.0.0', :path => 'vendor/prototype_legacy_helper'
  8 +gem 'rails_autolink', '~> 1.1.5'
  9 +gem 'pg', '~> 0.13.2'
  10 +gem 'rmagick', '~> 2.13.1'
  11 +gem 'RedCloth', '~> 4.2.9'
  12 +gem 'will_paginate', '~> 3.0.3'
  13 +gem 'ruby-feedparser', '~> 0.7'
  14 +gem 'daemons', '~> 1.1.5'
  15 +gem 'thin', '~> 1.3.1'
  16 +gem 'hpricot', '~> 0.8.6'
  17 +gem 'nokogiri', '~> 1.5.5'
17 gem 'rake', :require => false 18 gem 'rake', :require => false
18 -gem 'rest-client'  
19 -gem 'exception_notification' 19 +gem 'rest-client', '~> 1.6.7'
  20 +gem 'exception_notification', '~> 4.0.1'
  21 +gem 'gettext', '~> 2.2.1', :require => false, :group => :development
  22 +gem 'locale', '~> 2.0.5'
  23 +
  24 +gem 'whenever', :require => false
20 25
21 # 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),
22 # with their GEM names (not the Debian package names) 27 # with their GEM names (not the Debian package names)
23 28
24 group :production do 29 group :production do
25 - gem 'dalli' 30 + gem 'dalli', '~> 2.7.0'
26 end 31 end
27 32
28 group :test do 33 group :test do
29 - gem 'rspec'  
30 - gem 'rspec-rails'  
31 - gem 'mocha', :require => false 34 + gem 'rspec', '~> 2.10.0'
  35 + gem 'rspec-rails', '~> 2.10.1'
  36 + gem 'mocha', '~> 1.1.0', :require => false
32 end 37 end
33 38
34 group :cucumber do 39 group :cucumber do
35 - gem 'cucumber-rails', :require => false  
36 - gem 'capybara'  
37 - gem 'cucumber'  
38 - gem 'database_cleaner'  
39 - gem 'selenium-webdriver' 40 + gem 'cucumber-rails', '~> 1.0.6', :require => false
  41 + gem 'capybara', '~> 2.1.0'
  42 + gem 'cucumber', '~> 1.0.6'
  43 + gem 'database_cleaner', '~> 1.2.0'
  44 + gem 'selenium-webdriver', '~> 2.39.0'
40 end 45 end
41 46
42 -# include plugin gemfiles  
43 -Dir.glob(File.join('config', 'plugins', '*')).each do |plugin|  
44 - plugin_gemfile = File.join(plugin, 'Gemfile')  
45 - 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)
46 end 52 end
Gemfile.lock
@@ -1,191 +0,0 @@ @@ -1,191 +0,0 @@
1 -PATH  
2 - remote: vendor/prototype_legacy_helper  
3 - specs:  
4 - prototype_legacy_helper (0.0.0)  
5 -  
6 -GEM  
7 - remote: https://rubygems.org/  
8 - specs:  
9 - RedCloth (4.2.9)  
10 - actionmailer (3.2.6)  
11 - actionpack (= 3.2.6)  
12 - mail (~> 2.4.4)  
13 - actionpack (3.2.6)  
14 - activemodel (= 3.2.6)  
15 - activesupport (= 3.2.6)  
16 - builder (~> 3.0.0)  
17 - erubis (~> 2.7.0)  
18 - journey (~> 1.0.1)  
19 - rack (~> 1.4.0)  
20 - rack-cache (~> 1.2)  
21 - rack-test (~> 0.6.1)  
22 - sprockets (~> 2.1.3)  
23 - activemodel (3.2.6)  
24 - activesupport (= 3.2.6)  
25 - builder (~> 3.0.0)  
26 - activerecord (3.2.6)  
27 - activemodel (= 3.2.6)  
28 - activesupport (= 3.2.6)  
29 - arel (~> 3.0.2)  
30 - tzinfo (~> 0.3.29)  
31 - activeresource (3.2.6)  
32 - activemodel (= 3.2.6)  
33 - activesupport (= 3.2.6)  
34 - activesupport (3.2.6)  
35 - i18n (~> 0.6)  
36 - multi_json (~> 1.0)  
37 - acts-as-taggable-on (3.0.2)  
38 - rails (>= 3, < 5)  
39 - arel (3.0.2)  
40 - builder (3.0.0)  
41 - capybara (2.1.0)  
42 - mime-types (>= 1.16)  
43 - nokogiri (>= 1.3.3)  
44 - rack (>= 1.0.0)  
45 - rack-test (>= 0.5.4)  
46 - xpath (~> 2.0)  
47 - childprocess (0.3.3)  
48 - ffi (~> 1.0.6)  
49 - cucumber (1.0.6)  
50 - builder (>= 2.1.2)  
51 - diff-lcs (>= 1.1.2)  
52 - gherkin (~> 2.4.18)  
53 - json (>= 1.4.6)  
54 - term-ansicolor (>= 1.0.6)  
55 - cucumber-rails (1.0.6)  
56 - capybara (>= 1.1.1)  
57 - cucumber (>= 1.0.6)  
58 - nokogiri (>= 1.5.0)  
59 - daemons (1.1.5)  
60 - dalli (2.7.0)  
61 - database_cleaner (1.2.0)  
62 - diff-lcs (1.1.3)  
63 - erubis (2.7.0)  
64 - eventmachine (0.12.10)  
65 - exception_notification (4.0.1)  
66 - actionmailer (>= 3.0.4)  
67 - activesupport (>= 3.0.4)  
68 - fast_gettext (0.6.8)  
69 - ffi (1.0.11)  
70 - gherkin (2.4.21)  
71 - json (>= 1.4.6)  
72 - hike (1.2.1)  
73 - hpricot (0.8.6)  
74 - i18n (0.6.0)  
75 - journey (1.0.3)  
76 - json (1.7.3)  
77 - mail (2.4.4)  
78 - i18n (>= 0.4.0)  
79 - mime-types (~> 1.16)  
80 - treetop (~> 1.4.8)  
81 - metaclass (0.0.1)  
82 - mime-types (1.19)  
83 - mocha (0.11.3)  
84 - metaclass (~> 0.0.1)  
85 - multi_json (1.3.6)  
86 - nokogiri (1.5.5)  
87 - pg (0.13.2)  
88 - polyglot (0.3.3)  
89 - prototype-rails (3.2.1)  
90 - rails (~> 3.2)  
91 - rack (1.4.1)  
92 - rack-cache (1.2)  
93 - rack (>= 0.4)  
94 - rack-ssl (1.3.2)  
95 - rack  
96 - rack-test (0.6.1)  
97 - rack (>= 1.0)  
98 - rails (3.2.6)  
99 - actionmailer (= 3.2.6)  
100 - actionpack (= 3.2.6)  
101 - activerecord (= 3.2.6)  
102 - activeresource (= 3.2.6)  
103 - activesupport (= 3.2.6)  
104 - bundler (~> 1.0)  
105 - railties (= 3.2.6)  
106 - rails_autolink (1.1.5)  
107 - rails (> 3.1)  
108 - railties (3.2.6)  
109 - actionpack (= 3.2.6)  
110 - activesupport (= 3.2.6)  
111 - rack-ssl (~> 1.3.2)  
112 - rake (>= 0.8.7)  
113 - rdoc (~> 3.4)  
114 - thor (>= 0.14.6, < 2.0)  
115 - rake (0.9.2.2)  
116 - rdoc (3.9.4)  
117 - rest-client (1.6.7)  
118 - mime-types (>= 1.16)  
119 - rmagick (2.13.1)  
120 - rspec (2.10.0)  
121 - rspec-core (~> 2.10.0)  
122 - rspec-expectations (~> 2.10.0)  
123 - rspec-mocks (~> 2.10.0)  
124 - rspec-core (2.10.1)  
125 - rspec-expectations (2.10.0)  
126 - diff-lcs (~> 1.1.3)  
127 - rspec-mocks (2.10.1)  
128 - rspec-rails (2.10.1)  
129 - actionpack (>= 3.0)  
130 - activesupport (>= 3.0)  
131 - railties (>= 3.0)  
132 - rspec (~> 2.10.0)  
133 - ruby-feedparser (0.7)  
134 - rubyzip (1.1.2)  
135 - selenium-webdriver (2.39.0)  
136 - childprocess (>= 0.2.5)  
137 - multi_json (~> 1.0)  
138 - rubyzip (~> 1.0)  
139 - websocket (~> 1.0.4)  
140 - sprockets (2.1.3)  
141 - hike (~> 1.2)  
142 - multi_json (~> 1.0)  
143 - rack (~> 1.0)  
144 - tilt (~> 1.1, != 1.3.0)  
145 - term-ansicolor (1.0.7)  
146 - thin (1.3.1)  
147 - daemons (>= 1.0.9)  
148 - eventmachine (>= 0.12.6)  
149 - rack (>= 1.0.0)  
150 - thor (0.15.3)  
151 - tilt (1.3.3)  
152 - treetop (1.4.10)  
153 - polyglot  
154 - polyglot (>= 0.3.1)  
155 - tzinfo (0.3.33)  
156 - websocket (1.0.7)  
157 - will_paginate (3.0.3)  
158 - xpath (2.0.0)  
159 - nokogiri (~> 1.3)  
160 -  
161 -PLATFORMS  
162 - ruby  
163 -  
164 -DEPENDENCIES  
165 - RedCloth  
166 - acts-as-taggable-on  
167 - capybara  
168 - cucumber  
169 - cucumber-rails  
170 - daemons  
171 - dalli  
172 - database_cleaner  
173 - exception_notification  
174 - fast_gettext  
175 - hpricot  
176 - mocha  
177 - nokogiri  
178 - pg  
179 - prototype-rails  
180 - prototype_legacy_helper (= 0.0.0)!  
181 - rails  
182 - rails_autolink  
183 - rake  
184 - rest-client  
185 - rmagick  
186 - rspec  
187 - rspec-rails  
188 - ruby-feedparser  
189 - selenium-webdriver  
190 - thin  
191 - will_paginate  
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 +
@@ -186,8 +186,8 @@ Apache instalation @@ -186,8 +186,8 @@ Apache instalation
186 186
187 # apt-get install apache2 187 # apt-get install apache2
188 188
189 -Apache configuration  
190 --------------------- 189 +Configuration - noosfero at /
  190 +-----------------------------
191 191
192 First you have to enable the following some apache modules: 192 First you have to enable the following some apache modules:
193 193
@@ -257,6 +257,62 @@ Now restart your apache server (as root): @@ -257,6 +257,62 @@ Now restart your apache server (as root):
257 257
258 # invoke-rc.d apache2 restart 258 # invoke-rc.d apache2 restart
259 259
  260 +Configuration - noosfero at a /subdirectory
  261 +-------------------------------------------
  262 +
  263 +This section describes how to configure noosfero at a subdirectory, what is
  264 +specially useful when you want Noosfero to share a domain name with other
  265 +applications. For example you can host noosfero at yourdomain.com/social, a
  266 +webmail application at yourdomain.com/webmail, and have a static HTML website
  267 +at yourdomain.com/.
  268 +
  269 +**NOTE:** Some plugins might not work well with this setting. Before deploying
  270 +this setting, make sure you test that everything you need works properly with
  271 +it.
  272 +
  273 +The configuration is similar to the main configuration instructions, except for
  274 +the following points. In the description below, replace '/subdirectory' with
  275 +the actual subdirectory you want.
  276 +
  277 +1) add a `prefix: /subdirectory` line to your thin configuration file (thin.yml).
  278 +
  279 +1.1) remember to restart the noosfero application server whenever you make
  280 +changes to that configuration file.
  281 +
  282 + # service noosfero restart
  283 +
  284 +2) add a line saying `export RAILS_RELATIVE_URL_ROOT=/subdirectory` to
  285 +/etc/default/noosfero (you can create it with just this line if it does not
  286 +exist already).
  287 +
  288 +3) You should add the following apache configuration to an existing virtual
  289 +host (plus the `<Proxy balancer://noosfero>` section as displayed above):
  290 +
  291 +```
  292 +Alias /subdirectory /path/to/noosfero/public
  293 +<Directory "/path/to/noosfero/public">
  294 + Options FollowSymLinks
  295 + AllowOverride None
  296 + Order Allow,Deny
  297 + Allow from all
  298 +
  299 + Include /path/to/noosfero/etc/noosfero/apache/cache.conf
  300 +
  301 + RewriteEngine On
  302 + RewriteBase /subdirectory
  303 + # Rewrite index to check for static index.html
  304 + RewriteRule ^$ index.html [QSA]
  305 + # Rewrite to check for Rails cached page
  306 + RewriteRule ^([^.]+)$ $1.html [QSA]
  307 + RewriteCond %{REQUEST_FILENAME} !-f
  308 + RewriteRule ^(.*)$ http://localhost:3000%{REQUEST_URI} [P,QSA,L]
  309 +</Directory>
  310 +```
  311 +
  312 +3.1) remember to reload the apache server whenever any apache configuration
  313 +file changes.
  314 +
  315 + # sudo service apache2 reload
260 316
261 Enabling exception notifications 317 Enabling exception notifications
262 ================================ 318 ================================
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  
@@ -6,3 +6,13 @@ @@ -6,3 +6,13 @@
6 require File.expand_path('../config/application', __FILE__) 6 require File.expand_path('../config/application', __FILE__)
7 7
8 Noosfero::Application.load_tasks 8 Noosfero::Application.load_tasks
  9 +
  10 +[
  11 + "baseplugins/*/{tasks,lib/tasks,rails/tasks}/**/*.rake",
  12 + "config/plugins/*/{tasks,lib/tasks,rails/tasks}/**/*.rake",
  13 + "config/plugins/*/vendor/plugins/*/{tasks,lib/tasks,rails/tasks}/**/*.rake",
  14 +].map do |pattern|
  15 + Dir.glob(pattern).sort
  16 +end.flatten.each do |taskfile|
  17 + load taskfile
  18 +end
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 3
4 VAGRANTFILE_API_VERSION = "2" 4 VAGRANTFILE_API_VERSION = "2"
5 Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 5 Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
6 - config.vm.box = "debian-wheezy" 6 + config.vm.box = ENV.fetch('VAGRANT_BOX', "debian-wheezy")
7 config.vm.network :forwarded_port, host: 3000, guest: 3000 7 config.vm.network :forwarded_port, host: 3000, guest: 3000
8 config.vm.provision :shell do |shell| 8 config.vm.provision :shell do |shell|
9 shell.inline = 'su vagrant -c /vagrant/script/vagrant' 9 shell.inline = 'su vagrant -c /vagrant/script/vagrant'
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/categories_controller.rb
@@ -45,9 +45,11 @@ class CategoriesController &lt; AdminController @@ -45,9 +45,11 @@ class CategoriesController &lt; AdminController
45 if request.post? 45 if request.post?
46 @category.update_attributes!(params[:category]) 46 @category.update_attributes!(params[:category])
47 @saved = true 47 @saved = true
  48 + session[:notice] = _("Category %s saved." % @category.name)
48 redirect_to :action => 'index' 49 redirect_to :action => 'index'
49 end 50 end
50 rescue Exception => e 51 rescue Exception => e
  52 + session[:notice] = _('Could not save category.')
51 render :action => 'edit' 53 render :action => 'edit'
52 end 54 end
53 end 55 end
app/controllers/admin/environment_design_controller.rb
@@ -3,9 +3,7 @@ class EnvironmentDesignController &lt; BoxOrganizerController @@ -3,9 +3,7 @@ class EnvironmentDesignController &lt; BoxOrganizerController
3 protect 'edit_environment_design', :environment 3 protect 'edit_environment_design', :environment
4 4
5 def available_blocks 5 def available_blocks
6 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
7 - # the Noosfero core soon, see ActionItem3045  
8 - @available_blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ] 6 + @available_blocks ||= [ ArticleBlock, LoginBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ]
9 @available_blocks += plugins.dispatch(:extra_blocks, :type => Environment) 7 @available_blocks += plugins.dispatch(:extra_blocks, :type => Environment)
10 end 8 end
11 9
app/controllers/admin/features_controller.rb
@@ -51,4 +51,10 @@ class FeaturesController &lt; AdminController @@ -51,4 +51,10 @@ class FeaturesController &lt; AdminController
51 redirect_to :action => 'manage_fields' 51 redirect_to :action => 'manage_fields'
52 end 52 end
53 53
  54 + def search_members
  55 + arg = params[:q].downcase
  56 + result = environment.people.find(:all, :conditions => ['LOWER(name) LIKE ? OR identifier LIKE ?', "%#{arg}%", "%#{arg}%"])
  57 + render :text => prepare_to_token_input(result).to_json
  58 + end
  59 +
54 end 60 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
@@ -7,6 +7,12 @@ class ApplicationController &lt; ActionController::Base @@ -7,6 +7,12 @@ class ApplicationController &lt; ActionController::Base
7 before_filter :detect_stuff_by_domain 7 before_filter :detect_stuff_by_domain
8 before_filter :init_noosfero_plugins 8 before_filter :init_noosfero_plugins
9 before_filter :allow_cross_domain_access 9 before_filter :allow_cross_domain_access
  10 + before_filter :login_required, :if => :private_environment?
  11 + before_filter :verify_members_whitelist, :if => [:private_environment?, :user]
  12 +
  13 + def verify_members_whitelist
  14 + render_access_denied unless user.is_admin? || environment.in_whitelist?(user)
  15 + end
10 16
11 after_filter :set_csrf_cookie 17 after_filter :set_csrf_cookie
12 18
@@ -34,7 +40,7 @@ class ApplicationController &lt; ActionController::Base @@ -34,7 +40,7 @@ class ApplicationController &lt; ActionController::Base
34 40
35 theme_layout = theme_option(:layout) 41 theme_layout = theme_option(:layout)
36 if theme_layout 42 if theme_layout
37 - theme_view_file('layouts/'+theme_layout) || theme_layout 43 + (theme_view_file('layouts/'+theme_layout) || theme_layout).to_s
38 else 44 else
39 'application' 45 'application'
40 end 46 end
@@ -121,6 +127,9 @@ class ApplicationController &lt; ActionController::Base @@ -121,6 +127,9 @@ class ApplicationController &lt; ActionController::Base
121 127
122 # TODO: move this logic somewhere else (Domain class?) 128 # TODO: move this logic somewhere else (Domain class?)
123 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 +
124 @domain = Domain.find_by_name(request.host) 133 @domain = Domain.find_by_name(request.host)
125 if @domain.nil? 134 if @domain.nil?
126 @environment = Environment.default 135 @environment = Environment.default
@@ -174,17 +183,19 @@ class ApplicationController &lt; ActionController::Base @@ -174,17 +183,19 @@ class ApplicationController &lt; ActionController::Base
174 end 183 end
175 end 184 end
176 185
177 - def find_by_contents(asset, scope, query, paginate_options={:page => 1}, options={})  
178 - plugins.dispatch_first(:find_by_contents, asset, scope, query, paginate_options, options) ||  
179 - fallback_find_by_contents(asset, scope, query, paginate_options, options)  
180 - end 186 + include SearchTermHelper
181 187
182 - 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
183 193
184 - def fallback_find_by_contents(asset, scope, query, paginate_options, options)  
185 - scope = scope.like_search(query) unless query.blank?  
186 - scope = scope.send(options[:filter]) unless options[:filter].blank?  
187 - {:results => scope.paginate(paginate_options)} 194 + def find_suggestions(query, context, asset, options={})
  195 + plugins.dispatch_first(:find_suggestions, query, context, asset, options)
188 end 196 end
189 197
  198 + def private_environment?
  199 + @environment.enabled?(:restrict_to_members)
  200 + end
190 end 201 end
app/controllers/my_profile/cms_controller.rb
@@ -4,6 +4,12 @@ class CmsController &lt; MyProfileController @@ -4,6 +4,12 @@ class CmsController &lt; MyProfileController
4 4
5 include ArticleHelper 5 include ArticleHelper
6 6
  7 + def search_tags
  8 + arg = params[:term].downcase
  9 + result = ActsAsTaggableOn::Tag.find(:all, :conditions => ['LOWER(name) LIKE ?', "%#{arg}%"])
  10 + render :text => prepare_to_token_input_by_label(result).to_json, :content_type => 'application/json'
  11 + end
  12 +
7 def self.protect_if(*args) 13 def self.protect_if(*args)
8 before_filter(*args) do |c| 14 before_filter(*args) do |c|
9 user, profile = c.send(:user), c.send(:profile) 15 user, profile = c.send(:user), c.send(:profile)
@@ -17,6 +23,9 @@ class CmsController &lt; MyProfileController @@ -17,6 +23,9 @@ class CmsController &lt; MyProfileController
17 end 23 end
18 24
19 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
20 29
21 protect_if :only => :upload_files do |c, user, profile| 30 protect_if :only => :upload_files do |c, user, profile|
22 article_id = c.params[:parent_id] 31 article_id = c.params[:parent_id]
@@ -24,7 +33,7 @@ class CmsController &lt; MyProfileController @@ -24,7 +33,7 @@ class CmsController &lt; MyProfileController
24 (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)))
25 end 34 end
26 35
27 - 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|
28 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))
29 end 38 end
30 39
@@ -34,7 +43,7 @@ class CmsController &lt; MyProfileController @@ -34,7 +43,7 @@ class CmsController &lt; MyProfileController
34 (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)))
35 end 44 end
36 45
37 - protect_if :only => [:destroy, :publish] do |c, user, profile| 46 + protect_if :only => :destroy do |c, user, profile|
38 profile.articles.find(c.params[:id]).allow_post_content?(user) 47 profile.articles.find(c.params[:id]).allow_post_content?(user)
39 end 48 end
40 49
@@ -111,7 +120,7 @@ class CmsController &lt; MyProfileController @@ -111,7 +120,7 @@ class CmsController &lt; MyProfileController
111 @success_back_to = params[:success_back_to] 120 @success_back_to = params[:success_back_to]
112 # user must choose an article type first 121 # user must choose an article type first
113 122
114 - @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?
115 record_coming 124 record_coming
116 @type = params[:type] 125 @type = params[:type]
117 if @type.blank? 126 if @type.blank?
@@ -157,7 +166,10 @@ class CmsController &lt; MyProfileController @@ -157,7 +166,10 @@ class CmsController &lt; MyProfileController
157 if continue 166 if continue
158 redirect_to :action => 'edit', :id => @article 167 redirect_to :action => 'edit', :id => @article
159 else 168 else
160 - 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
161 end 173 end
162 return 174 return
163 end 175 end
@@ -168,6 +180,8 @@ class CmsController &lt; MyProfileController @@ -168,6 +180,8 @@ class CmsController &lt; MyProfileController
168 180
169 post_only :set_home_page 181 post_only :set_home_page
170 def set_home_page 182 def set_home_page
  183 + return render_access_denied unless user.can_change_homepage?
  184 +
171 article = params[:id].nil? ? nil : profile.articles.find(params[:id]) 185 article = params[:id].nil? ? nil : profile.articles.find(params[:id])
172 profile.update_attribute(:home_page, article) 186 profile.update_attribute(:home_page, article)
173 187
@@ -206,6 +220,7 @@ class CmsController &lt; MyProfileController @@ -206,6 +220,7 @@ class CmsController &lt; MyProfileController
206 if @errors.any? 220 if @errors.any?
207 render :action => 'upload_files', :parent_id => @parent_id 221 render :action => 'upload_files', :parent_id => @parent_id
208 else 222 else
  223 + session[:notice] = _('File(s) successfully uploaded')
209 if @back_to 224 if @back_to
210 redirect_to @back_to 225 redirect_to @back_to
211 elsif @parent 226 elsif @parent
@@ -221,7 +236,7 @@ class CmsController &lt; MyProfileController @@ -221,7 +236,7 @@ class CmsController &lt; MyProfileController
221 @article = profile.articles.find(params[:id]) 236 @article = profile.articles.find(params[:id])
222 if request.post? 237 if request.post?
223 @article.destroy 238 @article.destroy
224 - session[:notice] = _("\"#{@article.name}\" was removed.") 239 + session[:notice] = _("\"%s\" was removed." % @article.name)
225 referer = Rails.application.routes.recognize_path URI.parse(request.referer).path rescue nil 240 referer = Rails.application.routes.recognize_path URI.parse(request.referer).path rescue nil
226 if referer and referer[:controller] == 'cms' and referer[:action] != 'edit' 241 if referer and referer[:controller] == 'cms' and referer[:action] != 'edit'
227 redirect_to referer 242 redirect_to referer
@@ -247,28 +262,53 @@ class CmsController &lt; MyProfileController @@ -247,28 +262,53 @@ class CmsController &lt; MyProfileController
247 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' }
248 end 263 end
249 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 +
250 def publish 269 def publish
251 @article = profile.articles.find(params[:id]) 270 @article = profile.articles.find(params[:id])
252 record_coming 271 record_coming
253 - @groups = profile.memberships - [profile]  
254 - @marked_groups = []  
255 - groups_ids = profile.memberships.map{|m|m.id.to_s}  
256 - @marked_groups = params[:marked_groups].map do |key, item|  
257 - if groups_ids.include?(item[:group_id])  
258 - 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
259 end 281 end
260 - end.compact unless params[:marked_groups].nil? 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
  289 + end
  290 + end
  291 + end
  292 +
  293 + def publish_on_communities
261 if request.post? 294 if request.post?
  295 + @back_to = params[:back_to]
  296 + @article = profile.articles.find(params[:id])
262 @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)
263 if @marked_groups.empty? 301 if @marked_groups.empty?
  302 + redirect_to @back_to
264 return session[:notice] = _("Select some group to publish your article") 303 return session[:notice] = _("Select some group to publish your article")
265 end 304 end
266 @marked_groups.each do |item| 305 @marked_groups.each do |item|
267 - 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)
268 begin 307 begin
269 - task.finish unless item[:group].moderated_articles? 308 + task.finish unless item.moderated_articles?
270 rescue Exception => ex 309 rescue Exception => ex
271 - @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
272 end 312 end
273 end 313 end
274 if @failed.blank? 314 if @failed.blank?
@@ -278,23 +318,27 @@ class CmsController &lt; MyProfileController @@ -278,23 +318,27 @@ class CmsController &lt; MyProfileController
278 else 318 else
279 redirect_to @article.view_url 319 redirect_to @article.view_url
280 end 320 end
  321 + else
  322 + session[:notice] = _("Some of your publish requests couldn't be sent.")
  323 + render :action => 'publish'
281 end 324 end
282 end 325 end
283 end 326 end
284 327
285 def publish_on_portal_community 328 def publish_on_portal_community
286 - @article = profile.articles.find(params[:id])  
287 if request.post? 329 if request.post?
288 - if environment.portal_community 330 + @article = profile.articles.find(params[:id])
  331 + if environment.portal_enabled
289 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)
290 begin 333 begin
291 task.finish unless environment.portal_community.moderated_articles? 334 task.finish unless environment.portal_community.moderated_articles?
292 - flash[:notice] = _("Your publish request was sent successfully") 335 + session[:notice] = _("Your publish request was sent successfully")
293 rescue 336 rescue
294 - flash[:error] = _("Your publish request couldn't be sent.") 337 + session[:notice] = _("Your publish request couldn't be sent.")
  338 + task.cancel
295 end 339 end
296 else 340 else
297 - flash[:notice] = _("There is no portal community to publish your article.") 341 + session[:notice] = _("There is no portal community to publish your article.")
298 end 342 end
299 343
300 if @back_to 344 if @back_to
@@ -322,7 +366,7 @@ class CmsController &lt; MyProfileController @@ -322,7 +366,7 @@ class CmsController &lt; MyProfileController
322 366
323 def search 367 def search
324 query = params[:q] 368 query = params[:q]
325 - results = find_by_contents(:uploaded_files, profile.files.published, query)[:results] 369 + results = find_by_contents(:uploaded_files, profile, profile.files.published, query)[:results]
326 render :text => article_list_to_json(results), :content_type => 'application/json' 370 render :text => article_list_to_json(results), :content_type => 'application/json'
327 end 371 end
328 372
@@ -333,15 +377,26 @@ class CmsController &lt; MyProfileController @@ -333,15 +377,26 @@ class CmsController &lt; MyProfileController
333 end 377 end
334 378
335 def media_upload 379 def media_upload
336 - files_uploaded = []  
337 parent = check_parent(params[:parent_id]) 380 parent = check_parent(params[:parent_id])
338 - files = [:file1,:file2, :file3].map { |f| params[f] }.compact  
339 if request.post? 381 if request.post?
340 - files.each do |file|  
341 - 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
342 end 387 end
343 end 388 end
344 - 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)
345 end 400 end
346 401
347 protected 402 protected
@@ -436,4 +491,36 @@ class CmsController &lt; MyProfileController @@ -436,4 +491,36 @@ class CmsController &lt; MyProfileController
436 end 491 end
437 end 492 end
438 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 +
439 end 526 end
app/controllers/my_profile/friends_controller.rb 100644 → 100755
@@ -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,11 +17,35 @@ class FriendsController &lt; MyProfileController @@ -16,11 +17,35 @@ 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
22 def per_page 47 def per_page
23 - 10 48 + 12
24 end 49 end
25 end 50 end
26 def per_page 51 def per_page
app/controllers/my_profile/memberships_controller.rb
@@ -20,9 +20,54 @@ class MembershipsController &lt; MyProfileController @@ -20,9 +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 - redirect_to @back_to 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
  30 + session[:notice] = _('Your new community creation request will be evaluated by an administrator. You will be notified.')
  31 + redirect_to @back_to
  32 + end
25 return 33 return
26 end 34 end
27 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 +
28 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/my_profile/tasks_controller.rb
@@ -4,6 +4,7 @@ class TasksController &lt; MyProfileController @@ -4,6 +4,7 @@ class TasksController &lt; MyProfileController
4 4
5 def index 5 def index
6 @filter = params[:filter_type].blank? ? nil : params[:filter_type] 6 @filter = params[:filter_type].blank? ? nil : params[:filter_type]
  7 + @task_types = Task.pending_types_for(profile)
7 @tasks = Task.to(profile).without_spam.pending.of(@filter).order_by('created_at', 'asc').paginate(:per_page => Task.per_page, :page => params[:page]) 8 @tasks = Task.to(profile).without_spam.pending.of(@filter).order_by('created_at', 'asc').paginate(:per_page => Task.per_page, :page => params[:page])
8 @failed = params ? params[:failed] : {} 9 @failed = params ? params[:failed] : {}
9 end 10 end
app/controllers/public/account_controller.rb
@@ -15,11 +15,23 @@ class AccountController &lt; ApplicationController @@ -15,11 +15,23 @@ class AccountController &lt; ApplicationController
15 15
16 def activate 16 def activate
17 @user = User.find_by_activation_code(params[:activation_code]) if params[:activation_code] 17 @user = User.find_by_activation_code(params[:activation_code]) if params[:activation_code]
18 - if @user and @user.activate  
19 - @message = _("Your account has been activated, now you can log in!")  
20 - check_redirection  
21 - session[:join] = params[:join] unless params[:join].blank?  
22 - render :action => 'login', :userlogin => @user.login 18 + if @user
  19 + unless @user.environment.enabled?('admin_must_approve_new_users')
  20 + if @user.activate
  21 + @message = _("Your account has been activated, now you can log in!")
  22 + check_redirection
  23 + session[:join] = params[:join] unless params[:join].blank?
  24 + render :action => 'login', :userlogin => @user.login
  25 + end
  26 + else
  27 + if @user.create_moderate_task
  28 + session[:notice] = _('Thanks for registering. The administrators were notified.')
  29 + @register_pending = true
  30 + @user.activation_code = nil
  31 + @user.save!
  32 + redirect_to :controller => :home
  33 + end
  34 + end
23 else 35 else
24 session[:notice] = _("It looks like you're trying to activate an account. Perhaps have already activated this account?") 36 session[:notice] = _("It looks like you're trying to activate an account. Perhaps have already activated this account?")
25 redirect_to :controller => :home 37 redirect_to :controller => :home
@@ -70,10 +82,12 @@ class AccountController &lt; ApplicationController @@ -70,10 +82,12 @@ class AccountController &lt; ApplicationController
70 if @plugins.dispatch(:allow_user_registration).include?(false) 82 if @plugins.dispatch(:allow_user_registration).include?(false)
71 redirect_back_or_default(:controller => 'home') 83 redirect_back_or_default(:controller => 'home')
72 session[:notice] = _("This environment doesn't allow user registration.") 84 session[:notice] = _("This environment doesn't allow user registration.")
  85 + return
73 end 86 end
74 87
75 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]
76 89
  90 + # Tranforming to boolean
77 @block_bot = !!session[:may_be_a_bot] 91 @block_bot = !!session[:may_be_a_bot]
78 @invitation_code = params[:invitation_code] 92 @invitation_code = params[:invitation_code]
79 begin 93 begin
@@ -85,6 +99,7 @@ class AccountController &lt; ApplicationController @@ -85,6 +99,7 @@ class AccountController &lt; ApplicationController
85 @user.return_to = session[:return_to] 99 @user.return_to = session[:return_to]
86 @person = Person.new(params[:profile_data]) 100 @person = Person.new(params[:profile_data])
87 @person.environment = @user.environment 101 @person.environment = @user.environment
  102 +
88 if request.post? 103 if request.post?
89 if may_be_a_bot 104 if may_be_a_bot
90 set_signup_start_time_for_now 105 set_signup_start_time_for_now
@@ -103,12 +118,21 @@ class AccountController &lt; ApplicationController @@ -103,12 +118,21 @@ class AccountController &lt; ApplicationController
103 invitation.update_attributes!({:friend => @user.person}) 118 invitation.update_attributes!({:friend => @user.person})
104 invitation.finish 119 invitation.finish
105 end 120 end
  121 +
  122 + unless params[:file].nil?
  123 + image = Image::new :uploaded_data=> params[:file][:image]
  124 +
  125 + @user.person.image = image
  126 + @user.person.save
  127 + end
  128 +
106 if @user.activated? 129 if @user.activated?
107 self.current_user = @user 130 self.current_user = @user
108 check_join_in_community(@user) 131 check_join_in_community(@user)
109 go_to_signup_initial_page 132 go_to_signup_initial_page
110 else 133 else
111 - @register_pending = true 134 + redirect_to :controller => :home, :action => :welcome, :template_id => (@user.person.template && @user.person.template.id)
  135 + session[:notice] = _('Thanks for registering!')
112 end 136 end
113 end 137 end
114 end 138 end
@@ -171,7 +195,7 @@ class AccountController &lt; ApplicationController @@ -171,7 +195,7 @@ class AccountController &lt; ApplicationController
171 else 195 else
172 @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]]
173 end 197 end
174 - rescue ActiveRecord::RecordInvald 198 + rescue ActiveRecord::RecordInvalid
175 @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.')
176 end 200 end
177 end 201 end
@@ -439,6 +463,8 @@ class AccountController &lt; ApplicationController @@ -439,6 +463,8 @@ class AccountController &lt; ApplicationController
439 redirect_to user.url 463 redirect_to user.url
440 when 'user_control_panel' 464 when 'user_control_panel'
441 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)
442 else 468 else
443 redirect_back_or_default(default) 469 redirect_back_or_default(default)
444 end 470 end
app/controllers/public/chat_controller.rb
@@ -19,9 +19,13 @@ class ChatController &lt; PublicController @@ -19,9 +19,13 @@ 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 - data = File.read(File.join(Rails.root, 'public', filename))  
23 - render :text => data, :layout => false, :content_type => mimetype  
24 - expires_in 24.hours 22 + if filename =~ /^(https?:)?\/\//
  23 + redirect_to filename
  24 + else
  25 + data = File.read(File.join(Rails.root, 'public', filename))
  26 + render :text => data, :layout => false, :content_type => mimetype
  27 + expires_in 24.hours
  28 + end
25 end 29 end
26 30
27 def index 31 def index
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
@@ -216,8 +216,6 @@ class ContentViewerController &lt; ApplicationController @@ -216,8 +216,6 @@ class ContentViewerController &lt; ApplicationController
216 if @page.has_posts? 216 if @page.has_posts?
217 posts = get_posts(params[:year], params[:month]) 217 posts = get_posts(params[:year], params[:month])
218 218
219 - posts = posts.includes(:parent, {:profile => [:domains, :environment]}, :author)  
220 -  
221 #FIXME Need to run this before the pagination because this version of 219 #FIXME Need to run this before the pagination because this version of
222 # will_paginate returns a will_paginate collection instead of a 220 # will_paginate returns a will_paginate collection instead of a
223 # relation. 221 # relation.
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,7 +17,11 @@ class ProfileController &lt; PublicController @@ -17,7 +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 20 + if profile.visible?
  21 + private_profile
  22 + else
  23 + invisible_profile
  24 + end
21 end 25 end
22 end 26 end
23 27
@@ -61,13 +65,13 @@ class ProfileController &lt; PublicController @@ -61,13 +65,13 @@ class ProfileController &lt; PublicController
61 65
62 def friends 66 def friends
63 if is_cache_expired?(profile.friends_cache_key(params)) 67 if is_cache_expired?(profile.friends_cache_key(params))
64 - @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)
65 end 69 end
66 end 70 end
67 71
68 def members 72 def members
69 if is_cache_expired?(profile.members_cache_key(params)) 73 if is_cache_expired?(profile.members_cache_key(params))
70 - @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)
71 end 75 end
72 end 76 end
73 77
@@ -315,7 +319,7 @@ class ProfileController &lt; PublicController @@ -315,7 +319,7 @@ class ProfileController &lt; PublicController
315 abuse_report = AbuseReport.new(params[:abuse_report]) 319 abuse_report = AbuseReport.new(params[:abuse_report])
316 if !params[:content_type].blank? 320 if !params[:content_type].blank?
317 article = params[:content_type].constantize.find(params[:content_id]) 321 article = params[:content_type].constantize.find(params[:content_id])
318 - abuse_report.content = instance_eval(&article.reported_version) 322 + abuse_report.content = article_reported_version(article)
319 end 323 end
320 324
321 user.register_report(abuse_report, profile) 325 user.register_report(abuse_report, profile)
@@ -394,6 +398,7 @@ class ProfileController &lt; PublicController @@ -394,6 +398,7 @@ class ProfileController &lt; PublicController
394 398
395 def private_profile 399 def private_profile
396 private_profile_partial_parameters 400 private_profile_partial_parameters
  401 + render :action => 'index', :status => 403
397 end 402 end
398 403
399 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
@@ -304,7 +304,7 @@ module ApplicationHelper @@ -304,7 +304,7 @@ module ApplicationHelper
304 def partial_for_class(klass, prefix=nil, suffix=nil) 304 def partial_for_class(klass, prefix=nil, suffix=nil)
305 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil? 305 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil?
306 name = klass.name.underscore 306 name = klass.name.underscore
307 - controller.view_paths.reverse_each do |view_path| 307 + controller.view_paths.each do |view_path|
308 partial = partial_for_class_in_view_path(klass, view_path, prefix, suffix) 308 partial = partial_for_class_in_view_path(klass, view_path, prefix, suffix)
309 return partial if partial 309 return partial if partial
310 end 310 end
@@ -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
@@ -482,7 +482,12 @@ module ApplicationHelper @@ -482,7 +482,12 @@ module ApplicationHelper
482 '/images/icons-app/enterprise-'+ size.to_s() +'.png' 482 '/images/icons-app/enterprise-'+ size.to_s() +'.png'
483 end 483 end
484 else 484 else
485 - '/images/icons-app/person-'+ size.to_s() +'.png' 485 + pixels = Image.attachment_options[:thumbnails][size].split('x').first
  486 + gravatar_profile_image_url(
  487 + profile.email,
  488 + :size => pixels,
  489 + :d => gravatar_default
  490 + )
486 end 491 end
487 filename = default_or_themed_icon(icon) 492 filename = default_or_themed_icon(icon)
488 end 493 end
@@ -602,7 +607,7 @@ module ApplicationHelper @@ -602,7 +607,7 @@ module ApplicationHelper
602 end 607 end
603 608
604 def gravatar_default 609 def gravatar_default
605 - (respond_to?(:theme_option) && theme_option.present? && theme_option['gravatar']) || NOOSFERO_CONF['gravatar'] 610 + (respond_to?(:theme_option) && theme_option.present? && theme_option['gravatar']) || NOOSFERO_CONF['gravatar'] || 'mm'
606 end 611 end
607 612
608 attr_reader :environment 613 attr_reader :environment
@@ -669,13 +674,14 @@ module ApplicationHelper @@ -669,13 +674,14 @@ module ApplicationHelper
669 html.join "\n" 674 html.join "\n"
670 end 675 end
671 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 +
672 def theme_javascript_ng 682 def theme_javascript_ng
673 - script = File.join(theme_path, 'theme.js')  
674 - if File.exists?(File.join(Rails.root, 'public', script))  
675 - javascript_include_tag script  
676 - else  
677 - nil  
678 - end 683 + script = theme_javascript_src
  684 + javascript_include_tag script if script
679 end 685 end
680 686
681 def file_field_or_thumbnail(label, image, i) 687 def file_field_or_thumbnail(label, image, i)
@@ -856,8 +862,9 @@ module ApplicationHelper @@ -856,8 +862,9 @@ module ApplicationHelper
856 end 862 end
857 863
858 def base_url 864 def base_url
859 - environment.top_url 865 + environment.top_url(request.scheme)
860 end 866 end
  867 + alias :top_url :base_url
861 868
862 def helper_for_article(article) 869 def helper_for_article(article)
863 article_helper = ActionView::Base.new 870 article_helper = ActionView::Base.new
@@ -902,13 +909,15 @@ module ApplicationHelper @@ -902,13 +909,15 @@ module ApplicationHelper
902 end 909 end
903 910
904 def page_title 911 def page_title
905 - (@page ? @page.title + ' - ' : '') +  
906 - (@topic ? @topic.title + ' - ' : '') +  
907 - (@section ? @section.title + ' - ' : '') +  
908 - (@toc ? _('Online Manual') + ' - ' : '') +  
909 - (controller.controller_name == 'chat' ? _('Chat') + ' - ' : '') +  
910 - (profile ? profile.short_name : environment.name) +  
911 - (@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 + )
912 end 921 end
913 922
914 # DEPRECATED. Do not use this. 923 # DEPRECATED. Do not use this.
@@ -937,9 +946,9 @@ module ApplicationHelper @@ -937,9 +946,9 @@ module ApplicationHelper
937 # from Article model for an ArticleBlock. 946 # from Article model for an ArticleBlock.
938 def reference_to_article(text, article, anchor=nil) 947 def reference_to_article(text, article, anchor=nil)
939 if article.profile.domains.empty? 948 if article.profile.domains.empty?
940 - href = "/#{article.url[:profile]}/" 949 + href = "#{Noosfero.root}/#{article.url[:profile]}/"
941 else 950 else
942 - href = "http://#{article.profile.domains.first.name}/" 951 + href = "http://#{article.profile.domains.first.name}#{Noosfero.root}/"
943 end 952 end
944 href += article.url[:page].join('/') 953 href += article.url[:page].join('/')
945 href += '#' + anchor if anchor 954 href += '#' + anchor if anchor
@@ -1280,11 +1289,13 @@ module ApplicationHelper @@ -1280,11 +1289,13 @@ module ApplicationHelper
1280 end 1289 end
1281 1290
1282 def delete_article_message(article) 1291 def delete_article_message(article)
1283 - if article.folder?  
1284 - _("Are you sure that you want to remove the folder \"#{article.name}\"? Note that all the items inside it will also be removed!")  
1285 - else  
1286 - _("Are you sure that you want to remove the item \"#{article.name}\"?")  
1287 - 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 + )
1288 end 1299 end
1289 1300
1290 def expirable_link_to(expired, content, url, options = {}) 1301 def expirable_link_to(expired, content, url, options = {})
@@ -1296,8 +1307,19 @@ module ApplicationHelper @@ -1296,8 +1307,19 @@ module ApplicationHelper
1296 end 1307 end
1297 end 1308 end
1298 1309
1299 - def remove_content_button(action)  
1300 - @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
1301 end 1323 end
1302 1324
1303 def template_options(kind, field_name) 1325 def template_options(kind, field_name)
@@ -1305,10 +1327,8 @@ module ApplicationHelper @@ -1305,10 +1327,8 @@ module ApplicationHelper
1305 return '' if templates.count == 0 1327 return '' if templates.count == 0
1306 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
1307 1329
1308 - counter = 0  
1309 radios = templates.map do |template| 1330 radios = templates.map do |template|
1310 - counter += 1  
1311 - 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)))
1312 end.join("\n") 1332 end.join("\n")
1313 1333
1314 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') +
@@ -1372,7 +1392,7 @@ module ApplicationHelper @@ -1372,7 +1392,7 @@ module ApplicationHelper
1372 # 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.
1373 html = @plugins.pipeline(:parse_content, html, source).first 1393 html = @plugins.pipeline(:parse_content, html, source).first
1374 end 1394 end
1375 - html 1395 + html && html.html_safe
1376 end 1396 end
1377 1397
1378 def convert_macro(html, source) 1398 def convert_macro(html, source)
@@ -1402,4 +1422,51 @@ module ApplicationHelper @@ -1402,4 +1422,51 @@ module ApplicationHelper
1402 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))})
1403 end 1423 end
1404 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 +
  1462 + def labelled_colorpicker_field(human_name, object_name, method, options = {})
  1463 + options[:id] ||= 'text-field-' + FormsHelper.next_id_number
  1464 + content_tag('label', human_name, :for => options[:id], :class => 'formlabel') +
  1465 + colorpicker_field(object_name, method, options.merge(:class => 'colorpicker_field'))
  1466 + end
  1467 +
  1468 + def colorpicker_field(object_name, method, options = {})
  1469 + text_field(object_name, method, options.merge(:class => 'colorpicker_field'))
  1470 + end
  1471 +
1405 end 1472 end
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,18 +77,69 @@ module ArticleHelper @@ -71,18 +77,69 @@ 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)
83 array.map { |object| {:id => object.id, :name => object.name} } 136 array.map { |object| {:id => object.id, :name => object.name} }
84 end 137 end
85 138
  139 + def prepare_to_token_input_by_label(array)
  140 + array.map { |object| {:label => object.name, :value => object.name} }
  141 + end
  142 +
86 def cms_label_for_new_children 143 def cms_label_for_new_children
87 _('New article') 144 _('New article')
88 end 145 end
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/categories_helper.rb
1 module CategoriesHelper 1 module CategoriesHelper
2 2
3 -  
4 - COLORS = [  
5 - [ N_('Do not display at the menu'), nil ],  
6 - [ N_('Orange'), 1],  
7 - [ N_('Green'), 2],  
8 - [ N_('Purple'), 3],  
9 - [ N_('Red'), 4],  
10 - [ N_('Dark Green'), 5],  
11 - [ N_('Blue Oil'), 6],  
12 - [ N_('Blue'), 7],  
13 - [ N_('Brown'), 8],  
14 - [ N_('Light Green'), 9],  
15 - [ N_('Light Blue'), 10],  
16 - [ N_('Dark Blue'), 11],  
17 - [ N_('Blue Pool'), 12],  
18 - [ N_('Beige'), 13],  
19 - [ N_('Yellow'), 14],  
20 - [ N_('Light Brown'), 15]  
21 - ]  
22 -  
23 TYPES = [ 3 TYPES = [
24 [ _('General Category'), Category.to_s ], 4 [ _('General Category'), Category.to_s ],
25 [ _('Product Category'), ProductCategory.to_s ], 5 [ _('Product Category'), ProductCategory.to_s ],
26 [ _('Region'), Region.to_s ], 6 [ _('Region'), Region.to_s ],
27 ] 7 ]
28 8
29 - def select_color_for_category  
30 - if @category.top_level?  
31 - labelled_form_field(_('Display at the menu?'), select('category', 'display_color', CategoriesHelper::COLORS.map {|item| [gettext(item[0]), item[1]] }))  
32 - else  
33 - ""  
34 - end  
35 - end  
36 -  
37 - def display_color_for_category(category)  
38 - color = category.display_color  
39 - if color.nil?  
40 - ""  
41 - else  
42 - "[" + gettext(CategoriesHelper::COLORS.find {|item| item[1] == color}.first) + "]"  
43 - end  
44 - end  
45 -  
46 def select_category_type(field) 9 def select_category_type(field)
47 value = params[field] 10 value = params[field]
48 labelled_form_field(_('Type of category'), select_tag('type', options_for_select(TYPES, value))) 11 labelled_form_field(_('Type of category'), select_tag('type', options_for_select(TYPES, value)))
49 end 12 end
50 13
  14 + def category_color_style(category)
  15 + return '' if category.nil? or category.display_color.blank?
  16 + 'background-color: #'+category.display_color+';'
  17 + end
  18 +
51 #FIXME make this test 19 #FIXME make this test
52 def selected_category_link(cat) 20 def selected_category_link(cat)
53 js_remove = "jQuery('#selected-category-#{cat.id}').remove();" 21 js_remove = "jQuery('#selected-category-#{cat.id}').remove();"
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,8 @@ module LayoutHelper @@ -27,6 +48,8 @@ module LayoutHelper
27 'thickbox', 48 'thickbox',
28 'lightbox', 49 'lightbox',
29 'colorbox', 50 'colorbox',
  51 + 'selectordie',
  52 + 'inputosaurus',
30 pngfix_stylesheet_path, 53 pngfix_stylesheet_path,
31 ] + tokeninput_stylesheets 54 ] + tokeninput_stylesheets
32 plugins_stylesheets = @plugins.select(&:stylesheet?).map { |plugin| plugin.class.public_path('style.css') } 55 plugins_stylesheets = @plugins.select(&:stylesheet?).map { |plugin| plugin.class.public_path('style.css') }
@@ -83,6 +106,10 @@ module LayoutHelper @@ -83,6 +106,10 @@ module LayoutHelper
83 theme_path + '/style.css' 106 theme_path + '/style.css'
84 end 107 end
85 108
  109 + def layout_template
  110 + if profile then profile.layout_template else environment.layout_template end
  111 + end
  112 +
86 def addthis_javascript 113 def addthis_javascript
87 if NOOSFERO_CONF['addthis_enabled'] 114 if NOOSFERO_CONF['addthis_enabled']
88 '<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>'
@@ -90,7 +117,7 @@ module LayoutHelper @@ -90,7 +117,7 @@ module LayoutHelper
90 end 117 end
91 118
92 def meta_description_tag(article=nil) 119 def meta_description_tag(article=nil)
93 - 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
94 end 121 end
95 end 122 end
96 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 - def display_field(title, profile, field, force = false) 3 + COMMON_CATEGORIES = ActiveSupport::OrderedHash.new
  4 + COMMON_CATEGORIES[:content] = [:blogs, :image_galleries, :events, :article_tags]
  5 + COMMON_CATEGORIES[:interests] = [:interests]
  6 + COMMON_CATEGORIES[:general] = nil
  7 +
  8 + PERSON_CATEGORIES = ActiveSupport::OrderedHash.new
  9 + PERSON_CATEGORIES[:basic_information] = [:nickname, :sex, :birth_date, :location, :privacy_setting, :created_at]
  10 + PERSON_CATEGORIES[:contact] = [:contact_phone, :cell_phone, :comercial_phone, :contact_information, :email, :personal_website, :jabber_id]
  11 + PERSON_CATEGORIES[:location] = [:address, :address_reference, :zip_code, :city, :state, :district, :country, :nationality]
  12 + PERSON_CATEGORIES[:work] = [:organization, :organization_website, :professional_activity]
  13 + PERSON_CATEGORIES[:study] = [:schooling, :formation, :area_of_study]
  14 + PERSON_CATEGORIES[:network] = [:friends, :communities, :enterprises]
  15 + PERSON_CATEGORIES.merge!(COMMON_CATEGORIES)
  16 +
  17 + ORGANIZATION_CATEGORIES = ActiveSupport::OrderedHash.new
  18 + ORGANIZATION_CATEGORIES[:basic_information] = [:display_name, :created_at, :foundation_year, :type, :language, :members_count, :location, :address_reference, :historic_and_current_context, :admins]
  19 + ORGANIZATION_CATEGORIES[:contact] = [:contact_person, :contact_phone, :contact_email, :organization_website, :jabber_id]
  20 + ORGANIZATION_CATEGORIES[:economic] = [:business_name, :acronym, :economic_activity, :legal_form, :products, :activities_short_description, :management_information]
  21 + ORGANIZATION_CATEGORIES.merge!(COMMON_CATEGORIES)
  22 +
  23 + CATEGORY_MAP = ActiveSupport::OrderedHash.new
  24 + CATEGORY_MAP[:person] = PERSON_CATEGORIES
  25 + CATEGORY_MAP[:organization] = ORGANIZATION_CATEGORIES
  26 +
  27 + FORCE = {
  28 + :person => [:privacy_setting],
  29 + :organization => [:privacy_setting, :location],
  30 + }
  31 +
  32 + MULTIPLE = {
  33 + :person => [:blogs, :image_galleries, :interests],
  34 + :organization => [:blogs, :image_galleries, :interests],
  35 + }
  36 +
  37 + CUSTOM_LABELS = {
  38 + :zip_code => _('ZIP code'),
  39 + :email => _('e-Mail'),
  40 + :jabber_id => _('Jabber'),
  41 + :birth_date => _('Date of birth'),
  42 + :created_at => _('Profile created at'),
  43 + :members_count => _('Members'),
  44 + :privacy_setting => _('Privacy setting'),
  45 + :article_tags => _('Tags')
  46 + }
  47 +
  48 + EXCEPTION = {
  49 + :person => [:image, :preferred_domain, :description, :tag_list],
  50 + :organization => [:image, :preferred_domain, :description, :tag_list, :address, :zip_code, :city, :state, :country, :district]
  51 + }
  52 +
  53 + def general_fields
  54 + categorized_fields = CATEGORY_MAP[kind].values.flatten
  55 + profile.class.fields.map(&:to_sym) - categorized_fields - EXCEPTION[kind]
  56 + end
  57 +
  58 + def kind
  59 + if profile.kind_of?(Person)
  60 + :person
  61 + else
  62 + :organization
  63 + end
  64 + end
  65 +
  66 + def title(field, entry = nil)
  67 + return self.send("#{field}_custom_title", entry) if MULTIPLE[kind].include?(field) && entry.present?
  68 + CUSTOM_LABELS[field.to_sym] || _(field.to_s.humanize)
  69 + end
  70 +
  71 + def display_field(field)
  72 + force = FORCE[kind].include?(field)
  73 + multiple = MULTIPLE[kind].include?(field)
4 unless force || profile.may_display_field_to?(field, user) 74 unless force || profile.may_display_field_to?(field, user)
5 return '' 75 return ''
6 end 76 end
7 - value = profile.send(field)  
8 - if !value.blank?  
9 - if block_given?  
10 - value = yield(value)  
11 - end  
12 - content_tag('tr', content_tag('td', title, :class => 'field-name') + content_tag('td', value)) 77 + value = begin profile.send(field) rescue nil end
  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))
13 else 82 else
14 - '' 83 + entries = multiple ? value : [] << value
  84 + entries.map do |entry|
  85 + content = self.send("treat_#{field}", entry)
  86 + unless content.blank?
  87 + content_tag('tr', content_tag('td', title(field, entry), :class => 'field-name') + content_tag('td', content))
  88 + end
  89 + end.join("\n")
15 end 90 end
16 end 91 end
17 92
18 - def display_contact(profile)  
19 - fields = []  
20 - fields << display_field(_('Address:'), profile, :address).html_safe  
21 - fields << display_field(_('ZIP code:'), profile, :zip_code).html_safe  
22 - fields << display_field(_('Contact phone:'), profile, :contact_phone).html_safe  
23 - fields << display_field(_('e-Mail:'), profile, :email) { |email| link_to_email(email) }.html_safe  
24 - fields << display_field(_('Personal website:'), profile, :personal_website).html_safe  
25 - fields << display_field(_('Jabber:'), profile, :jabber_id).html_safe  
26 - if fields.reject!(&:blank?).empty?  
27 - ''  
28 - else  
29 - content_tag('tr', content_tag('th', _('Contact'), { :colspan => 2 })) + fields.join.html_safe 93 + def treat_email(email)
  94 + link_to_email(email)
  95 + end
  96 +
  97 + def treat_organization_website(url)
  98 + link_to(url, url)
  99 + end
  100 +
  101 + def treat_sex(gender)
  102 + { 'male' => _('Male'), 'female' => _('Female') }[gender]
  103 + end
  104 +
  105 + def treat_date(date)
  106 + show_date(date.to_date)
  107 + end
  108 + alias :treat_birth_date :treat_date
  109 + alias :treat_created_at :treat_date
  110 +
  111 + def treat_friends(friends)
  112 + link_to friends.count, :controller => 'profile', :action => 'friends'
  113 + end
  114 +
  115 + def treat_communities(communities)
  116 + link_to communities.count, :controller => "profile", :action => 'communities'
  117 + end
  118 +
  119 + def treat_enterprises(enterprises)
  120 + if environment.disabled?('disable_asset_enterprises')
  121 + link_to enterprises.count, :controller => "profile", :action => 'enterprises'
  122 + end
  123 + end
  124 +
  125 + def treat_members_count(count)
  126 + link_to count, :controller => 'profile', :action => 'members'
  127 + end
  128 +
  129 + def treat_products(products)
  130 + if profile.kind_of?(Enterprise) && profile.environment.enabled?('products_for_enterprises')
  131 + link_to _('Products/Services'), :controller => 'catalog', :action => 'index'
30 end 132 end
31 end 133 end
32 134
33 - def display_work_info(profile)  
34 - organization = display_field(_('Organization:'), profile, :organization)  
35 - organization_site = display_field(_('Organization website:'), profile, :organization_website) { |url| link_to(url, url) }  
36 - if organization.blank? && organization_site.blank?  
37 - '' 135 + def treat_admins(admins)
  136 + profile.admins.map { |admin| link_to(admin.short_name, admin.url)}.join(', ')
  137 + end
  138 +
  139 + def treat_blogs(blog)
  140 + link_to(n_('One post', '%{num} posts', blog.posts.published.count) % { :num => blog.posts.published.count }, blog.url)
  141 + end
  142 +
  143 + def treat_image_galleries(gallery)
  144 + link_to(n_('One picture', '%{num} pictures', gallery.images.published.count) % { :num => gallery.images.published.count }, gallery.url)
  145 + end
  146 +
  147 + def treat_events(events)
  148 + link_to events.published.count, :controller => 'events', :action => 'events'
  149 + end
  150 +
  151 + def treat_article_tags(tags)
  152 + tag_cloud @tags, :id, { :action => 'tags' }, :max_size => 18, :min_size => 10
  153 + end
  154 +
  155 + def treat_interests(interest)
  156 + link_to interest.name, :controller => 'search', :action => 'category_index', :category_path => interest.explode_path
  157 + end
  158 +
  159 + def article_custom_title(article)
  160 + article.name
  161 + end
  162 + alias :blogs_custom_title :article_custom_title
  163 + alias :image_galleries_custom_title :article_custom_title
  164 +
  165 + def interests_custom_title(interest)
  166 + ''
  167 + end
  168 +
  169 + def method_missing(method, *args, &block)
  170 + if method.to_s =~ /^treat_(.+)$/
  171 + args[0]
  172 + elsif method.to_s =~ /^display_(.+)$/ && CATEGORY_MAP[kind].has_key?($1.to_sym)
  173 + category = $1.to_sym
  174 + fields = category == :general ? general_fields : CATEGORY_MAP[kind][category]
  175 + contents = []
  176 +
  177 + fields.each do |field|
  178 + contents << display_field(field).html_safe
  179 + end
  180 +
  181 + contents = contents.delete_if(&:blank?)
  182 +
  183 + unless contents.empty?
  184 + content_tag('tr', content_tag('th', title(category), { :colspan => 2 })) + contents.join.html_safe
  185 + else
  186 + ''
  187 + end
38 else 188 else
39 - content_tag('tr', content_tag('th', _('Work'), { :colspan => 2 })) + organization + organization_site 189 + super
40 end 190 end
41 end 191 end
42 192
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 0 → 100644
@@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
  1 +module TinymceHelper
  2 + include MacrosHelper
  3 +
  4 + def tinymce_js
  5 + output = ''
  6 + output += javascript_include_tag 'tinymce/js/tinymce/tinymce.min.js'
  7 + output += javascript_include_tag 'tinymce/js/tinymce/jquery.tinymce.min.js'
  8 + output += javascript_include_tag 'tinymce.js'
  9 + output += include_macro_js_files.to_s
  10 + output
  11 + end
  12 +
  13 + def tinymce_init_js options = {}
  14 + options.merge! :document_base_url => top_url,
  15 + :content_css => "/stylesheets/tinymce.css,#{macro_css_files}",
  16 + :plugins => %w[compat3x advlist autolink lists link image charmap print preview hr anchor pagebreak
  17 + searchreplace wordcount visualblocks visualchars code fullscreen
  18 + insertdatetime media nonbreaking save table contextmenu directionality
  19 + emoticons template paste textcolor colorpicker textpattern],
  20 + :language => tinymce_language
  21 +
  22 + options[:toolbar1] = "insertfile undo redo | copy paste | bold italic underline | styleselect fontsizeselect | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
  23 + if options[:mode] == 'simple'
  24 + options[:menubar] = false
  25 + else
  26 + options[:menubar] = 'edit insert view tools'
  27 + options[:toolbar2] = 'print preview code media | table'
  28 +
  29 + options[:toolbar2] += ' | macros'
  30 + macros_with_buttons.each do |macro|
  31 + options[:toolbar2] += " #{macro.identifier}"
  32 + end
  33 + end
  34 +
  35 + options[:macros_setup] = macros_with_buttons.map do |macro|
  36 + <<-EOS
  37 + ed.addButton('#{macro.identifier}', {
  38 + title: #{macro_title(macro).to_json},
  39 + onclick: #{generate_macro_config_dialog macro},
  40 + image : '#{macro.configuration[:icon_path]}'
  41 + });
  42 + EOS
  43 + end
  44 +
  45 + #cleanup non tinymce options
  46 + options = options.except :mode
  47 +
  48 + "noosfero.tinymce.init(#{options.to_json})"
  49 + end
  50 +
  51 +end
app/helpers/token_helper.rb
@@ -12,12 +12,14 @@ module TokenHelper @@ -12,12 +12,14 @@ 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'
18 options[:on_add] ||= 'null' 19 options[:on_add] ||= 'null'
19 options[:on_delete] ||= 'null' 20 options[:on_delete] ||= 'null'
20 options[:on_ready] ||= 'null' 21 options[:on_ready] ||= 'null'
  22 + options[:query_param] ||= 'q'
21 23
22 result = text_field_tag(name, nil, text_field_options.merge(html_options.merge({:id => element_id}))) 24 result = text_field_tag(name, nil, text_field_options.merge(html_options.merge({:id => element_id})))
23 result += javascript_tag("jQuery('##{element_id}') 25 result += javascript_tag("jQuery('##{element_id}')
@@ -30,7 +32,8 @@ module TokenHelper @@ -30,7 +32,8 @@ module TokenHelper
30 searchDelay: #{options[:search_delay].to_json}, 32 searchDelay: #{options[:search_delay].to_json},
31 preventDuplicates: #{options[:prevent_duplicates].to_json}, 33 preventDuplicates: #{options[:prevent_duplicates].to_json},
32 backspaceDeleteItem: #{options[:backspace_delete_item].to_json}, 34 backspaceDeleteItem: #{options[:backspace_delete_item].to_json},
33 - queryParam: #{name.to_json}, 35 + zindex: #{options[:zindex].to_json},
  36 + queryParam: #{options[:query_param].to_json},
34 tokenLimit: #{options[:token_limit].to_json}, 37 tokenLimit: #{options[:token_limit].to_json},
35 onResult: #{options[:on_result]}, 38 onResult: #{options[:on_result]},
36 onAdd: #{options[:on_add]}, 39 onAdd: #{options[:on_add]},
@@ -48,4 +51,4 @@ module TokenHelper @@ -48,4 +51,4 @@ module TokenHelper
48 result 51 result
49 end 52 end
50 53
51 -end  
52 \ No newline at end of file 54 \ No newline at end of file
  55 +end
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|
@@ -157,14 +166,17 @@ class Article &lt; ActiveRecord::Base @@ -157,14 +166,17 @@ class Article &lt; ActiveRecord::Base
157 self.profile 166 self.profile
158 end 167 end
159 168
160 - def self.human_attribute_name(attrib, options = {}) 169 + def self.human_attribute_name_with_customization(attrib, options={})
161 case attrib.to_sym 170 case attrib.to_sym
162 when :name 171 when :name
163 _('Title') 172 _('Title')
164 else 173 else
165 - _(self.superclass.human_attribute_name(attrib)) 174 + _(self.human_attribute_name_without_customization(attrib))
166 end 175 end
167 end 176 end
  177 + class << self
  178 + alias_method_chain :human_attribute_name, :customization
  179 + end
168 180
169 def css_class_list 181 def css_class_list
170 [self.class.name.to_css_class] 182 [self.class.name.to_css_class]
@@ -282,13 +294,6 @@ class Article &lt; ActiveRecord::Base @@ -282,13 +294,6 @@ class Article &lt; ActiveRecord::Base
282 end 294 end
283 end 295 end
284 296
285 - def reported_version(options = {})  
286 - article = self  
287 - search_path = Rails.root.join('app', 'views', 'shared', 'reported_versions')  
288 - partial_path = File.join('shared', 'reported_versions', partial_for_class_in_view_path(article.class, search_path))  
289 - lambda { render_to_string(:partial => partial_path, :locals => {:article => article}) }  
290 - end  
291 -  
292 # 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
293 # provide the correct content for the article. 298 # provide the correct content for the article.
294 def data 299 def data
@@ -337,7 +342,7 @@ class Article &lt; ActiveRecord::Base @@ -337,7 +342,7 @@ class Article &lt; ActiveRecord::Base
337 def belongs_to_blog? 342 def belongs_to_blog?
338 self.parent and self.parent.blog? 343 self.parent and self.parent.blog?
339 end 344 end
340 - 345 +
341 def belongs_to_forum? 346 def belongs_to_forum?
342 self.parent and self.parent.forum? 347 self.parent and self.parent.forum?
343 end 348 end
@@ -449,6 +454,7 @@ class Article &lt; ActiveRecord::Base @@ -449,6 +454,7 @@ class Article &lt; ActiveRecord::Base
449 if self.parent && !self.parent.published? 454 if self.parent && !self.parent.published?
450 return false 455 return false
451 end 456 end
  457 +
452 true 458 true
453 else 459 else
454 false 460 false
@@ -468,7 +474,9 @@ class Article &lt; ActiveRecord::Base @@ -468,7 +474,9 @@ class Article &lt; ActiveRecord::Base
468 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]}}
469 scope :galleries, :conditions => [ "articles.type IN ('Gallery')" ] 475 scope :galleries, :conditions => [ "articles.type IN ('Gallery')" ]
470 scope :images, :conditions => { :is_image => true } 476 scope :images, :conditions => { :is_image => true }
  477 + scope :no_images, :conditions => { :is_image => false }
471 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' }
472 scope :with_types, lambda { |types| { :conditions => [ 'articles.type IN (?)', types ] } } 480 scope :with_types, lambda { |types| { :conditions => [ 'articles.type IN (?)', types ] } }
473 481
474 scope :more_popular, :order => 'hits DESC' 482 scope :more_popular, :order => 'hits DESC'
@@ -480,14 +488,17 @@ class Article &lt; ActiveRecord::Base @@ -480,14 +488,17 @@ class Article &lt; ActiveRecord::Base
480 {:conditions => [" articles.published = ? OR 488 {:conditions => [" articles.published = ? OR
481 articles.last_changed_by_id = ? OR 489 articles.last_changed_by_id = ? OR
482 articles.profile_id = ? OR 490 articles.profile_id = ? OR
483 - ?",  
484 - 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)]}
485 end 494 end
486 495
  496 +
487 def display_unpublished_article_to?(user) 497 def display_unpublished_article_to?(user)
488 user == author || allow_view_private_content?(user) || user == profile || 498 user == author || allow_view_private_content?(user) || user == profile ||
489 user.is_admin?(profile.environment) || user.is_admin?(profile) || 499 user.is_admin?(profile.environment) || user.is_admin?(profile) ||
490 - article_privacy_exceptions.include?(user) 500 + article_privacy_exceptions.include?(user) ||
  501 + (self.show_to_followers && user.follows?(profile))
491 end 502 end
492 503
493 def display_to?(user = nil) 504 def display_to?(user = nil)
@@ -516,7 +527,10 @@ class Article &lt; ActiveRecord::Base @@ -516,7 +527,10 @@ class Article &lt; ActiveRecord::Base
516 end 527 end
517 528
518 alias :allow_delete? :allow_post_content? 529 alias :allow_delete? :allow_post_content?
519 - alias :allow_spread? :allow_post_content? 530 +
  531 + def allow_spread?(user = nil)
  532 + user && public?
  533 + end
520 534
521 def allow_create?(user) 535 def allow_create?(user)
522 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
@@ -192,7 +195,7 @@ class Block &lt; ActiveRecord::Base @@ -192,7 +195,7 @@ class Block &lt; ActiveRecord::Base
192 195
193 # Override in your subclasses. 196 # Override in your subclasses.
194 # Define which events and context should cause the block cache to expire 197 # Define which events and context should cause the block cache to expire
195 - # Possible events are: :article, :profile, :friendship, :category 198 + # Possible events are: :article, :profile, :friendship, :category, :role_assignment
196 # Possible contexts are: :profile, :environment 199 # Possible contexts are: :profile, :environment
197 def self.expire_on 200 def self.expire_on
198 { 201 {
@@ -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
@@ -234,4 +238,26 @@ class Block &lt; ActiveRecord::Base @@ -234,4 +238,26 @@ class Block &lt; ActiveRecord::Base
234 duplicated_block 238 duplicated_block
235 end 239 end
236 240
  241 + def copy_from(block)
  242 + self.settings = block.settings
  243 + self.position = block.position
  244 + end
  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 +
237 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/box.rb
@@ -28,9 +28,6 @@ class Box &lt; ActiveRecord::Base @@ -28,9 +28,6 @@ class Box &lt; ActiveRecord::Base
28 CategoriesBlock, 28 CategoriesBlock,
29 CommunitiesBlock, 29 CommunitiesBlock,
30 EnterprisesBlock, 30 EnterprisesBlock,
31 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
32 - # the Noosfero core soon, see ActionItem3045  
33 - EnvironmentStatisticsBlock,  
34 FansBlock, 31 FansBlock,
35 FavoriteEnterprisesBlock, 32 FavoriteEnterprisesBlock,
36 FeedReaderBlock, 33 FeedReaderBlock,
@@ -53,9 +50,6 @@ class Box &lt; ActiveRecord::Base @@ -53,9 +50,6 @@ class Box &lt; ActiveRecord::Base
53 CommunitiesBlock, 50 CommunitiesBlock,
54 DisabledEnterpriseMessageBlock, 51 DisabledEnterpriseMessageBlock,
55 EnterprisesBlock, 52 EnterprisesBlock,
56 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
57 - # the Noosfero core soon, see ActionItem3045  
58 - EnvironmentStatisticsBlock,  
59 FansBlock, 53 FansBlock,
60 FavoriteEnterprisesBlock, 54 FavoriteEnterprisesBlock,
61 FeaturedProductsBlock, 55 FeaturedProductsBlock,
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
@@ -14,9 +14,6 @@ class Category &lt; ActiveRecord::Base @@ -14,9 +14,6 @@ class Category &lt; ActiveRecord::Base
14 validates_uniqueness_of :slug,:scope => [ :environment_id, :parent_id ], :message => N_('{fn} is already being used by another category.').fix_i18n 14 validates_uniqueness_of :slug,:scope => [ :environment_id, :parent_id ], :message => N_('{fn} is already being used by another category.').fix_i18n
15 belongs_to :environment 15 belongs_to :environment
16 16
17 - validates_inclusion_of :display_color, :in => 1..15, :allow_nil => true  
18 - validates_uniqueness_of :display_color, :scope => :environment_id, :if => (lambda { |cat| ! cat.display_color.nil? }), :message => N_('{fn} was already assigned to another category.').fix_i18n  
19 -  
20 # Finds all top level categories for a given environment. 17 # Finds all top level categories for a given environment.
21 scope :top_level_for, lambda { |environment| 18 scope :top_level_for, lambda { |environment|
22 {:conditions => ['parent_id is null and environment_id = ?', environment.id ]} 19 {:conditions => ['parent_id is null and environment_id = ?', environment.id ]}
@@ -42,6 +39,13 @@ class Category &lt; ActiveRecord::Base @@ -42,6 +39,13 @@ class Category &lt; ActiveRecord::Base
42 39
43 acts_as_having_image 40 acts_as_having_image
44 41
  42 + before_save :normalize_display_color
  43 +
  44 + def normalize_display_color
  45 + display_color.gsub!('#', '') if display_color
  46 + display_color = nil if display_color.blank?
  47 + end
  48 +
45 scope :from_types, lambda { |types| 49 scope :from_types, lambda { |types|
46 types.select{ |t| t.blank? }.empty? ? 50 types.select{ |t| t.blank? }.empty? ?
47 { :conditions => { :type => types } } : 51 { :conditions => { :type => types } } :
@@ -101,4 +105,12 @@ class Category &lt; ActiveRecord::Base @@ -101,4 +105,12 @@ class Category &lt; ActiveRecord::Base
101 self.children.find(:all, :conditions => {:display_in_menu => true}).empty? 105 self.children.find(:all, :conditions => {:display_in_menu => true}).empty?
102 end 106 end
103 107
  108 + def with_color
  109 + if display_color.blank?
  110 + parent.nil? ? nil : parent.with_color
  111 + else
  112 + self
  113 + end
  114 + end
  115 +
104 end 116 end
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/change_password.rb
@@ -2,16 +2,19 @@ class ChangePassword &lt; Task @@ -2,16 +2,19 @@ class ChangePassword &lt; Task
2 2
3 attr_accessor :password, :password_confirmation 3 attr_accessor :password, :password_confirmation
4 4
5 - def self.human_attribute_name(attrib, options = {}) 5 + def self.human_attribute_name_with_customization(attrib, options={})
6 case attrib.to_sym 6 case attrib.to_sym
7 when :password 7 when :password
8 _('Password') 8 _('Password')
9 when :password_confirmation 9 when :password_confirmation
10 _('Password Confirmation') 10 _('Password Confirmation')
11 else 11 else
12 - _(self.superclass.human_attribute_name(attrib)) 12 + _(self.human_attribute_name_without_customization(attrib))
13 end 13 end
14 end 14 end
  15 + class << self
  16 + alias_method_chain :human_attribute_name, :customization
  17 + end
15 18
16 validates_presence_of :requestor 19 validates_presence_of :requestor
17 20
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
@@ -3,13 +3,14 @@ @@ -3,13 +3,14 @@
3 # domains. 3 # domains.
4 class Environment < ActiveRecord::Base 4 class Environment < ActiveRecord::Base
5 5
6 - attr_accessible :name, :is_default, :signup_welcome_text_subject, :signup_welcome_text_body, :terms_of_use, :message_for_disabled_enterprise, :news_amount_by_folder, :default_language, :languages, :description, :organization_approval_method, :enabled_plugins, :enabled_features, :redirection_after_login, :redirection_after_signup, :contact_email, :theme, :reports_lower_bound, :noreply_email, :signup_welcome_screen_body 6 + attr_accessible :name, :is_default, :signup_welcome_text_subject, :signup_welcome_text_body, :terms_of_use, :message_for_disabled_enterprise, :news_amount_by_folder, :default_language, :languages, :description, :organization_approval_method, :enabled_plugins, :enabled_features, :redirection_after_login, :redirection_after_signup, :contact_email, :theme, :reports_lower_bound, :noreply_email, :signup_welcome_screen_body, :members_whitelist_enabled, :members_whitelist
7 7
8 has_many :users 8 has_many :users
9 9
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
@@ -124,6 +127,7 @@ class Environment &lt; ActiveRecord::Base @@ -124,6 +127,7 @@ class Environment &lt; ActiveRecord::Base
124 'organizations_are_moderated_by_default' => _("Organizations have moderated publication by default"), 127 'organizations_are_moderated_by_default' => _("Organizations have moderated publication by default"),
125 'enable_organization_url_change' => _("Allow organizations to change their URL"), 128 'enable_organization_url_change' => _("Allow organizations to change their URL"),
126 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"), 129 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"),
  130 + 'admin_must_approve_new_users' => _("Admin must approve registration of new users"),
127 'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'), 131 'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'),
128 'xmpp_chat' => _('XMPP/Jabber based chat'), 132 'xmpp_chat' => _('XMPP/Jabber based chat'),
129 'show_zoom_button_on_article_images' => _('Show a zoom link on all article images'), 133 'show_zoom_button_on_article_images' => _('Show a zoom link on all article images'),
@@ -132,7 +136,8 @@ class Environment &lt; ActiveRecord::Base @@ -132,7 +136,8 @@ class Environment &lt; ActiveRecord::Base
132 'send_welcome_email_to_new_users' => _('Send welcome e-mail to new users'), 136 'send_welcome_email_to_new_users' => _('Send welcome e-mail to new users'),
133 'allow_change_of_redirection_after_login' => _('Allow users to set the page to redirect after login'), 137 'allow_change_of_redirection_after_login' => _('Allow users to set the page to redirect after login'),
134 'display_my_communities_on_user_menu' => _('Display on menu the list of communities the user can manage'), 138 'display_my_communities_on_user_menu' => _('Display on menu the list of communities the user can manage'),
135 - 'display_my_enterprises_on_user_menu' => _('Display on menu the list of enterprises the user can manage') 139 + 'display_my_enterprises_on_user_menu' => _('Display on menu the list of enterprises the user can manage'),
  140 + 'restrict_to_members' => _('Show content only to members')
136 } 141 }
137 end 142 end
138 143
@@ -153,7 +158,8 @@ class Environment &lt; ActiveRecord::Base @@ -153,7 +158,8 @@ class Environment &lt; ActiveRecord::Base
153 'site_homepage' => _('Redirects the user to the environment homepage.'), 158 'site_homepage' => _('Redirects the user to the environment homepage.'),
154 'user_profile_page' => _('Redirects the user to his profile page.'), 159 'user_profile_page' => _('Redirects the user to his profile page.'),
155 'user_homepage' => _('Redirects the user to his homepage.'), 160 'user_homepage' => _('Redirects the user to his homepage.'),
156 - '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.')
157 } 163 }
158 end 164 end
159 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
@@ -175,9 +181,6 @@ class Environment &lt; ActiveRecord::Base @@ -175,9 +181,6 @@ class Environment &lt; ActiveRecord::Base
175 181
176 # "left" area 182 # "left" area
177 env.boxes[1].blocks << LoginBlock.new 183 env.boxes[1].blocks << LoginBlock.new
178 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
179 - # the Noosfero core soon, see ActionItem3045  
180 - env.boxes[1].blocks << EnvironmentStatisticsBlock.new  
181 env.boxes[1].blocks << RecentDocumentsBlock.new 184 env.boxes[1].blocks << RecentDocumentsBlock.new
182 185
183 # "right" area 186 # "right" area
@@ -284,6 +287,7 @@ class Environment &lt; ActiveRecord::Base @@ -284,6 +287,7 @@ class Environment &lt; ActiveRecord::Base
284 www.flickr.com 287 www.flickr.com
285 www.gmodules.com 288 www.gmodules.com
286 www.youtube.com 289 www.youtube.com
  290 + openstreetmap.org
287 ] + ('a' .. 'z').map{|i| "#{i}.yimg.com"} 291 ] + ('a' .. 'z').map{|i| "#{i}.yimg.com"}
288 292
289 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
@@ -303,6 +307,17 @@ class Environment &lt; ActiveRecord::Base @@ -303,6 +307,17 @@ class Environment &lt; ActiveRecord::Base
303 settings[:signup_welcome_screen_body].present? 307 settings[:signup_welcome_screen_body].present?
304 end 308 end
305 309
  310 + settings_items :members_whitelist_enabled, :type => :boolean, :default => false
  311 + settings_items :members_whitelist, :type => Array, :default => []
  312 +
  313 + def in_whitelist?(person)
  314 + !members_whitelist_enabled || members_whitelist.include?(person.id)
  315 + end
  316 +
  317 + def members_whitelist=(members)
  318 + settings[:members_whitelist] = members.split(',').map(&:to_i)
  319 + end
  320 +
306 def news_amount_by_folder=(amount) 321 def news_amount_by_folder=(amount)
307 settings[:news_amount_by_folder] = amount.to_i 322 settings[:news_amount_by_folder] = amount.to_i
308 end 323 end
@@ -646,10 +661,11 @@ class Environment &lt; ActiveRecord::Base @@ -646,10 +661,11 @@ class Environment &lt; ActiveRecord::Base
646 { :controller => 'admin_panel', :action => 'index' } 661 { :controller => 'admin_panel', :action => 'index' }
647 end 662 end
648 663
649 - def top_url  
650 - url = 'http://' 664 + def top_url(scheme = 'http')
  665 + url = scheme + '://'
651 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)
652 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)
  668 + url << Noosfero.root('')
653 url 669 url
654 end 670 end
655 671
@@ -717,31 +733,50 @@ class Environment &lt; ActiveRecord::Base @@ -717,31 +733,50 @@ class Environment &lt; ActiveRecord::Base
717 ] 733 ]
718 end 734 end
719 735
720 - 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
721 template = Community.find_by_id settings[:community_template_id] 748 template = Community.find_by_id settings[:community_template_id]
722 - template if template && template.is_template 749 + template if template && template.is_template?
723 end 750 end
724 751
725 - def community_template=(value)  
726 - settings[:community_template_id] = value.id 752 + def community_default_template=(value)
  753 + settings[:community_template_id] = value.kind_of?(Community) ? value.id : value
727 end 754 end
728 755
729 - def person_template 756 + def person_templates
  757 + self.people.templates
  758 + end
  759 +
  760 + def person_default_template
730 template = Person.find_by_id settings[:person_template_id] 761 template = Person.find_by_id settings[:person_template_id]
731 - 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
732 end 767 end
733 768
734 - def person_template=(value)  
735 - settings[:person_template_id] = value.id 769 + def enterprise_templates
  770 + self.enterprises.templates
736 end 771 end
737 772
738 - def enterprise_template 773 + def enterprise_default_template
739 template = Enterprise.find_by_id settings[:enterprise_template_id] 774 template = Enterprise.find_by_id settings[:enterprise_template_id]
740 - template if template && template.is_template 775 + template if template && template.is_template?
741 end 776 end
742 777
743 - def enterprise_template=(value)  
744 - settings[:enterprise_template_id] = value.id 778 + def enterprise_default_template=(value)
  779 + settings[:enterprise_template_id] = value.kind_of?(Enterprise) ? value.id : value
745 end 780 end
746 781
747 def inactive_enterprise_template 782 def inactive_enterprise_template
@@ -793,8 +828,12 @@ class Environment &lt; ActiveRecord::Base @@ -793,8 +828,12 @@ class Environment &lt; ActiveRecord::Base
793 "home-page-news/#{cache_key}-#{language}" 828 "home-page-news/#{cache_key}-#{language}"
794 end 829 end
795 830
  831 + def portal_enabled
  832 + portal_community && enabled?('use_portal_community')
  833 + end
  834 +
796 def notification_emails 835 def notification_emails
797 - [noreply_email.blank? ? nil : noreply_email].compact + admins.map(&:email) 836 + [contact_email].select(&:present?) + admins.map(&:email)
798 end 837 end
799 838
800 after_create :create_templates 839 after_create :create_templates
@@ -839,10 +878,10 @@ class Environment &lt; ActiveRecord::Base @@ -839,10 +878,10 @@ class Environment &lt; ActiveRecord::Base
839 person_template.visible = false 878 person_template.visible = false
840 person_template.save! 879 person_template.save!
841 880
842 - self.enterprise_template = enterprise_template 881 + self.enterprise_default_template = enterprise_template
843 self.inactive_enterprise_template = inactive_enterprise_template 882 self.inactive_enterprise_template = inactive_enterprise_template
844 - self.community_template = community_template  
845 - self.person_template = person_template 883 + self.community_default_template = community_template
  884 + self.person_default_template = person_template
846 self.save! 885 self.save!
847 end 886 end
848 887
@@ -906,6 +945,10 @@ class Environment &lt; ActiveRecord::Base @@ -906,6 +945,10 @@ class Environment &lt; ActiveRecord::Base
906 locales_list 945 locales_list
907 end 946 end
908 947
  948 + def has_license?
  949 + self.licenses.any?
  950 + end
  951 +
909 private 952 private
910 953
911 def default_language_available 954 def default_language_available
app/models/environment_statistics_block.rb
@@ -1,33 +0,0 @@ @@ -1,33 +0,0 @@
1 -# TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
2 -# the Noosfero core soon, see ActionItem3045  
3 -  
4 -class EnvironmentStatisticsBlock < Block  
5 -  
6 - def self.description  
7 - _('Environment stastistics (DEPRECATED)')  
8 - end  
9 -  
10 - def default_title  
11 - _('Statistics for %s') % owner.name  
12 - end  
13 -  
14 - def help  
15 - _('This block presents some statistics about your environment.')  
16 - end  
17 -  
18 - def content(args={})  
19 - users = owner.people.visible.count  
20 - enterprises = owner.enterprises.visible.count  
21 - communities = owner.communities.visible.count  
22 -  
23 - info = []  
24 - info << (n_('One user', '%{num} users', users) % { :num => users })  
25 - unless owner.enabled?('disable_asset_enterprises')  
26 - info << (n_('One enterprise', '%{num} enterprises', enterprises) % { :num => enterprises })  
27 - end  
28 - info << (n_('One community', '%{num} communities', communities) % { :num => communities })  
29 -  
30 - block_title(title) + content_tag('ul', info.map {|item| content_tag('li', item) }.join("\n"))  
31 - end  
32 -  
33 -end  
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
@@ -30,6 +31,7 @@ class ExternalFeed &lt; ActiveRecord::Base @@ -30,6 +31,7 @@ class ExternalFeed &lt; ActiveRecord::Base
30 article.source = link 31 article.source = link
31 article.profile = blog.profile 32 article.profile = blog.profile
32 article.parent = blog 33 article.parent = blog
  34 + article.author_name = self.feed_title
33 unless blog.children.exists?(:slug => article.slug) 35 unless blog.children.exists?(:slug => article.slug)
34 article.save! 36 article.save!
35 article.delay.create_activity 37 article.delay.create_activity
app/models/feed_reader_block.rb
@@ -85,8 +85,4 @@ class FeedReaderBlock &lt; Block @@ -85,8 +85,4 @@ class FeedReaderBlock &lt; Block
85 block_title(title) + formatted_feed_content 85 block_title(title) + formatted_feed_content
86 end 86 end
87 87
88 - def editable?  
89 - true  
90 - end  
91 -  
92 end 88 end
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/link_list_block.rb
@@ -78,16 +78,17 @@ class LinkListBlock &lt; Block @@ -78,16 +78,17 @@ class LinkListBlock &lt; Block
78 address 78 address
79 end 79 end
80 if add !~ /^[a-z]+:\/\// && add !~ /^\// 80 if add !~ /^[a-z]+:\/\// && add !~ /^\//
81 - 'http://' + add 81 + '//' + add
82 else 82 else
  83 + if root = Noosfero.root
  84 + if !add.starts_with?(root)
  85 + add = root + add
  86 + end
  87 + end
83 add 88 add
84 end 89 end
85 end 90 end
86 91
87 - def editable?  
88 - true  
89 - end  
90 -  
91 def icons_options 92 def icons_options
92 ICONS.map do |i| 93 ICONS.map do |i|
93 "<span title=\"#{i[1]}\" class=\"icon-#{i[0]}\" onclick=\"changeIcon(this, '#{i[0]}')\"></span>".html_safe 94 "<span title=\"#{i[1]}\" class=\"icon-#{i[0]}\" onclick=\"changeIcon(this, '#{i[0]}')\"></span>".html_safe
@@ -100,4 +101,5 @@ class LinkListBlock &lt; Block @@ -100,4 +101,5 @@ class LinkListBlock &lt; Block
100 sanitizer = HTML::WhiteListSanitizer.new 101 sanitizer = HTML::WhiteListSanitizer.new
101 sanitizer.sanitize(text) 102 sanitizer.sanitize(text)
102 end 103 end
  104 +
103 end 105 end
app/models/main_block.rb
@@ -16,10 +16,6 @@ class MainBlock &lt; Block @@ -16,10 +16,6 @@ class MainBlock &lt; Block
16 true 16 true
17 end 17 end
18 18
19 - def editable?  
20 - true  
21 - end  
22 -  
23 def cacheable? 19 def cacheable?
24 false 20 false
25 end 21 end
app/models/moderate_user_registration.rb 0 → 100644
@@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
  1 +class ModerateUserRegistration < Task
  2 +
  3 + settings_items :user_id, :type => String
  4 + settings_items :name, :type => String
  5 + settings_items :author_name, :type => String
  6 + settings_items :email, :type => String
  7 +
  8 + after_create :schedule_spam_checking
  9 +
  10 + alias :environment :target
  11 + alias :environment= :target=
  12 +
  13 + def schedule_spam_checking
  14 + self.delay.check_for_spam
  15 + end
  16 +
  17 + include Noosfero::Plugin::HotSpot
  18 +
  19 + def sender
  20 + "#{name} (#{email})"
  21 + end
  22 +
  23 + def perform
  24 + user=environment.users.find_by_id(user_id)
  25 + user.activate
  26 + end
  27 +
  28 + def title
  29 + _("New user")
  30 + end
  31 +
  32 + def subject
  33 + name
  34 + end
  35 +
  36 + def information
  37 + { :message => _('%{sender} wants to register.'),
  38 + :variables => {:sender => sender} }
  39 + end
  40 +
  41 + def icon
  42 + result = {:type => :defined_image, :src => '/images/icons-app/person-minor.png', :name => name}
  43 + end
  44 +
  45 + def target_notification_description
  46 + _('%{sender} tried to register.') %
  47 + {:sender => sender}
  48 + end
  49 +
  50 + def target_notification_message
  51 + target_notification_description + "\n\n" +
  52 + _('You need to login on %{system} in order to approve or reject this user.') % { :environment => self.environment }
  53 + end
  54 +
  55 + def target_notification_message
  56 + _("User \"%{user}\" just requested to register. You have to approve or reject it through the \"Pending Validations\" section in your control panel.\n") % { :user => self.name }
  57 + end
  58 +
  59 +end
0 \ No newline at end of file 60 \ No newline at end of file
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
@@ -135,7 +146,11 @@ class Organization &lt; Profile @@ -135,7 +146,11 @@ class Organization &lt; Profile
135 end 146 end
136 147
137 def notification_emails 148 def notification_emails
138 - [contact_email.blank? ? nil : contact_email].compact + admins.map(&:email) 149 + emails = [contact_email].select(&:present?) + admins.map(&:email)
  150 + if emails.empty?
  151 + emails << environment.contact_email
  152 + end
  153 + emails
139 end 154 end
140 155
141 def already_request_membership?(person) 156 def already_request_membership?(person)
@@ -162,4 +177,8 @@ class Organization &lt; Profile @@ -162,4 +177,8 @@ class Organization &lt; Profile
162 self.visible = false 177 self.visible = false
163 save! 178 save!
164 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
165 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|
@@ -346,16 +361,17 @@ class Profile &lt; ActiveRecord::Base @@ -346,16 +361,17 @@ class Profile &lt; ActiveRecord::Base
346 end 361 end
347 362
348 def copy_blocks_from(profile) 363 def copy_blocks_from(profile)
  364 + template_boxes = profile.boxes.select{|box| box.position}
349 self.boxes.destroy_all 365 self.boxes.destroy_all
350 - profile.boxes.each do |box|  
351 - new_box = Box.new 366 + self.boxes = template_boxes.size.times.map { Box.new }
  367 +
  368 + template_boxes.each_with_index do |box, i|
  369 + new_box = self.boxes[i]
352 new_box.position = box.position 370 new_box.position = box.position
353 - self.boxes << new_box  
354 box.blocks.each do |block| 371 box.blocks.each do |block|
355 new_block = block.class.new(:title => block[:title]) 372 new_block = block.class.new(:title => block[:title])
356 - new_block.settings = block.settings  
357 - new_block.position = block.position  
358 - self.boxes[-1].blocks << new_block 373 + new_block.copy_from(block)
  374 + new_box.blocks << new_block
359 end 375 end
360 end 376 end
361 end 377 end
@@ -390,7 +406,7 @@ class Profile &lt; ActiveRecord::Base @@ -390,7 +406,7 @@ class Profile &lt; ActiveRecord::Base
390 end 406 end
391 407
392 xss_terminate :only => [ :name, :nickname, :address, :contact_phone, :description ], :on => 'validation' 408 xss_terminate :only => [ :name, :nickname, :address, :contact_phone, :description ], :on => 'validation'
393 - xss_terminate :only => [ :custom_footer, :custom_header ], :with => 'white_list', :on => 'validation' 409 + xss_terminate :only => [ :custom_footer, :custom_header ], :with => 'white_list'
394 410
395 include WhiteListFilter 411 include WhiteListFilter
396 filter_iframes :custom_header, :custom_footer 412 filter_iframes :custom_header, :custom_footer
@@ -511,6 +527,14 @@ class Profile &lt; ActiveRecord::Base @@ -511,6 +527,14 @@ class Profile &lt; ActiveRecord::Base
511 generate_url(:profile => identifier, :controller => 'profile', :action => 'index') 527 generate_url(:profile => identifier, :controller => 'profile', :action => 'index')
512 end 528 end
513 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 +
514 def generate_url(options) 538 def generate_url(options)
515 url_options.merge(options) 539 url_options.merge(options)
516 end 540 end
@@ -600,7 +624,7 @@ private :generate_url, :url_options @@ -600,7 +624,7 @@ private :generate_url, :url_options
600 end 624 end
601 625
602 def copy_article_tree(article, parent=nil) 626 def copy_article_tree(article, parent=nil)
603 - return if article.is_a?(RssFeed) 627 + return if !copy_article?(article)
604 original_article = self.articles.find_by_name(article.name) 628 original_article = self.articles.find_by_name(article.name)
605 if original_article 629 if original_article
606 num = 2 630 num = 2
@@ -620,6 +644,11 @@ private :generate_url, :url_options @@ -620,6 +644,11 @@ private :generate_url, :url_options
620 end 644 end
621 end 645 end
622 646
  647 + def copy_article?(article)
  648 + !article.is_a?(RssFeed) &&
  649 + !(is_template && article.slug=='welcome-page')
  650 + end
  651 +
623 # Adds a person as member of this Profile. 652 # Adds a person as member of this Profile.
624 def add_member(person) 653 def add_member(person)
625 if self.has_members? 654 if self.has_members?
@@ -629,6 +658,8 @@ private :generate_url, :url_options @@ -629,6 +658,8 @@ private :generate_url, :url_options
629 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
630 self.affiliate(person, Profile::Roles.member(environment.id)) 659 self.affiliate(person, Profile::Roles.member(environment.id))
631 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
632 else 663 else
633 raise _("%s can't have members") % self.class.name 664 raise _("%s can't have members") % self.class.name
634 end 665 end
@@ -766,7 +797,10 @@ private :generate_url, :url_options @@ -766,7 +797,10 @@ private :generate_url, :url_options
766 end 797 end
767 798
768 def admins 799 def admins
769 - 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)
770 end 804 end
771 805
772 def enable_contact? 806 def enable_contact?
@@ -774,7 +808,7 @@ private :generate_url, :url_options @@ -774,7 +808,7 @@ private :generate_url, :url_options
774 end 808 end
775 809
776 include Noosfero::Plugin::HotSpot 810 include Noosfero::Plugin::HotSpot
777 - 811 +
778 def folder_types 812 def folder_types
779 types = Article.folder_types 813 types = Article.folder_types
780 plugins.dispatch(:content_types).each {|type| 814 plugins.dispatch(:content_types).each {|type|
@@ -898,6 +932,13 @@ private :generate_url, :url_options @@ -898,6 +932,13 @@ private :generate_url, :url_options
898 end 932 end
899 933
900 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
901 end 942 end
902 943
903 def control_panel_settings_button 944 def control_panel_settings_button
@@ -957,4 +998,14 @@ private :generate_url, :url_options @@ -957,4 +998,14 @@ private :generate_url, :url_options
957 def preferred_login_redirection 998 def preferred_login_redirection
958 redirection_after_login.blank? ? environment.redirection_after_login : redirection_after_login 999 redirection_after_login.blank? ? environment.redirection_after_login : redirection_after_login
959 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 +
960 end 1011 end
app/models/profile_image_block.rb
@@ -23,10 +23,6 @@ class ProfileImageBlock &lt; Block @@ -23,10 +23,6 @@ class ProfileImageBlock &lt; Block
23 end 23 end
24 end 24 end
25 25
26 - def editable?  
27 - true  
28 - end  
29 -  
30 def cacheable? 26 def cacheable?
31 false 27 false
32 end 28 end
app/models/profile_info_block.rb
@@ -15,10 +15,6 @@ class ProfileInfoBlock &lt; Block @@ -15,10 +15,6 @@ class ProfileInfoBlock &lt; Block
15 end 15 end
16 end 16 end
17 17
18 - def editable?  
19 - false  
20 - end  
21 -  
22 def cacheable? 18 def cacheable?
23 false 19 false
24 end 20 end
app/models/profile_search_block.rb
@@ -11,8 +11,4 @@ class ProfileSearchBlock &lt; Block @@ -11,8 +11,4 @@ class ProfileSearchBlock &lt; Block
11 end 11 end
12 end 12 end
13 13
14 - def editable?  
15 - true  
16 - end  
17 -  
18 end 14 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
@@ -73,10 +73,6 @@ class Task &lt; ActiveRecord::Base @@ -73,10 +73,6 @@ class Task &lt; ActiveRecord::Base
73 end 73 end
74 end 74 end
75 75
76 - def self.all_types  
77 - %w[Invitation EnterpriseActivation AddMember Ticket SuggestArticle AddFriend CreateCommunity AbuseComplaint ApproveComment ApproveArticle CreateEnterprise ChangePassword EmailActivation InviteFriend InviteMember]  
78 - end  
79 -  
80 # this method finished the task. It calls #perform, which must be overriden 76 # this method finished the task. It calls #perform, which must be overriden
81 # by subclasses. At the end a message (as returned by #finish_message) is 77 # by subclasses. At the end a message (as returned by #finish_message) is
82 # sent to the requestor with #notify_requestor. 78 # sent to the requestor with #notify_requestor.
@@ -254,6 +250,10 @@ class Task &lt; ActiveRecord::Base @@ -254,6 +250,10 @@ class Task &lt; ActiveRecord::Base
254 { :conditions => [environment_condition, profile_condition].compact.join(' OR ') } 250 { :conditions => [environment_condition, profile_condition].compact.join(' OR ') }
255 } 251 }
256 252
  253 + def self.pending_types_for(profile)
  254 + Task.to(profile).pending.select('distinct type').map { |t| [t.class.name, t.title] }
  255 + end
  256 +
257 def opened? 257 def opened?
258 status == Task::Status::ACTIVE || status == Task::Status::HIDDEN 258 status == Task::Status::ACTIVE || status == Task::Status::HIDDEN
259 end 259 end
@@ -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