Commit 6946395bf26bd2438ff2f85deb063936651d3b58
Exists in
theme-brasil-digital-from-staging
and in
9 other branches
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.
.ackrc
AUTHORS
| ... | ... | @@ -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> |
AUTHORS.md
| 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 | 6 | Developers |
| 4 | 7 | ========== |
| 5 | 8 | |
| 9 | +Ábner Silva de Oliveira <abner.oliveira@serpro.gov.br> | |
| 6 | 10 | Alan Freihof Tygel <alantygel@gmail.com> |
| 11 | +alcampelo <alcampelo@alcampelo.(none)> | |
| 7 | 12 | Alessandro Palmeira <alessandro.palmeira@gmail.com> |
| 8 | 13 | Alessandro Palmeira + Caio C. Salgado <alessandro.palmeira@gmail.com> |
| 9 | 14 | Alessandro Palmeira + Caio Salgado <alessandro.palmeira@gmail.com> |
| ... | ... | @@ -35,9 +40,14 @@ Alessandro Palmeira + João M. M. Silva <alessandro.palmeira@gmail.com> |
| 35 | 40 | Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com> |
| 36 | 41 | Alessandro Palmeira + Paulo Meirelles + João M. M. da Silva <alessandro.palmeira@gmail.com> |
| 37 | 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 | 46 | Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br> |
| 39 | 47 | Antonio Terceiro + Paulo Meirelles <terceiro@colivre.coop.br> |
| 40 | 48 | Antonio Terceiro <terceiro@colivre.coop.br> |
| 49 | +Arthur Del Esposte <arthurmde@gmail.com> | |
| 50 | +Arthur Del Esposte <arthurmde@yahoo.com.br> | |
| 41 | 51 | Aurelio A. Heckert <aurelio@colivre.coop.br> |
| 42 | 52 | Braulio Bhavamitra <brauliobo@gmail.com> |
| 43 | 53 | Bráulio Bhavamitra <brauliobo@gmail.com> |
| ... | ... | @@ -65,6 +75,8 @@ Caio Salgado + Renan Teruo <caio.salgado@gmail.com> |
| 65 | 75 | Caio Salgado + Renan Teruo + Jefferson Fernandes <jeffs.fernandes@gmail.com> |
| 66 | 76 | Caio Salgado + Renan Teruo <renanteruoc@gmail.com> |
| 67 | 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 | 80 | Carlos Morais <carlos88morais@gmail.com> |
| 69 | 81 | Carlos Morais + Diego Araújo <diegoamc90@gmail.com> |
| 70 | 82 | Carlos Morais + Eduardo Morais <carlos88morais@gmail.com> |
| ... | ... | @@ -78,7 +90,9 @@ Daniel Alves + Diego Araújo + Guilherme Rojas <guilhermehrojas@gmail.com> |
| 78 | 90 | Daniel Alves + Guilherme Rojas <danpaulalves@gmail.com> |
| 79 | 91 | Daniel Alves + Rafael Manzo <rr.manzo@gmail.com> |
| 80 | 92 | Daniela Soares Feitosa <danielafeitosa@colivre.coop.br> |
| 93 | +Daniel Bucher <daniel.bucher88@gmail.com> | |
| 81 | 94 | Daniel Cunha <daniel@colivre.coop.br> |
| 95 | +David Carlos <ddavidcarlos1392@gmail.com> | |
| 82 | 96 | diegoamc <diegoamc90@gmail.com> |
| 83 | 97 | Diego Araújo + Alessandro Palmeira <diegoamc90@gmail.com> |
| 84 | 98 | Diego Araújo + Alessandro Palmeira + João M. M. da Silva <diegoamc90@gmail.com> |
| ... | ... | @@ -105,17 +119,27 @@ Diego Araújo + Renan Teruo <diegoamc90@gmail.com> |
| 105 | 119 | Diego Araujo + Rodrigo Souto + Rafael Manzo <rr.manzo@gmail.com> |
| 106 | 120 | Diego + Jefferson <diegoamc90@gmail.com> |
| 107 | 121 | Diego Martinez <diegoamc90@gmail.com> |
| 108 | -Diego Martinez <diego@diego-K55A.(none)> | |
| 109 | 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 | 127 | Fernanda Lopes <nanda.listas+psl@gmail.com> |
| 111 | 128 | Francisco Marcelo A. Lima Júnior <francisco.lima-junior@serpro.gov.br> |
| 112 | 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 | 133 | Grazieno Pellegrino <grazieno@gmail.com> |
| 134 | +Gust <darksshades@hotmail.com> | |
| 135 | +Hebert Douglas <hebertdougl@gmail.com> | |
| 136 | +Hugo Melo <hugo@riseup.net> | |
| 114 | 137 | Isaac Canan <isaac@intelletto.com.br> |
| 115 | 138 | Italo Valcy <italo@dcc.ufba.br> |
| 116 | 139 | Jefferson Fernandes + Diego Araujo + Rafael Manzo <jeffs.fernandes@gmail.com> |
| 117 | 140 | Jefferson Fernandes + Joao M. M. da Silva <jeffs.fernandes@gmail.com> |
| 118 | 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 | 143 | João da Silva <jaodsilv@linux.ime.usp.br> |
| 120 | 144 | João Marco Maciel da Silva + Rafael Manzo + Renan Teruo <jaodsilv@linux.ime.usp.br> |
| 121 | 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 <jaodsilv@linux.ime.usp.br> |
| 146 | 170 | João M. M. Silva + Renan Teruo <jaodsilv@linux.ime.usp.br> |
| 147 | 171 | Joenio Costa <joenio@colivre.coop.br> |
| 148 | 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 | 176 | Junior Silva <juniorsilva1001@gmail.com> |
| 150 | 177 | Junior Silva <juniorsilva7@juniorsilva-Aspire-5750Z.(none)> |
| 178 | +Junior Silva <juniorsilva@colivre.coop.br> | |
| 179 | +juniorsilva <juniorsilva@QonoS.localhost.localdomain> | |
| 151 | 180 | Keilla Menezes <keilla@colivre.coop.br> |
| 152 | 181 | Larissa Reis <larissa@colivre.coop.br> |
| 153 | 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 | 186 | Leandro Nunes dos Santos <leandronunes@gmail.com> |
| 155 | 187 | Leandro Nunes dos Santos <leandro.santos@serpro.gov.br> |
| 156 | 188 | LinguÁgil 2010 <linguagil.bahia@gmail.com> |
| 157 | 189 | Lucas Melo <lucas@colivre.coop.br> |
| 158 | 190 | Lucas Melo <lucaspradomelo@gmail.com> |
| 191 | +Luciano <lucianopcbr@gmail.com> | |
| 192 | +Luciano Prestes Cavalcanti <lucianopcbr@gmail.com> | |
| 159 | 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 | 196 | Martín Olivera <molivera@solar.org.ar> |
| 197 | +Michal Čihař <michal@cihar.com> | |
| 161 | 198 | Moises Machado <moises@colivre.coop.br> |
| 162 | 199 | Naíla Alves <naila@colivre.coop.br> |
| 163 | 200 | Nanda Lopes <nanda.listas+psl@gmail.com> |
| 201 | +Parley Martins <parleypachecomartins@gmail.com> | |
| 164 | 202 | Paulo Meirelles + Alessandro Palmeira + João M. M. da Silva <paulo@softwarelivre.org> |
| 165 | 203 | Paulo Meirelles + Alessandro Palmeira <paulo@softwarelivre.org> |
| 166 | 204 | Paulo Meirelles + Carlos Morais <paulo@softwarelivre.org> |
| ... | ... | @@ -183,20 +221,28 @@ Rafael Reggiani Manzo + João M. M. da Silva <rr.manzo@gmail.com> |
| 183 | 221 | Rafael Reggiani Manzo <rr.manzo@gmail.com> |
| 184 | 222 | Raphaël Rousseau <raph@r4f.org> |
| 185 | 223 | Raquel Lira <raquel.lira@gmail.com> |
| 224 | +Raquel <rcordioli@gmail.com> | |
| 186 | 225 | Renan Teruo + Caio Salgado <renanteruoc@gmail.com> |
| 187 | 226 | Renan Teruoc + Diego Araujo <renanteruoc@gmail.com> |
| 188 | 227 | Renan Teruo + Diego Araujo <renanteruoc@gmail.com> |
| 189 | 228 | Renan Teruo + Diego Araújo <renanteruoc@gmail.com> |
| 190 | 229 | Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com> |
| 191 | 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 | 232 | Rodrigo Souto <rodrigo@colivre.coop.br> |
| 194 | 233 | Ronny Kursawe <kursawe.ronny@googlemail.com> |
| 195 | 234 | root <root@debian.sdr.serpro> |
| 196 | 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 | 241 | Valessio Brito <valessio@gmail.com> |
| 198 | 242 | vfcosta <vfcosta@gmail.com> |
| 243 | +Victor Carvalho <victorhugodf.ac@gmail.com> | |
| 199 | 244 | Victor Costa <vfcosta@gmail.com> |
| 245 | +Victor Hugo Alves de Carvalho <victorhugodf.ac@gmail.com> | |
| 200 | 246 | Vinicius Cubas Brand <viniciuscb@gmail.com> |
| 201 | 247 | Visita <visita@debian.(none)> |
| 202 | 248 | Yann Lugrin <yann.lugrin@liquid-concept.ch> | ... | ... |
| ... | ... | @@ -0,0 +1,124 @@ |
| 1 | +# Noosfero Development Policy | |
| 2 | + | |
| 3 | +## Developer Roles | |
| 4 | + | |
| 5 | +* *Developers* are everyone that is contributing code to Noosfero. | |
| 6 | +* *Committers* are the people with direct commit access to the Noosfero source | |
| 7 | + code. They are responsible for reviewing contributions from other developers | |
| 8 | + and integrating them in the Noosfero code base. They are the members of the | |
| 9 | + [Noosfero group on Gitlab](https://gitlab.com/groups/noosfero/members). | |
| 10 | +* *Release managers* are the people that are managing the release of a new | |
| 11 | + Noosfero version and/or the maintainance work of an existing Noosfero stable | |
| 12 | + branch. See MAINTAINANCE.md for details on the maintaince policy. | |
| 13 | + | |
| 14 | +## Development process | |
| 15 | + | |
| 16 | +* Every new feature or non-trivial bugfix should be reviewed by at least one | |
| 17 | + committer. This must be the case even if the original author is a committer. | |
| 18 | + | |
| 19 | + * In the case the original author is a committer, he/she should feel free to | |
| 20 | + commit directly if after 1 week nobody has provided any kind of feedback. | |
| 21 | + | |
| 22 | + * Developers who are not committers should feel free to ping committers if | |
| 23 | + they do not get feedback on their contributions after 1 week. | |
| 24 | + | |
| 25 | + * On GitLab, one can just add a comment to the merge request; one can also | |
| 26 | + @-mention specific committers or other developers who have expertise on | |
| 27 | + the area of the contribution. | |
| 28 | + | |
| 29 | + * Committers should follow the activity of the project, and try to help | |
| 30 | + reviewing contributions from others as much as possible. | |
| 31 | + | |
| 32 | + * On GitLab one can get emails for all activity on a project by setting the | |
| 33 | + [notification settings](https://gitlab.com/profile/notifications) to | |
| 34 | + "watch". | |
| 35 | + | |
| 36 | + * Anyone can help by reviewing contributions. Committers are the only ones | |
| 37 | + who can give the final approval to a contribution, but everyone is welcome | |
| 38 | + to help with code review, testing, etc. | |
| 39 | + | |
| 40 | + * See note above about setting up notification on GitLab. | |
| 41 | + | |
| 42 | +* Committers should feel free to push trivial (or urgent) changes directly. | |
| 43 | + There are no strict rule on what makes a change trivial or urgent; committers | |
| 44 | + are expected to exercise good judgement on a case by case basis. | |
| 45 | + | |
| 46 | + * Usually changes to the database are not trivial. | |
| 47 | + | |
| 48 | +* In the case of unsolvable conflict between commiters regarding any change to | |
| 49 | + the code, the current release manager(s) will have the final say in the | |
| 50 | + matter. | |
| 51 | + | |
| 52 | +* Release managers are responsible for stablishing a release schedule, and | |
| 53 | + about deciding when and what to release. | |
| 54 | + | |
| 55 | + * Release managers should announce release schedules to the project mailing | |
| 56 | + lists in advance. | |
| 57 | + | |
| 58 | + * The release schedule may include a period of feature freeze, during which | |
| 59 | + no new features or any other changes that are not pre-approved by the | |
| 60 | + release manager must be committed to the repository. | |
| 61 | + | |
| 62 | + * Committers must respect the release schedule and feature freezes. | |
| 63 | + | |
| 64 | +## Maintainance process | |
| 65 | + | |
| 66 | +### Not all feature releases will be maintained as a stable release | |
| 67 | + | |
| 68 | +We will be choosing specific release series to be maintained as stable | |
| 69 | +releases. | |
| 70 | + | |
| 71 | +This means that a given release is not guaranteed to be maintained as a stable | |
| 72 | +release, but does *not* mean it won't be. Any committer (or anyone, really) can | |
| 73 | +decide to maintain a given release as stable and seek help from others to do | |
| 74 | +so. | |
| 75 | + | |
| 76 | +### No merges from stable branches to master | |
| 77 | + | |
| 78 | +*All* changes must be submitted against the master branch first, and when | |
| 79 | +applicable, backported to the desired stable releases. Exceptions to this rules | |
| 80 | +are bug fixes that only apply to a given stable branch and not to master. | |
| 81 | + | |
| 82 | +In the past we had non-trivial changes accepted into stable releases while | |
| 83 | +master was way ahead (e.g. during the rails3 migration period), that made the | |
| 84 | +merge back into master very painful. By eliminating the need to do these | |
| 85 | +merges, we save time for the people responsible for the release, and eliminate | |
| 86 | +the possibility of human errors or oversights causing changes to be accepted | |
| 87 | +into stable that will be a problem to merge back into master. | |
| 88 | + | |
| 89 | +By getting all fixes in master first, we improve the chances that a future | |
| 90 | +release will not present regressions against bugs that should already be fixed, | |
| 91 | +but the fixes got lost in a big, complicated merge (and those won't exist | |
| 92 | +anymore, at least not from stable branches to master). | |
| 93 | + | |
| 94 | +After a fix gets into master, backporting changes into a stable release branch | |
| 95 | +is the responsibility of whoever is maintaing that branch, and those interested | |
| 96 | +in it. The stable branch release manager(s) are entitled the final say on any | |
| 97 | +matters related to that branch. | |
| 98 | + | |
| 99 | +## Apendix A: how to become a committer | |
| 100 | + | |
| 101 | +Every developer that wants to be a committer should create [an issue on | |
| 102 | +Gitlab](https://gitlab.com/noosfero/noosfero/issues) requesting to be added as | |
| 103 | +a committer. This request must include information about the requestor's | |
| 104 | +previous contributions to the project. | |
| 105 | + | |
| 106 | +If 2 or more commiters consider second the request, the requestor is accepted | |
| 107 | +as new commiter and added to the Noosfero group. | |
| 108 | + | |
| 109 | +The existing committers are free to choose whatever criteria they want to | |
| 110 | +second the request, but they must be sure that the new committer is a | |
| 111 | +responsible developer and knows what she/he is doing. They must be aware that | |
| 112 | +seconding these requests means seconding the actions of the new committer: if | |
| 113 | +the new committer screw up, her/his seconds screwed up. | |
| 114 | + | |
| 115 | +## Apendix B: how to become a release manager | |
| 116 | + | |
| 117 | +A new release manager for the development version of Noosfero (i.e. the one | |
| 118 | +that includes new features, a.k.a. the master branch) is apointed by the | |
| 119 | +current release manager, and must be a committer first. | |
| 120 | + | |
| 121 | +Release managers for stable branches are self-appointed, i.e. whoever takes the | |
| 122 | +work takes the role. In case of a conflict (e.g. 2+ different people want to do | |
| 123 | +the work but can't agree on working together), the development release manager | |
| 124 | +decides. | ... | ... |
Gemfile
| 1 | 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 | 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 | 26 | # FIXME list here all actual dependencies (i.e. the ones in debian/control), |
| 22 | 27 | # with their GEM names (not the Debian package names) |
| 23 | 28 | |
| 24 | 29 | group :production do |
| 25 | - gem 'dalli' | |
| 30 | + gem 'dalli', '~> 2.7.0' | |
| 26 | 31 | end |
| 27 | 32 | |
| 28 | 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 | 37 | end |
| 33 | 38 | |
| 34 | 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 | 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 | 52 | end | ... | ... |
Gemfile.lock
| ... | ... | @@ -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 | 3 | This document assumes that you have a fully and clean Noosfero |
| 5 | 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 | 8 | You should get a valid SSL certificate, but if you want to test |
| 11 | 9 | your setup before, you could generate a self-signed certificate |
| ... | ... | @@ -17,99 +15,106 @@ as below: |
| 17 | 15 | # openssl req -new -x509 -nodes -sha1 -days $[10*365] -key noosfero.key > noosfero.cert |
| 18 | 16 | # cat noosfero.key noosfero.cert > noosfero.pem |
| 19 | 17 | |
| 18 | +## Web server configuration | |
| 19 | + | |
| 20 | 20 | There are two ways of using SSL with Noosfero: 1) If you are not using |
| 21 | 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 | 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 | 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 | 46 | Be aware that if you had configured varnish, the requests won't reach |
| 47 | 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 | 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 | 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 | 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 | 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 | 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 | +``` | ... | ... |
| ... | ... | @@ -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 | + | ... | ... |
INSTALL.md
| ... | ... | @@ -186,8 +186,8 @@ Apache instalation |
| 186 | 186 | |
| 187 | 187 | # apt-get install apache2 |
| 188 | 188 | |
| 189 | -Apache configuration | |
| 190 | --------------------- | |
| 189 | +Configuration - noosfero at / | |
| 190 | +----------------------------- | |
| 191 | 191 | |
| 192 | 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 | 257 | |
| 258 | 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 | 317 | Enabling exception notifications |
| 262 | 318 | ================================ | ... | ... |
MIGRATION_ISSUES
| ... | ... | @@ -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 |
Rakefile
| ... | ... | @@ -6,3 +6,13 @@ |
| 6 | 6 | require File.expand_path('../config/application', __FILE__) |
| 7 | 7 | |
| 8 | 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 | ... | ... |
Vagrantfile
| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | |
| 4 | 4 | VAGRANTFILE_API_VERSION = "2" |
| 5 | 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 | 7 | config.vm.network :forwarded_port, host: 3000, guest: 3000 |
| 8 | 8 | config.vm.provision :shell do |shell| |
| 9 | 9 | shell.inline = 'su vagrant -c /vagrant/script/vagrant' | ... | ... |
app/controllers/admin/admin_panel_controller.rb
| ... | ... | @@ -71,4 +71,22 @@ class AdminPanelController < AdminController |
| 71 | 71 | end |
| 72 | 72 | end |
| 73 | 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 | 92 | end | ... | ... |
app/controllers/admin/categories_controller.rb
| ... | ... | @@ -45,9 +45,11 @@ class CategoriesController < AdminController |
| 45 | 45 | if request.post? |
| 46 | 46 | @category.update_attributes!(params[:category]) |
| 47 | 47 | @saved = true |
| 48 | + session[:notice] = _("Category %s saved." % @category.name) | |
| 48 | 49 | redirect_to :action => 'index' |
| 49 | 50 | end |
| 50 | 51 | rescue Exception => e |
| 52 | + session[:notice] = _('Could not save category.') | |
| 51 | 53 | render :action => 'edit' |
| 52 | 54 | end |
| 53 | 55 | end | ... | ... |
app/controllers/admin/environment_design_controller.rb
| ... | ... | @@ -3,9 +3,7 @@ class EnvironmentDesignController < BoxOrganizerController |
| 3 | 3 | protect 'edit_environment_design', :environment |
| 4 | 4 | |
| 5 | 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 | 7 | @available_blocks += plugins.dispatch(:extra_blocks, :type => Environment) |
| 10 | 8 | end |
| 11 | 9 | ... | ... |
app/controllers/admin/features_controller.rb
| ... | ... | @@ -51,4 +51,10 @@ class FeaturesController < AdminController |
| 51 | 51 | redirect_to :action => 'manage_fields' |
| 52 | 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 | 60 | end | ... | ... |
app/controllers/admin/region_validators_controller.rb
| ... | ... | @@ -33,7 +33,7 @@ class RegionValidatorsController < AdminController |
| 33 | 33 | def load_region_and_search |
| 34 | 34 | @region = environment.regions.find(params[:id]) |
| 35 | 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 | 37 | end |
| 38 | 38 | end |
| 39 | 39 | ... | ... |
app/controllers/admin/templates_controller.rb
| ... | ... | @@ -40,8 +40,67 @@ class TemplatesController < AdminController |
| 40 | 40 | end |
| 41 | 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 | 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 | 104 | def create_organization_template(klass) |
| 46 | 105 | identifier = params[:name].to_slug |
| 47 | 106 | template = klass.new(:name => params[:name], :identifier => identifier, :is_template => true) | ... | ... |
app/controllers/admin/users_controller.rb
| ... | ... | @@ -18,7 +18,7 @@ class UsersController < AdminController |
| 18 | 18 | end |
| 19 | 19 | scope = scope.order('name ASC') |
| 20 | 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 | 22 | end |
| 23 | 23 | |
| 24 | 24 | def set_admin_role | ... | ... |
app/controllers/application_controller.rb
| ... | ... | @@ -7,6 +7,12 @@ class ApplicationController < ActionController::Base |
| 7 | 7 | before_filter :detect_stuff_by_domain |
| 8 | 8 | before_filter :init_noosfero_plugins |
| 9 | 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 | 17 | after_filter :set_csrf_cookie |
| 12 | 18 | |
| ... | ... | @@ -34,7 +40,7 @@ class ApplicationController < ActionController::Base |
| 34 | 40 | |
| 35 | 41 | theme_layout = theme_option(:layout) |
| 36 | 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 | 44 | else |
| 39 | 45 | 'application' |
| 40 | 46 | end |
| ... | ... | @@ -121,6 +127,9 @@ class ApplicationController < ActionController::Base |
| 121 | 127 | |
| 122 | 128 | # TODO: move this logic somewhere else (Domain class?) |
| 123 | 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 | 133 | @domain = Domain.find_by_name(request.host) |
| 125 | 134 | if @domain.nil? |
| 126 | 135 | @environment = Environment.default |
| ... | ... | @@ -174,17 +183,19 @@ class ApplicationController < ActionController::Base |
| 174 | 183 | end |
| 175 | 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 | 196 | end |
| 189 | 197 | |
| 198 | + def private_environment? | |
| 199 | + @environment.enabled?(:restrict_to_members) | |
| 200 | + end | |
| 190 | 201 | end | ... | ... |
app/controllers/my_profile/cms_controller.rb
| ... | ... | @@ -4,6 +4,12 @@ class CmsController < MyProfileController |
| 4 | 4 | |
| 5 | 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 | 13 | def self.protect_if(*args) |
| 8 | 14 | before_filter(*args) do |c| |
| 9 | 15 | user, profile = c.send(:user), c.send(:profile) |
| ... | ... | @@ -17,6 +23,9 @@ class CmsController < MyProfileController |
| 17 | 23 | end |
| 18 | 24 | |
| 19 | 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 | 30 | protect_if :only => :upload_files do |c, user, profile| |
| 22 | 31 | article_id = c.params[:parent_id] |
| ... | ... | @@ -24,7 +33,7 @@ class CmsController < MyProfileController |
| 24 | 33 | (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))) |
| 25 | 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 | 37 | user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile)) |
| 29 | 38 | end |
| 30 | 39 | |
| ... | ... | @@ -34,7 +43,7 @@ class CmsController < MyProfileController |
| 34 | 43 | (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))) |
| 35 | 44 | end |
| 36 | 45 | |
| 37 | - protect_if :only => [:destroy, :publish] do |c, user, profile| | |
| 46 | + protect_if :only => :destroy do |c, user, profile| | |
| 38 | 47 | profile.articles.find(c.params[:id]).allow_post_content?(user) |
| 39 | 48 | end |
| 40 | 49 | |
| ... | ... | @@ -111,7 +120,7 @@ class CmsController < MyProfileController |
| 111 | 120 | @success_back_to = params[:success_back_to] |
| 112 | 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 | 124 | record_coming |
| 116 | 125 | @type = params[:type] |
| 117 | 126 | if @type.blank? |
| ... | ... | @@ -157,7 +166,10 @@ class CmsController < MyProfileController |
| 157 | 166 | if continue |
| 158 | 167 | redirect_to :action => 'edit', :id => @article |
| 159 | 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 | 173 | end |
| 162 | 174 | return |
| 163 | 175 | end |
| ... | ... | @@ -168,6 +180,8 @@ class CmsController < MyProfileController |
| 168 | 180 | |
| 169 | 181 | post_only :set_home_page |
| 170 | 182 | def set_home_page |
| 183 | + return render_access_denied unless user.can_change_homepage? | |
| 184 | + | |
| 171 | 185 | article = params[:id].nil? ? nil : profile.articles.find(params[:id]) |
| 172 | 186 | profile.update_attribute(:home_page, article) |
| 173 | 187 | |
| ... | ... | @@ -206,6 +220,7 @@ class CmsController < MyProfileController |
| 206 | 220 | if @errors.any? |
| 207 | 221 | render :action => 'upload_files', :parent_id => @parent_id |
| 208 | 222 | else |
| 223 | + session[:notice] = _('File(s) successfully uploaded') | |
| 209 | 224 | if @back_to |
| 210 | 225 | redirect_to @back_to |
| 211 | 226 | elsif @parent |
| ... | ... | @@ -221,7 +236,7 @@ class CmsController < MyProfileController |
| 221 | 236 | @article = profile.articles.find(params[:id]) |
| 222 | 237 | if request.post? |
| 223 | 238 | @article.destroy |
| 224 | - session[:notice] = _("\"#{@article.name}\" was removed.") | |
| 239 | + session[:notice] = _("\"%s\" was removed." % @article.name) | |
| 225 | 240 | referer = Rails.application.routes.recognize_path URI.parse(request.referer).path rescue nil |
| 226 | 241 | if referer and referer[:controller] == 'cms' and referer[:action] != 'edit' |
| 227 | 242 | redirect_to referer |
| ... | ... | @@ -247,28 +262,53 @@ class CmsController < MyProfileController |
| 247 | 262 | render :template => 'shared/update_categories', :locals => { :category => @current_category, :object_name => 'article' } |
| 248 | 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 | 269 | def publish |
| 251 | 270 | @article = profile.articles.find(params[:id]) |
| 252 | 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 | 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 | 294 | if request.post? |
| 295 | + @back_to = params[:back_to] | |
| 296 | + @article = profile.articles.find(params[:id]) | |
| 262 | 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 | 301 | if @marked_groups.empty? |
| 302 | + redirect_to @back_to | |
| 264 | 303 | return session[:notice] = _("Select some group to publish your article") |
| 265 | 304 | end |
| 266 | 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 | 307 | begin |
| 269 | - task.finish unless item[:group].moderated_articles? | |
| 308 | + task.finish unless item.moderated_articles? | |
| 270 | 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 | 312 | end |
| 273 | 313 | end |
| 274 | 314 | if @failed.blank? |
| ... | ... | @@ -278,23 +318,27 @@ class CmsController < MyProfileController |
| 278 | 318 | else |
| 279 | 319 | redirect_to @article.view_url |
| 280 | 320 | end |
| 321 | + else | |
| 322 | + session[:notice] = _("Some of your publish requests couldn't be sent.") | |
| 323 | + render :action => 'publish' | |
| 281 | 324 | end |
| 282 | 325 | end |
| 283 | 326 | end |
| 284 | 327 | |
| 285 | 328 | def publish_on_portal_community |
| 286 | - @article = profile.articles.find(params[:id]) | |
| 287 | 329 | if request.post? |
| 288 | - if environment.portal_community | |
| 330 | + @article = profile.articles.find(params[:id]) | |
| 331 | + if environment.portal_enabled | |
| 289 | 332 | task = ApproveArticle.create!(:article => @article, :name => params[:name], :target => environment.portal_community, :requestor => user) |
| 290 | 333 | begin |
| 291 | 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 | 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 | 339 | end |
| 296 | 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 | 342 | end |
| 299 | 343 | |
| 300 | 344 | if @back_to |
| ... | ... | @@ -322,7 +366,7 @@ class CmsController < MyProfileController |
| 322 | 366 | |
| 323 | 367 | def search |
| 324 | 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 | 370 | render :text => article_list_to_json(results), :content_type => 'application/json' |
| 327 | 371 | end |
| 328 | 372 | |
| ... | ... | @@ -333,15 +377,26 @@ class CmsController < MyProfileController |
| 333 | 377 | end |
| 334 | 378 | |
| 335 | 379 | def media_upload |
| 336 | - files_uploaded = [] | |
| 337 | 380 | parent = check_parent(params[:parent_id]) |
| 338 | - files = [:file1,:file2, :file3].map { |f| params[f] }.compact | |
| 339 | 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 | 387 | end |
| 343 | 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 | 400 | end |
| 346 | 401 | |
| 347 | 402 | protected |
| ... | ... | @@ -436,4 +491,36 @@ class CmsController < MyProfileController |
| 436 | 491 | end |
| 437 | 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 | 526 | end | ... | ... |
| ... | ... | @@ -3,6 +3,7 @@ class FriendsController < MyProfileController |
| 3 | 3 | protect 'manage_friends', :profile |
| 4 | 4 | |
| 5 | 5 | def index |
| 6 | + @suggestions = profile.profile_suggestions.of_person.enabled.includes(:suggestion).limit(per_page) | |
| 6 | 7 | if is_cache_expired?(profile.manage_friends_cache_key(params)) |
| 7 | 8 | @friends = profile.friends.paginate(:per_page => per_page, :page => params[:npage]) |
| 8 | 9 | end |
| ... | ... | @@ -16,11 +17,35 @@ class FriendsController < MyProfileController |
| 16 | 17 | end |
| 17 | 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 | 44 | protected |
| 20 | 45 | |
| 21 | 46 | class << self |
| 22 | 47 | def per_page |
| 23 | - 10 | |
| 48 | + 12 | |
| 24 | 49 | end |
| 25 | 50 | end |
| 26 | 51 | def per_page | ... | ... |
app/controllers/my_profile/memberships_controller.rb
| ... | ... | @@ -20,9 +20,54 @@ class MembershipsController < MyProfileController |
| 20 | 20 | @community.environment = environment |
| 21 | 21 | @back_to = params[:back_to] || url_for(:action => 'index') |
| 22 | 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 | 33 | return |
| 26 | 34 | end |
| 27 | 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 | 73 | end | ... | ... |
app/controllers/my_profile/profile_design_controller.rb
| ... | ... | @@ -3,7 +3,16 @@ class ProfileDesignController < BoxOrganizerController |
| 3 | 3 | needs_profile |
| 4 | 4 | |
| 5 | 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 | 16 | def available_blocks |
| 8 | 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 < MyProfileController |
| 3 | 3 | protect 'edit_profile', :profile, :except => [:destroy_profile] |
| 4 | 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 | 10 | def index |
| 7 | 11 | @pending_tasks = Task.to(profile).pending.without_spam.select{|i| user.has_permission?(i.permission, profile)} |
| 8 | 12 | end |
| ... | ... | @@ -16,14 +20,16 @@ class ProfileEditorController < MyProfileController |
| 16 | 20 | if request.post? |
| 17 | 21 | params[:profile_data][:fields_privacy] ||= {} if profile.person? && params[:profile_data].is_a?(Hash) |
| 18 | 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 | 31 | end |
| 25 | 32 | end |
| 26 | - end | |
| 27 | 33 | end |
| 28 | 34 | end |
| 29 | 35 | |
| ... | ... | @@ -72,10 +78,81 @@ class ProfileEditorController < MyProfileController |
| 72 | 78 | if request.post? |
| 73 | 79 | if @profile.destroy |
| 74 | 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 | 86 | else |
| 77 | 87 | session[:notice] = _('Could not delete profile') |
| 78 | 88 | end |
| 79 | 89 | end |
| 80 | 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 | 158 | end | ... | ... |
app/controllers/my_profile/profile_members_controller.rb
| ... | ... | @@ -20,7 +20,7 @@ class ProfileMembersController < MyProfileController |
| 20 | 20 | redirect_to :action => :last_admin |
| 21 | 21 | elsif @person.define_roles(@roles, profile) |
| 22 | 22 | session[:notice] = _('Roles successfuly updated') |
| 23 | - redirect_to :controller => 'profile_editor' | |
| 23 | + redirect_to :action => 'index' | |
| 24 | 24 | else |
| 25 | 25 | session[:notice] = _('Couldn\'t change the roles') |
| 26 | 26 | redirect_to :action => 'index' | ... | ... |
app/controllers/my_profile/tasks_controller.rb
| ... | ... | @@ -4,6 +4,7 @@ class TasksController < MyProfileController |
| 4 | 4 | |
| 5 | 5 | def index |
| 6 | 6 | @filter = params[:filter_type].blank? ? nil : params[:filter_type] |
| 7 | + @task_types = Task.pending_types_for(profile) | |
| 7 | 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 | 9 | @failed = params ? params[:failed] : {} |
| 9 | 10 | end | ... | ... |
app/controllers/public/account_controller.rb
| ... | ... | @@ -15,11 +15,23 @@ class AccountController < ApplicationController |
| 15 | 15 | |
| 16 | 16 | def activate |
| 17 | 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 | 35 | else |
| 24 | 36 | session[:notice] = _("It looks like you're trying to activate an account. Perhaps have already activated this account?") |
| 25 | 37 | redirect_to :controller => :home |
| ... | ... | @@ -70,10 +82,12 @@ class AccountController < ApplicationController |
| 70 | 82 | if @plugins.dispatch(:allow_user_registration).include?(false) |
| 71 | 83 | redirect_back_or_default(:controller => 'home') |
| 72 | 84 | session[:notice] = _("This environment doesn't allow user registration.") |
| 85 | + return | |
| 73 | 86 | end |
| 74 | 87 | |
| 75 | 88 | store_location(request.referer) unless params[:return_to] or session[:return_to] |
| 76 | 89 | |
| 90 | + # Tranforming to boolean | |
| 77 | 91 | @block_bot = !!session[:may_be_a_bot] |
| 78 | 92 | @invitation_code = params[:invitation_code] |
| 79 | 93 | begin |
| ... | ... | @@ -85,6 +99,7 @@ class AccountController < ApplicationController |
| 85 | 99 | @user.return_to = session[:return_to] |
| 86 | 100 | @person = Person.new(params[:profile_data]) |
| 87 | 101 | @person.environment = @user.environment |
| 102 | + | |
| 88 | 103 | if request.post? |
| 89 | 104 | if may_be_a_bot |
| 90 | 105 | set_signup_start_time_for_now |
| ... | ... | @@ -103,12 +118,21 @@ class AccountController < ApplicationController |
| 103 | 118 | invitation.update_attributes!({:friend => @user.person}) |
| 104 | 119 | invitation.finish |
| 105 | 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 | 129 | if @user.activated? |
| 107 | 130 | self.current_user = @user |
| 108 | 131 | check_join_in_community(@user) |
| 109 | 132 | go_to_signup_initial_page |
| 110 | 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 | 136 | end |
| 113 | 137 | end |
| 114 | 138 | end |
| ... | ... | @@ -171,7 +195,7 @@ class AccountController < ApplicationController |
| 171 | 195 | else |
| 172 | 196 | @change_password.errors[:base] << _('Could not find any user with %s equal to "%s".') % [fields_label, params[:value]] |
| 173 | 197 | end |
| 174 | - rescue ActiveRecord::RecordInvald | |
| 198 | + rescue ActiveRecord::RecordInvalid | |
| 175 | 199 | @change_password.errors[:base] << _('Could not perform password recovery for the user.') |
| 176 | 200 | end |
| 177 | 201 | end |
| ... | ... | @@ -439,6 +463,8 @@ class AccountController < ApplicationController |
| 439 | 463 | redirect_to user.url |
| 440 | 464 | when 'user_control_panel' |
| 441 | 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 | 468 | else |
| 443 | 469 | redirect_back_or_default(default) |
| 444 | 470 | end | ... | ... |
app/controllers/public/chat_controller.rb
| ... | ... | @@ -19,9 +19,13 @@ class ChatController < PublicController |
| 19 | 19 | def avatar |
| 20 | 20 | profile = environment.profiles.find_by_identifier(params[:id]) |
| 21 | 21 | filename, mimetype = profile_icon(profile, :minor, true) |
| 22 | - 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 | 29 | end |
| 26 | 30 | |
| 27 | 31 | def index | ... | ... |
app/controllers/public/contact_controller.rb
| 1 | 1 | class ContactController < PublicController |
| 2 | 2 | |
| 3 | - before_filter :login_required | |
| 4 | - | |
| 5 | 3 | needs_profile |
| 6 | 4 | |
| 7 | 5 | def new |
| 8 | - @contact | |
| 6 | + @contact = build_contact | |
| 9 | 7 | if request.post? && params[:confirm] == 'true' |
| 10 | - @contact = user.build_contact(profile, params[:contact]) | |
| 11 | 8 | @contact.city = (!params[:city].blank? && City.exists?(params[:city])) ? City.find(params[:city]).name : nil |
| 12 | 9 | @contact.state = (!params[:state].blank? && State.exists?(params[:state])) ? State.find(params[:state]).name : nil |
| 13 | 10 | if @contact.deliver |
| ... | ... | @@ -16,8 +13,17 @@ class ContactController < PublicController |
| 16 | 13 | else |
| 17 | 14 | session[:notice] = _('Contact not sent') |
| 18 | 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 | 25 | else |
| 20 | - @contact = user.build_contact(profile) | |
| 26 | + Contact.new params[:contact].merge(dest: profile) | |
| 21 | 27 | end |
| 22 | 28 | end |
| 23 | 29 | ... | ... |
app/controllers/public/content_viewer_controller.rb
| ... | ... | @@ -126,7 +126,7 @@ class ContentViewerController < ApplicationController |
| 126 | 126 | elsif !@page.display_to?(user) |
| 127 | 127 | if !profile.public? |
| 128 | 128 | private_profile_partial_parameters |
| 129 | - render :template => 'profile/_private_profile', :status => 403 | |
| 129 | + render :template => 'profile/_private_profile', :status => 403, :formats => [:html] | |
| 130 | 130 | allowed = false |
| 131 | 131 | else #if !profile.visible? |
| 132 | 132 | render_access_denied |
| ... | ... | @@ -216,8 +216,6 @@ class ContentViewerController < ApplicationController |
| 216 | 216 | if @page.has_posts? |
| 217 | 217 | posts = get_posts(params[:year], params[:month]) |
| 218 | 218 | |
| 219 | - posts = posts.includes(:parent, {:profile => [:domains, :environment]}, :author) | |
| 220 | - | |
| 221 | 219 | #FIXME Need to run this before the pagination because this version of |
| 222 | 220 | # will_paginate returns a will_paginate collection instead of a |
| 223 | 221 | # relation. | ... | ... |
app/controllers/public/home_controller.rb
| ... | ... | @@ -18,4 +18,10 @@ class HomeController < PublicController |
| 18 | 18 | @no_design_blocks = true |
| 19 | 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 | 27 | end | ... | ... |
app/controllers/public/invite_controller.rb
| ... | ... | @@ -4,8 +4,15 @@ class InviteController < PublicController |
| 4 | 4 | before_filter :login_required |
| 5 | 5 | before_filter :check_permissions_to_invite |
| 6 | 6 | |
| 7 | - def select_address_book | |
| 7 | + def invite_friends | |
| 8 | 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 | 16 | if request.post? |
| 10 | 17 | contact_list = ContactList.create |
| 11 | 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 < PublicController |
| 22 | 29 | webmail_import_addresses = params[:webmail_import_addresses] |
| 23 | 30 | contacts_to_invite = Invitation.join_contacts(manual_import_addresses, webmail_import_addresses) |
| 24 | 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 | 33 | session[:notice] = _('Your invitations are being sent.') |
| 27 | 34 | if profile.person? |
| 28 | 35 | redirect_to :controller => 'profile', :action => 'friends' |
| ... | ... | @@ -52,16 +59,36 @@ class InviteController < PublicController |
| 52 | 59 | def cancel_fetching_emails |
| 53 | 60 | contact_list = ContactList.find(params[:contact_list]) |
| 54 | 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 | 87 | end |
| 57 | 88 | |
| 58 | 89 | protected |
| 59 | 90 | |
| 60 | 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 | 93 | end |
| 66 | - | |
| 67 | 94 | end | ... | ... |
app/controllers/public/profile_controller.rb
| ... | ... | @@ -17,7 +17,11 @@ class ProfileController < PublicController |
| 17 | 17 | end |
| 18 | 18 | @tags = profile.article_tags |
| 19 | 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 | 25 | end |
| 22 | 26 | end |
| 23 | 27 | |
| ... | ... | @@ -61,13 +65,13 @@ class ProfileController < PublicController |
| 61 | 65 | |
| 62 | 66 | def friends |
| 63 | 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 | 69 | end |
| 66 | 70 | end |
| 67 | 71 | |
| 68 | 72 | def members |
| 69 | 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 | 75 | end |
| 72 | 76 | end |
| 73 | 77 | |
| ... | ... | @@ -315,7 +319,7 @@ class ProfileController < PublicController |
| 315 | 319 | abuse_report = AbuseReport.new(params[:abuse_report]) |
| 316 | 320 | if !params[:content_type].blank? |
| 317 | 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 | 323 | end |
| 320 | 324 | |
| 321 | 325 | user.register_report(abuse_report, profile) |
| ... | ... | @@ -394,6 +398,7 @@ class ProfileController < PublicController |
| 394 | 398 | |
| 395 | 399 | def private_profile |
| 396 | 400 | private_profile_partial_parameters |
| 401 | + render :action => 'index', :status => 403 | |
| 397 | 402 | end |
| 398 | 403 | |
| 399 | 404 | def invisible_profile | ... | ... |
app/controllers/public/profile_search_controller.rb
| ... | ... | @@ -9,9 +9,12 @@ class ProfileSearchController < PublicController |
| 9 | 9 | @q = params[:q] |
| 10 | 10 | unless @q.blank? |
| 11 | 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 | 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 | 18 | end |
| 16 | 19 | end |
| 17 | 20 | end | ... | ... |
app/controllers/public/search_controller.rb
| ... | ... | @@ -4,11 +4,11 @@ class SearchController < PublicController |
| 4 | 4 | include SearchHelper |
| 5 | 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 | 13 | # Backwards compatibility with old URLs |
| 14 | 14 | def redirect_asset_param |
| ... | ... | @@ -20,7 +20,7 @@ class SearchController < PublicController |
| 20 | 20 | |
| 21 | 21 | def index |
| 22 | 22 | @searches = {} |
| 23 | - @order = [] | |
| 23 | + @assets = [] | |
| 24 | 24 | @names = {} |
| 25 | 25 | @results_only = true |
| 26 | 26 | |
| ... | ... | @@ -28,7 +28,7 @@ class SearchController < PublicController |
| 28 | 28 | load_query |
| 29 | 29 | @asset = key |
| 30 | 30 | send(key) |
| 31 | - @order << key | |
| 31 | + @assets << key | |
| 32 | 32 | @names[key] = _(description) |
| 33 | 33 | end |
| 34 | 34 | @asset = nil |
| ... | ... | @@ -42,7 +42,7 @@ class SearchController < PublicController |
| 42 | 42 | # view the summary of one category |
| 43 | 43 | def category_index |
| 44 | 44 | @searches = {} |
| 45 | - @order = [] | |
| 45 | + @assets = [] | |
| 46 | 46 | @names = {} |
| 47 | 47 | limit = MULTIPLE_SEARCH_LIMIT |
| 48 | 48 | [ |
| ... | ... | @@ -53,7 +53,7 @@ class SearchController < PublicController |
| 53 | 53 | [ :communities, _('Communities'), :recent_communities ], |
| 54 | 54 | [ :articles, _('Contents'), :recent_articles ] |
| 55 | 55 | ].each do |asset, name, filter| |
| 56 | - @order << asset | |
| 56 | + @assets << asset | |
| 57 | 57 | @searches[asset]= {:results => @category.send(filter, limit)} |
| 58 | 58 | raise "No total_entries for: #{asset}" unless @searches[asset][:results].respond_to?(:total_entries) |
| 59 | 59 | @names[asset] = name |
| ... | ... | @@ -90,10 +90,14 @@ class SearchController < PublicController |
| 90 | 90 | end |
| 91 | 91 | |
| 92 | 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 | 101 | date_range = (@date - 1.month).at_beginning_of_month..(@date + 1.month).at_end_of_month |
| 98 | 102 | |
| 99 | 103 | @events = [] |
| ... | ... | @@ -143,12 +147,16 @@ class SearchController < PublicController |
| 143 | 147 | render :partial => 'events/events' |
| 144 | 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 | 155 | protected |
| 148 | 156 | |
| 149 | 157 | def load_query |
| 150 | 158 | @asset = (params[:asset] || params[:action]).to_sym |
| 151 | - @order ||= [@asset] | |
| 159 | + @assets ||= [@asset] | |
| 152 | 160 | @searches ||= {} |
| 153 | 161 | |
| 154 | 162 | @query = params[:query] || '' |
| ... | ... | @@ -169,13 +177,22 @@ class SearchController < PublicController |
| 169 | 177 | end |
| 170 | 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 | 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 | 191 | render_not_found |
| 175 | 192 | return |
| 176 | 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 | 196 | @searching = {} |
| 180 | 197 | @titles = {} |
| 181 | 198 | @enabled_searches.each do |key, name| |
| ... | ... | @@ -185,11 +202,11 @@ class SearchController < PublicController |
| 185 | 202 | @names = @titles if @names.nil? |
| 186 | 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 | 210 | end |
| 194 | 211 | end |
| 195 | 212 | |
| ... | ... | @@ -213,7 +230,7 @@ class SearchController < PublicController |
| 213 | 230 | end |
| 214 | 231 | |
| 215 | 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 | 234 | end |
| 218 | 235 | |
| 219 | 236 | private |
| ... | ... | @@ -228,4 +245,14 @@ class SearchController < PublicController |
| 228 | 245 | 20 |
| 229 | 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 | 258 | end | ... | ... |
app/helpers/application_helper.rb
| ... | ... | @@ -304,7 +304,7 @@ module ApplicationHelper |
| 304 | 304 | def partial_for_class(klass, prefix=nil, suffix=nil) |
| 305 | 305 | raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil? |
| 306 | 306 | name = klass.name.underscore |
| 307 | - controller.view_paths.reverse_each do |view_path| | |
| 307 | + controller.view_paths.each do |view_path| | |
| 308 | 308 | partial = partial_for_class_in_view_path(klass, view_path, prefix, suffix) |
| 309 | 309 | return partial if partial |
| 310 | 310 | end |
| ... | ... | @@ -433,19 +433,19 @@ module ApplicationHelper |
| 433 | 433 | end |
| 434 | 434 | |
| 435 | 435 | def theme_site_title |
| 436 | - theme_include('site_title') | |
| 436 | + @theme_site_title ||= theme_include 'site_title' | |
| 437 | 437 | end |
| 438 | 438 | |
| 439 | 439 | def theme_header |
| 440 | - theme_include('header') | |
| 440 | + @theme_header ||= theme_include 'header' | |
| 441 | 441 | end |
| 442 | 442 | |
| 443 | 443 | def theme_footer |
| 444 | - theme_include('footer') | |
| 444 | + @theme_footer ||= theme_include 'footer' | |
| 445 | 445 | end |
| 446 | 446 | |
| 447 | 447 | def theme_extra_navigation |
| 448 | - theme_include('navigation') | |
| 448 | + @theme_extra_navigation ||= theme_include 'navigation' | |
| 449 | 449 | end |
| 450 | 450 | |
| 451 | 451 | def is_testing_theme |
| ... | ... | @@ -482,7 +482,12 @@ module ApplicationHelper |
| 482 | 482 | '/images/icons-app/enterprise-'+ size.to_s() +'.png' |
| 483 | 483 | end |
| 484 | 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 | 491 | end |
| 487 | 492 | filename = default_or_themed_icon(icon) |
| 488 | 493 | end |
| ... | ... | @@ -602,7 +607,7 @@ module ApplicationHelper |
| 602 | 607 | end |
| 603 | 608 | |
| 604 | 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 | 611 | end |
| 607 | 612 | |
| 608 | 613 | attr_reader :environment |
| ... | ... | @@ -669,13 +674,14 @@ module ApplicationHelper |
| 669 | 674 | html.join "\n" |
| 670 | 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 | 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 | 685 | end |
| 680 | 686 | |
| 681 | 687 | def file_field_or_thumbnail(label, image, i) |
| ... | ... | @@ -856,8 +862,9 @@ module ApplicationHelper |
| 856 | 862 | end |
| 857 | 863 | |
| 858 | 864 | def base_url |
| 859 | - environment.top_url | |
| 865 | + environment.top_url(request.scheme) | |
| 860 | 866 | end |
| 867 | + alias :top_url :base_url | |
| 861 | 868 | |
| 862 | 869 | def helper_for_article(article) |
| 863 | 870 | article_helper = ActionView::Base.new |
| ... | ... | @@ -902,13 +909,15 @@ module ApplicationHelper |
| 902 | 909 | end |
| 903 | 910 | |
| 904 | 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 | 921 | end |
| 913 | 922 | |
| 914 | 923 | # DEPRECATED. Do not use this. |
| ... | ... | @@ -937,9 +946,9 @@ module ApplicationHelper |
| 937 | 946 | # from Article model for an ArticleBlock. |
| 938 | 947 | def reference_to_article(text, article, anchor=nil) |
| 939 | 948 | if article.profile.domains.empty? |
| 940 | - href = "/#{article.url[:profile]}/" | |
| 949 | + href = "#{Noosfero.root}/#{article.url[:profile]}/" | |
| 941 | 950 | else |
| 942 | - href = "http://#{article.profile.domains.first.name}/" | |
| 951 | + href = "http://#{article.profile.domains.first.name}#{Noosfero.root}/" | |
| 943 | 952 | end |
| 944 | 953 | href += article.url[:page].join('/') |
| 945 | 954 | href += '#' + anchor if anchor |
| ... | ... | @@ -1280,11 +1289,13 @@ module ApplicationHelper |
| 1280 | 1289 | end |
| 1281 | 1290 | |
| 1282 | 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 | 1299 | end |
| 1289 | 1300 | |
| 1290 | 1301 | def expirable_link_to(expired, content, url, options = {}) |
| ... | ... | @@ -1296,8 +1307,19 @@ module ApplicationHelper |
| 1296 | 1307 | end |
| 1297 | 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 | 1323 | end |
| 1302 | 1324 | |
| 1303 | 1325 | def template_options(kind, field_name) |
| ... | ... | @@ -1305,10 +1327,8 @@ module ApplicationHelper |
| 1305 | 1327 | return '' if templates.count == 0 |
| 1306 | 1328 | return hidden_field_tag("#{field_name}[template_id]", templates.first.id) if templates.count == 1 |
| 1307 | 1329 | |
| 1308 | - counter = 0 | |
| 1309 | 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 | 1332 | end.join("\n") |
| 1313 | 1333 | |
| 1314 | 1334 | content_tag('div', content_tag('label', _('Profile organization'), :for => 'template-options', :class => 'formlabel') + |
| ... | ... | @@ -1372,7 +1392,7 @@ module ApplicationHelper |
| 1372 | 1392 | # are old things that do not support it we are keeping this hot spot. |
| 1373 | 1393 | html = @plugins.pipeline(:parse_content, html, source).first |
| 1374 | 1394 | end |
| 1375 | - html | |
| 1395 | + html && html.html_safe | |
| 1376 | 1396 | end |
| 1377 | 1397 | |
| 1378 | 1398 | def convert_macro(html, source) |
| ... | ... | @@ -1402,4 +1422,51 @@ module ApplicationHelper |
| 1402 | 1422 | content_tag('ul', article.versions.map {|v| link_to("r#{v.version}", @page.url.merge(:version => v.version))}) |
| 1403 | 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 | 1472 | end | ... | ... |
app/helpers/article_helper.rb
| ... | ... | @@ -3,6 +3,12 @@ module ArticleHelper |
| 3 | 3 | include PrototypeHelper |
| 4 | 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 | 12 | def custom_options_for_article(article, tokenized_children) |
| 7 | 13 | @article = article |
| 8 | 14 | |
| ... | ... | @@ -71,18 +77,69 @@ module ArticleHelper |
| 71 | 77 | content_tag('div', |
| 72 | 78 | radio_button(:article, :published, false) + |
| 73 | 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 | 133 | end |
| 81 | 134 | |
| 82 | 135 | def prepare_to_token_input(array) |
| 83 | 136 | array.map { |object| {:id => object.id, :name => object.name} } |
| 84 | 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 | 143 | def cms_label_for_new_children |
| 87 | 144 | _('New article') |
| 88 | 145 | end | ... | ... |
app/helpers/boxes_helper.rb
| ... | ... | @@ -170,49 +170,54 @@ module BoxesHelper |
| 170 | 170 | else |
| 171 | 171 | "before-block-#{block.id}" |
| 172 | 172 | end |
| 173 | - | |
| 174 | - content_tag('div', ' ', :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', ' ', :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 | 178 | end |
| 176 | 179 | |
| 177 | 180 | # makes the given block draggable so it can be moved away. |
| 178 | 181 | def block_handle(block) |
| 179 | - draggable_element("block-#{block.id}", :revert => true) | |
| 182 | + modifiable?(block) ? draggable_element("block-#{block.id}", :revert => true) : "" | |
| 180 | 183 | end |
| 181 | 184 | |
| 182 | 185 | def block_edit_buttons(block) |
| 183 | 186 | buttons = [] |
| 184 | 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 | 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 | 221 | end |
| 217 | 222 | |
| 218 | 223 | if block.respond_to?(:help) |
| ... | ... | @@ -248,5 +253,7 @@ module BoxesHelper |
| 248 | 253 | classes |
| 249 | 254 | end |
| 250 | 255 | |
| 251 | - | |
| 256 | + def modifiable?(block) | |
| 257 | + return !block.fixed || environment.admins.include?(user) | |
| 258 | + end | |
| 252 | 259 | end | ... | ... |
app/helpers/categories_helper.rb
| 1 | 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 | 3 | TYPES = [ |
| 24 | 4 | [ _('General Category'), Category.to_s ], |
| 25 | 5 | [ _('Product Category'), ProductCategory.to_s ], |
| 26 | 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 | 9 | def select_category_type(field) |
| 47 | 10 | value = params[field] |
| 48 | 11 | labelled_form_field(_('Type of category'), select_tag('type', options_for_select(TYPES, value))) |
| 49 | 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 | 19 | #FIXME make this test |
| 52 | 20 | def selected_category_link(cat) |
| 53 | 21 | js_remove = "jQuery('#selected-category-#{cat.id}').remove();" | ... | ... |
app/helpers/cms_helper.rb
| ... | ... | @@ -40,12 +40,8 @@ module CmsHelper |
| 40 | 40 | end |
| 41 | 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 | 45 | end |
| 50 | 46 | |
| 51 | 47 | def display_delete_button(article) | ... | ... |
app/helpers/content_viewer_helper.rb
| ... | ... | @@ -10,7 +10,7 @@ module ContentViewerHelper |
| 10 | 10 | end |
| 11 | 11 | |
| 12 | 12 | def number_of_comments(article) |
| 13 | - display_number_of_comments(article.comments_count - article.spam_comments_count) | |
| 13 | + display_number_of_comments(article.comments_count - article.spam_comments_count.to_i) | |
| 14 | 14 | end |
| 15 | 15 | |
| 16 | 16 | def article_title(article, args = {}) |
| ... | ... | @@ -45,7 +45,7 @@ module ContentViewerHelper |
| 45 | 45 | { article.environment.locales[translation.language] => { :href => url_for(translation.url) } } |
| 46 | 46 | end |
| 47 | 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 | 49 | :class => 'article-translations-menu simplemenu-trigger up'), |
| 50 | 50 | :class => 'article-translations') |
| 51 | 51 | end | ... | ... |
app/helpers/forms_helper.rb
| ... | ... | @@ -265,7 +265,7 @@ module FormsHelper |
| 265 | 265 | ) |
| 266 | 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 | 269 | if find_options.empty? |
| 270 | 270 | folders = profile.folders |
| 271 | 271 | else |
| ... | ... | @@ -276,7 +276,7 @@ module FormsHelper |
| 276 | 276 | select_tag( |
| 277 | 277 | field_id, |
| 278 | 278 | options_for_select( |
| 279 | - [[profile.identifier, '']] + | |
| 279 | + [[(extra_options[:root_label] || profile.identifier), '']] + | |
| 280 | 280 | folders.collect {|f| [ profile.identifier + '/' + f.full_name, f.id.to_s ] }, |
| 281 | 281 | default_value.to_s |
| 282 | 282 | ), | ... | ... |
app/helpers/layout_helper.rb
| ... | ... | @@ -2,12 +2,31 @@ module LayoutHelper |
| 2 | 2 | |
| 3 | 3 | def body_classes |
| 4 | 4 | # Identify the current controller and action for the CSS: |
| 5 | + (logged_in? ? " logged-in" : "") + | |
| 5 | 6 | " controller-#{controller.controller_name}" + |
| 6 | 7 | " action-#{controller.controller_name}-#{controller.action_name}" + |
| 7 | 8 | " template-#{@layout_template || if profile.blank? then 'default' else profile.layout_template end}" + |
| 8 | 9 | (!profile.nil? && profile.is_on_homepage?(request.path,@page) ? " profile-homepage" : "") |
| 9 | 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 | 30 | def noosfero_javascript |
| 12 | 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 | 36 | unless plugins_javascripts.empty? |
| 18 | 37 | output += javascript_include_tag plugins_javascripts, :cache => "cache/plugins-#{Digest::MD5.hexdigest plugins_javascripts.to_s}" |
| 19 | 38 | end |
| 39 | + output += theme_javascript_ng.to_s | |
| 40 | + | |
| 20 | 41 | output |
| 21 | 42 | end |
| 22 | 43 | |
| ... | ... | @@ -27,6 +48,8 @@ module LayoutHelper |
| 27 | 48 | 'thickbox', |
| 28 | 49 | 'lightbox', |
| 29 | 50 | 'colorbox', |
| 51 | + 'selectordie', | |
| 52 | + 'inputosaurus', | |
| 30 | 53 | pngfix_stylesheet_path, |
| 31 | 54 | ] + tokeninput_stylesheets |
| 32 | 55 | plugins_stylesheets = @plugins.select(&:stylesheet?).map { |plugin| plugin.class.public_path('style.css') } |
| ... | ... | @@ -83,6 +106,10 @@ module LayoutHelper |
| 83 | 106 | theme_path + '/style.css' |
| 84 | 107 | end |
| 85 | 108 | |
| 109 | + def layout_template | |
| 110 | + if profile then profile.layout_template else environment.layout_template end | |
| 111 | + end | |
| 112 | + | |
| 86 | 113 | def addthis_javascript |
| 87 | 114 | if NOOSFERO_CONF['addthis_enabled'] |
| 88 | 115 | '<script src="https://s7.addthis.com/js/152/addthis_widget.js"></script>' |
| ... | ... | @@ -90,7 +117,7 @@ module LayoutHelper |
| 90 | 117 | end |
| 91 | 118 | |
| 92 | 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 | 121 | end |
| 95 | 122 | end |
| 96 | 123 | ... | ... |
app/helpers/person_notifier_helper.rb
app/helpers/profile_helper.rb
| 1 | 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 | 74 | unless force || profile.may_display_field_to?(field, user) |
| 5 | 75 | return '' |
| 6 | 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 | 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 | 90 | end |
| 16 | 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 | 132 | end |
| 31 | 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 | 188 | else |
| 39 | - content_tag('tr', content_tag('th', _('Work'), { :colspan => 2 })) + organization + organization_site | |
| 189 | + super | |
| 40 | 190 | end |
| 41 | 191 | end |
| 42 | 192 | ... | ... |
app/helpers/role_helper.rb
app/helpers/search_helper.rb
| ... | ... | @@ -5,22 +5,31 @@ module SearchHelper |
| 5 | 5 | BLOCKS_SEARCH_LIMIT = 24 |
| 6 | 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 | 33 | # FIXME remove it after search_controler refactored |
| 25 | 34 | include EventsHelper |
| 26 | 35 | |
| ... | ... | @@ -50,7 +59,7 @@ module SearchHelper |
| 50 | 59 | end |
| 51 | 60 | |
| 52 | 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 | 63 | end |
| 55 | 64 | |
| 56 | 65 | def display_results(searches=nil, asset=nil) |
| ... | ... | @@ -87,6 +96,16 @@ module SearchHelper |
| 87 | 96 | end |
| 88 | 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 | 109 | def display_selector(asset, display, float = 'right') |
| 91 | 110 | display = nil if display.blank? |
| 92 | 111 | display ||= asset_class(asset).default_search_display |
| ... | ... | @@ -94,43 +113,39 @@ module SearchHelper |
| 94 | 113 | compact_link = display?(asset, :compact) ? (display == 'compact' ? _('Compact') : link_to(_('Compact'), params.merge(:display => 'compact'))) : nil |
| 95 | 114 | map_link = display?(asset, :map) ? (display == 'map' ? _('Map') : link_to(_('Map'), params.merge(:display => 'map'))) : nil |
| 96 | 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 | 117 | content_tag('strong', _('Display')) + ': ' + [compact_link, map_link, full_link].compact.join(' | ').html_safe, |
| 99 | 118 | :class => 'search-customize-options' |
| 100 | 119 | ) |
| 101 | 120 | end |
| 102 | 121 | end |
| 103 | 122 | |
| 104 | - def filter_selector(asset, filter, float = 'right') | |
| 123 | + def filters(asset) | |
| 124 | + return if !asset | |
| 105 | 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 | 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 | 149 | end |
| 135 | 150 | |
| 136 | 151 | end | ... | ... |
| ... | ... | @@ -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 | 56 | if profile |
| 57 | 57 | profile.blocks.each {|block| |
| 58 | 58 | conditions = block.class.expire_on |
| 59 | - blocks_to_expire << block unless (conditions[:profile] & causes).empty? | |
| 59 | + blocks_to_expire << block unless (conditions[:profile] & causes).blank? | |
| 60 | 60 | } |
| 61 | 61 | end |
| 62 | 62 | environment.blocks.each {|block| |
| 63 | 63 | conditions = block.class.expire_on |
| 64 | - blocks_to_expire << block unless (conditions[:environment] & causes).empty? | |
| 64 | + blocks_to_expire << block unless (conditions[:environment] & causes).blank? | |
| 65 | 65 | } |
| 66 | 66 | |
| 67 | 67 | blocks_to_expire.uniq! | ... | ... |
| ... | ... | @@ -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 | options[:search_delay] ||= 1000 |
| 13 | 13 | options[:prevent_duplicates] ||= true |
| 14 | 14 | options[:backspace_delete_item] ||= false |
| 15 | + options[:zindex] ||= 999 | |
| 15 | 16 | options[:focus] ||= false |
| 16 | 17 | options[:avoid_enter] ||= true |
| 17 | 18 | options[:on_result] ||= 'null' |
| 18 | 19 | options[:on_add] ||= 'null' |
| 19 | 20 | options[:on_delete] ||= 'null' |
| 20 | 21 | options[:on_ready] ||= 'null' |
| 22 | + options[:query_param] ||= 'q' | |
| 21 | 23 | |
| 22 | 24 | result = text_field_tag(name, nil, text_field_options.merge(html_options.merge({:id => element_id}))) |
| 23 | 25 | result += javascript_tag("jQuery('##{element_id}') |
| ... | ... | @@ -30,7 +32,8 @@ module TokenHelper |
| 30 | 32 | searchDelay: #{options[:search_delay].to_json}, |
| 31 | 33 | preventDuplicates: #{options[:prevent_duplicates].to_json}, |
| 32 | 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 | 37 | tokenLimit: #{options[:token_limit].to_json}, |
| 35 | 38 | onResult: #{options[:on_result]}, |
| 36 | 39 | onAdd: #{options[:on_add]}, |
| ... | ... | @@ -48,4 +51,4 @@ module TokenHelper |
| 48 | 51 | result |
| 49 | 52 | end |
| 50 | 53 | |
| 51 | -end | |
| 52 | 54 | \ No newline at end of file |
| 55 | +end | ... | ... |
app/mailers/user_mailer.rb
| ... | ... | @@ -41,6 +41,23 @@ class UserMailer < ActionMailer::Base |
| 41 | 41 | ) |
| 42 | 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 | 61 | class Job < Struct.new(:user, :method) |
| 45 | 62 | def perform |
| 46 | 63 | UserMailer.send(method, user).deliver | ... | ... |
app/models/add_friend.rb
| ... | ... | @@ -14,6 +14,11 @@ class AddFriend < Task |
| 14 | 14 | alias :friend :target |
| 15 | 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 | 22 | def perform |
| 18 | 23 | target.add_friend(requestor, group_for_friend) |
| 19 | 24 | requestor.add_friend(target, group_for_person) |
| ... | ... | @@ -48,4 +53,8 @@ class AddFriend < Task |
| 48 | 53 | {:type => :profile_image, :profile => requestor, :url => requestor.url} |
| 49 | 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 | 60 | end | ... | ... |
app/models/add_member.rb
| ... | ... | @@ -10,6 +10,10 @@ class AddMember < Task |
| 10 | 10 | |
| 11 | 11 | settings_items :roles |
| 12 | 12 | |
| 13 | + after_create do |task| | |
| 14 | + remove_from_suggestion_list(task) | |
| 15 | + end | |
| 16 | + | |
| 13 | 17 | def perform |
| 14 | 18 | if !self.roles or (self.roles.uniq.compact.length == 1 and self.roles.uniq.compact.first.to_i.zero?) |
| 15 | 19 | self.roles = [Profile::Roles.member(organization.environment.id).id] |
| ... | ... | @@ -46,4 +50,9 @@ class AddMember < Task |
| 46 | 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 | 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 | 58 | end | ... | ... |
app/models/approve_article.rb
| ... | ... | @@ -22,6 +22,7 @@ class ApproveArticle < Task |
| 22 | 22 | end |
| 23 | 23 | |
| 24 | 24 | settings_items :closing_statment, :article_parent_id, :highlighted |
| 25 | + settings_items :create_link, :type => :boolean, :default => false | |
| 25 | 26 | |
| 26 | 27 | def article_parent |
| 27 | 28 | Article.find_by_id article_parent_id.to_i |
| ... | ... | @@ -48,7 +49,11 @@ class ApproveArticle < Task |
| 48 | 49 | end |
| 49 | 50 | |
| 50 | 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 | 57 | end |
| 53 | 58 | |
| 54 | 59 | def title | ... | ... |
app/models/article.rb
| ... | ... | @@ -2,25 +2,29 @@ require 'hpricot' |
| 2 | 2 | |
| 3 | 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 | 14 | acts_as_having_image |
| 8 | 15 | |
| 9 | 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 | 29 | def self.default_search_display |
| 26 | 30 | 'full' |
| ... | ... | @@ -103,6 +107,11 @@ class Article < ActiveRecord::Base |
| 103 | 107 | self.activity.destroy if self.activity |
| 104 | 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 | 115 | xss_terminate :only => [ :name ], :on => 'validation', :with => 'white_list' |
| 107 | 116 | |
| 108 | 117 | scope :in_category, lambda { |category| |
| ... | ... | @@ -157,14 +166,17 @@ class Article < ActiveRecord::Base |
| 157 | 166 | self.profile |
| 158 | 167 | end |
| 159 | 168 | |
| 160 | - def self.human_attribute_name(attrib, options = {}) | |
| 169 | + def self.human_attribute_name_with_customization(attrib, options={}) | |
| 161 | 170 | case attrib.to_sym |
| 162 | 171 | when :name |
| 163 | 172 | _('Title') |
| 164 | 173 | else |
| 165 | - _(self.superclass.human_attribute_name(attrib)) | |
| 174 | + _(self.human_attribute_name_without_customization(attrib)) | |
| 166 | 175 | end |
| 167 | 176 | end |
| 177 | + class << self | |
| 178 | + alias_method_chain :human_attribute_name, :customization | |
| 179 | + end | |
| 168 | 180 | |
| 169 | 181 | def css_class_list |
| 170 | 182 | [self.class.name.to_css_class] |
| ... | ... | @@ -282,13 +294,6 @@ class Article < ActiveRecord::Base |
| 282 | 294 | end |
| 283 | 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 | 297 | # returns the data of the article. Must be overriden in each subclass to |
| 293 | 298 | # provide the correct content for the article. |
| 294 | 299 | def data |
| ... | ... | @@ -337,7 +342,7 @@ class Article < ActiveRecord::Base |
| 337 | 342 | def belongs_to_blog? |
| 338 | 343 | self.parent and self.parent.blog? |
| 339 | 344 | end |
| 340 | - | |
| 345 | + | |
| 341 | 346 | def belongs_to_forum? |
| 342 | 347 | self.parent and self.parent.forum? |
| 343 | 348 | end |
| ... | ... | @@ -449,6 +454,7 @@ class Article < ActiveRecord::Base |
| 449 | 454 | if self.parent && !self.parent.published? |
| 450 | 455 | return false |
| 451 | 456 | end |
| 457 | + | |
| 452 | 458 | true |
| 453 | 459 | else |
| 454 | 460 | false |
| ... | ... | @@ -468,7 +474,9 @@ class Article < ActiveRecord::Base |
| 468 | 474 | scope :no_folders, lambda {|profile|{:conditions => ['articles.type NOT IN (?)', profile.folder_types]}} |
| 469 | 475 | scope :galleries, :conditions => [ "articles.type IN ('Gallery')" ] |
| 470 | 476 | scope :images, :conditions => { :is_image => true } |
| 477 | + scope :no_images, :conditions => { :is_image => false } | |
| 471 | 478 | scope :text_articles, :conditions => [ 'articles.type IN (?)', text_article_types ] |
| 479 | + scope :files, :conditions => { :type => 'UploadedFile' } | |
| 472 | 480 | scope :with_types, lambda { |types| { :conditions => [ 'articles.type IN (?)', types ] } } |
| 473 | 481 | |
| 474 | 482 | scope :more_popular, :order => 'hits DESC' |
| ... | ... | @@ -480,14 +488,17 @@ class Article < ActiveRecord::Base |
| 480 | 488 | {:conditions => [" articles.published = ? OR |
| 481 | 489 | articles.last_changed_by_id = ? OR |
| 482 | 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 | 494 | end |
| 486 | 495 | |
| 496 | + | |
| 487 | 497 | def display_unpublished_article_to?(user) |
| 488 | 498 | user == author || allow_view_private_content?(user) || user == profile || |
| 489 | 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 | 502 | end |
| 492 | 503 | |
| 493 | 504 | def display_to?(user = nil) |
| ... | ... | @@ -516,7 +527,10 @@ class Article < ActiveRecord::Base |
| 516 | 527 | end |
| 517 | 528 | |
| 518 | 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 | 535 | def allow_create?(user) |
| 522 | 536 | allow_post_content?(user) || allow_publish_content?(user) | ... | ... |
app/models/block.rb
| 1 | 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 | 5 | # to be able to generate HTML |
| 6 | 6 | include ActionView::Helpers::UrlHelper |
| ... | ... | @@ -64,7 +64,7 @@ class Block < ActiveRecord::Base |
| 64 | 64 | end |
| 65 | 65 | |
| 66 | 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 | 68 | end |
| 69 | 69 | |
| 70 | 70 | def display_always(context) |
| ... | ... | @@ -75,7 +75,7 @@ class Block < ActiveRecord::Base |
| 75 | 75 | if context[:article] |
| 76 | 76 | return context[:article] == owner.home_page |
| 77 | 77 | else |
| 78 | - return context[:request_path] == '/' | |
| 78 | + return home_page_path?(context[:request_path]) | |
| 79 | 79 | end |
| 80 | 80 | end |
| 81 | 81 | |
| ... | ... | @@ -83,7 +83,7 @@ class Block < ActiveRecord::Base |
| 83 | 83 | if context[:article] |
| 84 | 84 | return context[:article] != owner.home_page |
| 85 | 85 | else |
| 86 | - return context[:request_path] != '/' + (owner.kind_of?(Profile) ? owner.identifier : '') | |
| 86 | + return !home_page_path?(context[:request_path]) | |
| 87 | 87 | end |
| 88 | 88 | end |
| 89 | 89 | |
| ... | ... | @@ -110,11 +110,14 @@ class Block < ActiveRecord::Base |
| 110 | 110 | # * <tt>'all'</tt>: the block is always displayed |
| 111 | 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 | 116 | # returns the description of the block, used when the user sees a list of |
| 114 | 117 | # blocks to choose one to include in the design. |
| 115 | 118 | # |
| 116 | 119 | # Must be redefined in subclasses to match the description of each block |
| 117 | - # type. | |
| 120 | + # type. | |
| 118 | 121 | def self.description |
| 119 | 122 | '(dummy)' |
| 120 | 123 | end |
| ... | ... | @@ -124,13 +127,13 @@ class Block < ActiveRecord::Base |
| 124 | 127 | # This method can return several types of objects: |
| 125 | 128 | # |
| 126 | 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 | 131 | # * <tt>Proc</tt>: the Proc is evaluated in the scope of BoxesHelper. The |
| 129 | 132 | # block can then use <tt>render</tt>, <tt>link_to</tt>, etc. |
| 130 | 133 | # |
| 131 | 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 | 137 | def content(args={}) |
| 135 | 138 | "This is block number %d" % self.id |
| 136 | 139 | end |
| ... | ... | @@ -192,7 +195,7 @@ class Block < ActiveRecord::Base |
| 192 | 195 | |
| 193 | 196 | # Override in your subclasses. |
| 194 | 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 | 199 | # Possible contexts are: :profile, :environment |
| 197 | 200 | def self.expire_on |
| 198 | 201 | { |
| ... | ... | @@ -221,6 +224,7 @@ class Block < ActiveRecord::Base |
| 221 | 224 | 'all' => _('All users'), |
| 222 | 225 | 'logged' => _('Logged'), |
| 223 | 226 | 'not_logged' => _('Not logged'), |
| 227 | + 'followers' => owner.class != Environment && owner.organization? ? _('Members') : _('Friends') | |
| 224 | 228 | } |
| 225 | 229 | end |
| 226 | 230 | |
| ... | ... | @@ -234,4 +238,26 @@ class Block < ActiveRecord::Base |
| 234 | 238 | duplicated_block |
| 235 | 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 | 263 | end | ... | ... |
app/models/blog.rb
| ... | ... | @@ -53,7 +53,7 @@ class Blog < Folder |
| 53 | 53 | def prepare_external_feed |
| 54 | 54 | unless self.external_feed_data.nil? |
| 55 | 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 | 57 | else |
| 58 | 58 | self.build_external_feed(self.external_feed_data, :without_protection => true) |
| 59 | 59 | end | ... | ... |
app/models/box.rb
| ... | ... | @@ -28,9 +28,6 @@ class Box < ActiveRecord::Base |
| 28 | 28 | CategoriesBlock, |
| 29 | 29 | CommunitiesBlock, |
| 30 | 30 | EnterprisesBlock, |
| 31 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | |
| 32 | - # the Noosfero core soon, see ActionItem3045 | |
| 33 | - EnvironmentStatisticsBlock, | |
| 34 | 31 | FansBlock, |
| 35 | 32 | FavoriteEnterprisesBlock, |
| 36 | 33 | FeedReaderBlock, |
| ... | ... | @@ -53,9 +50,6 @@ class Box < ActiveRecord::Base |
| 53 | 50 | CommunitiesBlock, |
| 54 | 51 | DisabledEnterpriseMessageBlock, |
| 55 | 52 | EnterprisesBlock, |
| 56 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | |
| 57 | - # the Noosfero core soon, see ActionItem3045 | |
| 58 | - EnvironmentStatisticsBlock, | |
| 59 | 53 | FansBlock, |
| 60 | 54 | FavoriteEnterprisesBlock, |
| 61 | 55 | FeaturedProductsBlock, | ... | ... |
app/models/category.rb
| ... | ... | @@ -3,10 +3,10 @@ class Category < ActiveRecord::Base |
| 3 | 3 | attr_accessible :name, :parent_id, :display_color, :display_in_menu, :image_builder, :environment, :parent |
| 4 | 4 | |
| 5 | 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 | 12 | validates_exclusion_of :slug, :in => [ 'index' ], :message => N_('{fn} cannot be like that.').fix_i18n |
| ... | ... | @@ -14,9 +14,6 @@ class Category < ActiveRecord::Base |
| 14 | 14 | validates_uniqueness_of :slug,:scope => [ :environment_id, :parent_id ], :message => N_('{fn} is already being used by another category.').fix_i18n |
| 15 | 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 | 17 | # Finds all top level categories for a given environment. |
| 21 | 18 | scope :top_level_for, lambda { |environment| |
| 22 | 19 | {:conditions => ['parent_id is null and environment_id = ?', environment.id ]} |
| ... | ... | @@ -42,6 +39,13 @@ class Category < ActiveRecord::Base |
| 42 | 39 | |
| 43 | 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 | 49 | scope :from_types, lambda { |types| |
| 46 | 50 | types.select{ |t| t.blank? }.empty? ? |
| 47 | 51 | { :conditions => { :type => types } } : |
| ... | ... | @@ -101,4 +105,12 @@ class Category < ActiveRecord::Base |
| 101 | 105 | self.children.find(:all, :conditions => {:display_in_menu => true}).empty? |
| 102 | 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 | 116 | end | ... | ... |
app/models/certifier.rb
| ... | ... | @@ -3,9 +3,9 @@ class Certifier < ActiveRecord::Base |
| 3 | 3 | attr_accessible :name, :environment |
| 4 | 4 | |
| 5 | 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 | 11 | belongs_to :environment | ... | ... |
app/models/change_password.rb
| ... | ... | @@ -2,16 +2,19 @@ class ChangePassword < Task |
| 2 | 2 | |
| 3 | 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 | 6 | case attrib.to_sym |
| 7 | 7 | when :password |
| 8 | 8 | _('Password') |
| 9 | 9 | when :password_confirmation |
| 10 | 10 | _('Password Confirmation') |
| 11 | 11 | else |
| 12 | - _(self.superclass.human_attribute_name(attrib)) | |
| 12 | + _(self.human_attribute_name_without_customization(attrib)) | |
| 13 | 13 | end |
| 14 | 14 | end |
| 15 | + class << self | |
| 16 | + alias_method_chain :human_attribute_name, :customization | |
| 17 | + end | |
| 15 | 18 | |
| 16 | 19 | validates_presence_of :requestor |
| 17 | 20 | ... | ... |
app/models/comment.rb
| 1 | 1 | class Comment < ActiveRecord::Base |
| 2 | 2 | |
| 3 | 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 | 9 | attr_accessible :body, :author, :name, :email, :title, :reply_of_id, :source | ... | ... |
app/models/communities_block.rb
| ... | ... | @@ -14,19 +14,17 @@ class CommunitiesBlock < ProfileListBlock |
| 14 | 14 | _('This block displays the communities in which the user is a member.') |
| 15 | 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 | 22 | def footer |
| 18 | 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 | 28 | end |
| 31 | 29 | end |
| 32 | 30 | ... | ... |
app/models/community.rb
| ... | ... | @@ -50,16 +50,6 @@ class Community < Organization |
| 50 | 50 | super + FIELDS |
| 51 | 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 | 53 | def active_fields |
| 64 | 54 | environment ? environment.active_community_fields : [] |
| 65 | 55 | end |
| ... | ... | @@ -78,7 +68,7 @@ class Community < Organization |
| 78 | 68 | end |
| 79 | 69 | |
| 80 | 70 | def default_template |
| 81 | - environment.community_template | |
| 71 | + environment.community_default_template | |
| 82 | 72 | end |
| 83 | 73 | |
| 84 | 74 | def news(limit = 30, highlight = false) | ... | ... |
app/models/create_enterprise.rb
| ... | ... | @@ -73,7 +73,13 @@ class CreateEnterprise < Task |
| 73 | 73 | |
| 74 | 74 | # sets the associated region for the enterprise creation |
| 75 | 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 | 84 | @region = value |
| 79 | 85 | self.region_id = value.id | ... | ... |
app/models/domain.rb
| ... | ... | @@ -92,4 +92,11 @@ class Domain < ActiveRecord::Base |
| 92 | 92 | @hosting = {} |
| 93 | 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 | 102 | end | ... | ... |
app/models/enterprise.rb
| ... | ... | @@ -4,7 +4,10 @@ class Enterprise < Organization |
| 4 | 4 | |
| 5 | 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 | 12 | def self.type_name |
| 10 | 13 | _('Enterprise') |
| ... | ... | @@ -59,16 +62,6 @@ class Enterprise < Organization |
| 59 | 62 | super + FIELDS |
| 60 | 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 | 65 | def active_fields |
| 73 | 66 | environment ? environment.active_enterprise_fields : [] |
| 74 | 67 | end |
| ... | ... | @@ -107,7 +100,12 @@ class Enterprise < Organization |
| 107 | 100 | self.tasks.where(:type => 'EnterpriseActivation').first |
| 108 | 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 | 109 | return if enabled |
| 112 | 110 | # must be set first for the following to work |
| 113 | 111 | self.enabled = true |
| ... | ... | @@ -169,7 +167,7 @@ class Enterprise < Organization |
| 169 | 167 | end |
| 170 | 168 | |
| 171 | 169 | def default_template |
| 172 | - environment.enterprise_template | |
| 170 | + environment.enterprise_default_template | |
| 173 | 171 | end |
| 174 | 172 | |
| 175 | 173 | def template_with_inactive_enterprise | ... | ... |
app/models/environment.rb
| ... | ... | @@ -3,13 +3,14 @@ |
| 3 | 3 | # domains. |
| 4 | 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 | 8 | has_many :users |
| 9 | 9 | |
| 10 | 10 | self.partial_updates = false |
| 11 | 11 | |
| 12 | 12 | has_many :tasks, :dependent => :destroy, :as => 'target' |
| 13 | + has_many :search_terms, :as => :context | |
| 13 | 14 | |
| 14 | 15 | IDENTIFY_SCRIPTS = /(php[0-9s]?|[sp]htm[l]?|pl|py|cgi|rb)/ |
| 15 | 16 | |
| ... | ... | @@ -85,7 +86,9 @@ class Environment < ActiveRecord::Base |
| 85 | 86 | end |
| 86 | 87 | |
| 87 | 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 | 92 | end |
| 90 | 93 | |
| 91 | 94 | # returns the available features for a Environment, in the form of a |
| ... | ... | @@ -124,6 +127,7 @@ class Environment < ActiveRecord::Base |
| 124 | 127 | 'organizations_are_moderated_by_default' => _("Organizations have moderated publication by default"), |
| 125 | 128 | 'enable_organization_url_change' => _("Allow organizations to change their URL"), |
| 126 | 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 | 131 | 'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'), |
| 128 | 132 | 'xmpp_chat' => _('XMPP/Jabber based chat'), |
| 129 | 133 | 'show_zoom_button_on_article_images' => _('Show a zoom link on all article images'), |
| ... | ... | @@ -132,7 +136,8 @@ class Environment < ActiveRecord::Base |
| 132 | 136 | 'send_welcome_email_to_new_users' => _('Send welcome e-mail to new users'), |
| 133 | 137 | 'allow_change_of_redirection_after_login' => _('Allow users to set the page to redirect after login'), |
| 134 | 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 | 142 | end |
| 138 | 143 | |
| ... | ... | @@ -153,7 +158,8 @@ class Environment < ActiveRecord::Base |
| 153 | 158 | 'site_homepage' => _('Redirects the user to the environment homepage.'), |
| 154 | 159 | 'user_profile_page' => _('Redirects the user to his profile page.'), |
| 155 | 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 | 164 | end |
| 159 | 165 | validates_inclusion_of :redirection_after_signup, :in => Environment.signup_redirection_options.keys, :allow_nil => true |
| ... | ... | @@ -175,9 +181,6 @@ class Environment < ActiveRecord::Base |
| 175 | 181 | |
| 176 | 182 | # "left" area |
| 177 | 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 | 184 | env.boxes[1].blocks << RecentDocumentsBlock.new |
| 182 | 185 | |
| 183 | 186 | # "right" area |
| ... | ... | @@ -284,6 +287,7 @@ class Environment < ActiveRecord::Base |
| 284 | 287 | www.flickr.com |
| 285 | 288 | www.gmodules.com |
| 286 | 289 | www.youtube.com |
| 290 | + openstreetmap.org | |
| 287 | 291 | ] + ('a' .. 'z').map{|i| "#{i}.yimg.com"} |
| 288 | 292 | |
| 289 | 293 | settings_items :enabled_plugins, :type => Array, :default => Noosfero::Plugin.available_plugin_names |
| ... | ... | @@ -303,6 +307,17 @@ class Environment < ActiveRecord::Base |
| 303 | 307 | settings[:signup_welcome_screen_body].present? |
| 304 | 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 | 321 | def news_amount_by_folder=(amount) |
| 307 | 322 | settings[:news_amount_by_folder] = amount.to_i |
| 308 | 323 | end |
| ... | ... | @@ -646,10 +661,11 @@ class Environment < ActiveRecord::Base |
| 646 | 661 | { :controller => 'admin_panel', :action => 'index' } |
| 647 | 662 | end |
| 648 | 663 | |
| 649 | - def top_url | |
| 650 | - url = 'http://' | |
| 664 | + def top_url(scheme = 'http') | |
| 665 | + url = scheme + '://' | |
| 651 | 666 | url << (Noosfero.url_options.key?(:host) ? Noosfero.url_options[:host] : default_hostname) |
| 652 | 667 | url << ':' << Noosfero.url_options[:port].to_s if Noosfero.url_options.key?(:port) |
| 668 | + url << Noosfero.root('') | |
| 653 | 669 | url |
| 654 | 670 | end |
| 655 | 671 | |
| ... | ... | @@ -717,31 +733,50 @@ class Environment < ActiveRecord::Base |
| 717 | 733 | ] |
| 718 | 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 | 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 | 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 | 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 | 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 | 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 | 771 | end |
| 737 | 772 | |
| 738 | - def enterprise_template | |
| 773 | + def enterprise_default_template | |
| 739 | 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 | 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 | 780 | end |
| 746 | 781 | |
| 747 | 782 | def inactive_enterprise_template |
| ... | ... | @@ -793,8 +828,12 @@ class Environment < ActiveRecord::Base |
| 793 | 828 | "home-page-news/#{cache_key}-#{language}" |
| 794 | 829 | end |
| 795 | 830 | |
| 831 | + def portal_enabled | |
| 832 | + portal_community && enabled?('use_portal_community') | |
| 833 | + end | |
| 834 | + | |
| 796 | 835 | def notification_emails |
| 797 | - [noreply_email.blank? ? nil : noreply_email].compact + admins.map(&:email) | |
| 836 | + [contact_email].select(&:present?) + admins.map(&:email) | |
| 798 | 837 | end |
| 799 | 838 | |
| 800 | 839 | after_create :create_templates |
| ... | ... | @@ -839,10 +878,10 @@ class Environment < ActiveRecord::Base |
| 839 | 878 | person_template.visible = false |
| 840 | 879 | person_template.save! |
| 841 | 880 | |
| 842 | - self.enterprise_template = enterprise_template | |
| 881 | + self.enterprise_default_template = enterprise_template | |
| 843 | 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 | 885 | self.save! |
| 847 | 886 | end |
| 848 | 887 | |
| ... | ... | @@ -906,6 +945,10 @@ class Environment < ActiveRecord::Base |
| 906 | 945 | locales_list |
| 907 | 946 | end |
| 908 | 947 | |
| 948 | + def has_license? | |
| 949 | + self.licenses.any? | |
| 950 | + end | |
| 951 | + | |
| 909 | 952 | private |
| 910 | 953 | |
| 911 | 954 | def default_language_available | ... | ... |
app/models/environment_statistics_block.rb
| ... | ... | @@ -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 < Article |
| 19 | 19 | maybe_add_http(self.setting[:link]) |
| 20 | 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 | 24 | def initialize(*args) |
| 25 | 25 | super(*args) | ... | ... |
app/models/external_feed.rb
| ... | ... | @@ -10,9 +10,10 @@ class ExternalFeed < ActiveRecord::Base |
| 10 | 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 | 15 | def add_item(title, link, date, content) |
| 16 | + return if content.blank? | |
| 16 | 17 | doc = Hpricot(content) |
| 17 | 18 | doc.search('*').each do |p| |
| 18 | 19 | if p.instance_of? Hpricot::Elem |
| ... | ... | @@ -30,6 +31,7 @@ class ExternalFeed < ActiveRecord::Base |
| 30 | 31 | article.source = link |
| 31 | 32 | article.profile = blog.profile |
| 32 | 33 | article.parent = blog |
| 34 | + article.author_name = self.feed_title | |
| 33 | 35 | unless blog.children.exists?(:slug => article.slug) |
| 34 | 36 | article.save! |
| 35 | 37 | article.delay.create_activity | ... | ... |
app/models/feed_reader_block.rb
app/models/folder.rb
| ... | ... | @@ -12,7 +12,7 @@ class Folder < Article |
| 12 | 12 | |
| 13 | 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 | 17 | include WhiteListFilter |
| 18 | 18 | filter_iframes :body | ... | ... |
app/models/invitation.rb
| ... | ... | @@ -51,7 +51,10 @@ class Invitation < Task |
| 51 | 51 | next if contact_to_invite == _("Firstname Lastname <friend@email.com>") |
| 52 | 52 | |
| 53 | 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 | 58 | friend_name = match[1].strip |
| 56 | 59 | friend_email = match[2] |
| 57 | 60 | elsif match = contact_to_invite.strip.match(Noosfero::Constants::EMAIL_FORMAT) |
| ... | ... | @@ -61,22 +64,24 @@ class Invitation < Task |
| 61 | 64 | next |
| 62 | 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 | 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 | 76 | {:person => person, :target => user.person} |
| 70 | 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 | 85 | end |
| 81 | 86 | end |
| 82 | 87 | end | ... | ... |
app/models/invite_friend.rb
| 1 | 1 | class InviteFriend < Invitation |
| 2 | 2 | |
| 3 | 3 | settings_items :group_for_person, :group_for_friend |
| 4 | + before_create :check_for_invitation_existence | |
| 4 | 5 | |
| 5 | 6 | def perform |
| 6 | 7 | person.add_friend(friend, group_for_person) |
| ... | ... | @@ -41,4 +42,11 @@ class InviteFriend < Invitation |
| 41 | 42 | ].join("\n\n") |
| 42 | 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 | 52 | end | ... | ... |
app/models/invite_member.rb
| ... | ... | @@ -2,6 +2,7 @@ class InviteMember < Invitation |
| 2 | 2 | |
| 3 | 3 | settings_items :community_id, :type => :integer |
| 4 | 4 | validates_presence_of :community_id |
| 5 | + before_create :check_for_invitation_existence | |
| 5 | 6 | |
| 6 | 7 | def community |
| 7 | 8 | Community.find(community_id) |
| ... | ... | @@ -39,6 +40,14 @@ class InviteMember < Invitation |
| 39 | 40 | _('%{requestor} invited you to join %{community}.') % {:requestor => requestor.name, :community => community.name} |
| 40 | 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 | 51 | def expanded_message |
| 43 | 52 | super.gsub /<community>/, community.name |
| 44 | 53 | end |
| ... | ... | @@ -53,4 +62,11 @@ class InviteMember < Invitation |
| 53 | 62 | ].join("\n\n") |
| 54 | 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 | 72 | end | ... | ... |
app/models/license.rb
| ... | ... | @@ -3,8 +3,8 @@ class License < ActiveRecord::Base |
| 3 | 3 | attr_accessible :name, :url |
| 4 | 4 | |
| 5 | 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 | 10 | belongs_to :environment | ... | ... |
| ... | ... | @@ -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 < Block |
| 78 | 78 | address |
| 79 | 79 | end |
| 80 | 80 | if add !~ /^[a-z]+:\/\// && add !~ /^\// |
| 81 | - 'http://' + add | |
| 81 | + '//' + add | |
| 82 | 82 | else |
| 83 | + if root = Noosfero.root | |
| 84 | + if !add.starts_with?(root) | |
| 85 | + add = root + add | |
| 86 | + end | |
| 87 | + end | |
| 83 | 88 | add |
| 84 | 89 | end |
| 85 | 90 | end |
| 86 | 91 | |
| 87 | - def editable? | |
| 88 | - true | |
| 89 | - end | |
| 90 | - | |
| 91 | 92 | def icons_options |
| 92 | 93 | ICONS.map do |i| |
| 93 | 94 | "<span title=\"#{i[1]}\" class=\"icon-#{i[0]}\" onclick=\"changeIcon(this, '#{i[0]}')\"></span>".html_safe |
| ... | ... | @@ -100,4 +101,5 @@ class LinkListBlock < Block |
| 100 | 101 | sanitizer = HTML::WhiteListSanitizer.new |
| 101 | 102 | sanitizer.sanitize(text) |
| 102 | 103 | end |
| 104 | + | |
| 103 | 105 | end | ... | ... |
app/models/main_block.rb
| ... | ... | @@ -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 | 60 | \ No newline at end of file | ... | ... |
app/models/national_region.rb
| 1 | 1 | class NationalRegion < ActiveRecord::Base |
| 2 | 2 | |
| 3 | 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 | 8 | def self.search_city(city_name, like = false, state = nil) | ... | ... |
app/models/organization.rb
| ... | ... | @@ -3,10 +3,11 @@ class Organization < Profile |
| 3 | 3 | |
| 4 | 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 | 12 | settings_items :closed, :type => :boolean, :default => false |
| 12 | 13 | def closed? |
| ... | ... | @@ -30,6 +31,16 @@ class Organization < Profile |
| 30 | 31 | |
| 31 | 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 | 44 | def validation_methodology |
| 34 | 45 | self.validation_info ? self.validation_info.validation_methodology : nil |
| 35 | 46 | end |
| ... | ... | @@ -135,7 +146,11 @@ class Organization < Profile |
| 135 | 146 | end |
| 136 | 147 | |
| 137 | 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 | 154 | end |
| 140 | 155 | |
| 141 | 156 | def already_request_membership?(person) |
| ... | ... | @@ -162,4 +177,8 @@ class Organization < Profile |
| 162 | 177 | self.visible = false |
| 163 | 178 | save! |
| 164 | 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 | 184 | end | ... | ... |
app/models/person.rb
| ... | ... | @@ -3,10 +3,11 @@ class Person < Profile |
| 3 | 3 | |
| 4 | 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 | 12 | def self.type_name |
| 12 | 13 | _('Person') |
| ... | ... | @@ -21,10 +22,34 @@ class Person < Profile |
| 21 | 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 | 51 | permissions += plugins.map do |plugin| |
| 27 | - plugin.has_permission?(self, permission, profile) | |
| 52 | + plugin.has_permission?(self, permission, resource) | |
| 28 | 53 | end |
| 29 | 54 | permissions.include?(true) |
| 30 | 55 | end |
| ... | ... | @@ -39,9 +64,9 @@ class Person < Profile |
| 39 | 64 | ScopeTool.union *scopes |
| 40 | 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 | 71 | has_many :friendships, :dependent => :destroy |
| 47 | 72 | has_many :friends, :class_name => 'Person', :through => :friendships |
| ... | ... | @@ -59,6 +84,10 @@ class Person < Profile |
| 59 | 84 | has_and_belongs_to_many :acepted_forums, :class_name => 'Forum', :join_table => 'terms_forum_people' |
| 60 | 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 | 91 | scope :more_popular, :order => 'friends_count DESC' |
| 63 | 92 | |
| 64 | 93 | scope :abusers, :joins => :abuse_complaints, :conditions => ['tasks.status = 3'], :select => 'DISTINCT profiles.*' |
| ... | ... | @@ -74,6 +103,10 @@ class Person < Profile |
| 74 | 103 | |
| 75 | 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 | 110 | def can_control_scrap?(scrap) |
| 78 | 111 | begin |
| 79 | 112 | !self.scraps(scrap).nil? |
| ... | ... | @@ -108,12 +141,12 @@ class Person < Profile |
| 108 | 141 | end |
| 109 | 142 | |
| 110 | 143 | def add_friend(friend, group = nil) |
| 111 | - unless self.is_a_friend?(friend) | |
| 144 | + unless self.is_a_friend?(friend) | |
| 112 | 145 | friendship = self.friendships.build |
| 113 | 146 | friendship.friend = friend |
| 114 | 147 | friendship.group = group |
| 115 | 148 | friendship.save |
| 116 | - end | |
| 149 | + end | |
| 117 | 150 | end |
| 118 | 151 | |
| 119 | 152 | def already_request_friendship?(person) |
| ... | ... | @@ -161,7 +194,7 @@ class Person < Profile |
| 161 | 194 | FIELDS |
| 162 | 195 | end |
| 163 | 196 | |
| 164 | - validate :presence_of_required_fields | |
| 197 | + validate :presence_of_required_fields, :unless => :is_template | |
| 165 | 198 | |
| 166 | 199 | def presence_of_required_fields |
| 167 | 200 | self.required_fields.each do |field| |
| ... | ... | @@ -300,7 +333,7 @@ class Person < Profile |
| 300 | 333 | end |
| 301 | 334 | |
| 302 | 335 | def default_template |
| 303 | - environment.person_template | |
| 336 | + environment.person_default_template | |
| 304 | 337 | end |
| 305 | 338 | |
| 306 | 339 | def apply_type_specific_template(template) |
| ... | ... | @@ -487,6 +520,15 @@ class Person < Profile |
| 487 | 520 | person.notifier.reschedule_next_notification_mail |
| 488 | 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 | 532 | protected |
| 491 | 533 | |
| 492 | 534 | def followed_by?(profile) | ... | ... |
app/models/person_notifier.rb
app/models/product.rb
| 1 | 1 | class Product < ActiveRecord::Base |
| 2 | 2 | |
| 3 | 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 | 15 | def self.default_search_display |
| 17 | 16 | 'full' | ... | ... |
app/models/profile.rb
| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | # which by default is the one returned by Environment:default. |
| 4 | 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 | 8 | # use for internationalizable human type names in search facets |
| 9 | 9 | # reimplement on subclasses |
| ... | ... | @@ -12,16 +12,15 @@ class Profile < ActiveRecord::Base |
| 12 | 12 | end |
| 13 | 13 | |
| 14 | 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 | 25 | def self.default_search_display |
| 27 | 26 | 'compact' |
| ... | ... | @@ -97,7 +96,7 @@ class Profile < ActiveRecord::Base |
| 97 | 96 | end |
| 98 | 97 | |
| 99 | 98 | def members_by_name |
| 100 | - members.order(:name) | |
| 99 | + members.order('profiles.name') | |
| 101 | 100 | end |
| 102 | 101 | |
| 103 | 102 | class << self |
| ... | ... | @@ -108,8 +107,8 @@ class Profile < ActiveRecord::Base |
| 108 | 107 | alias_method_chain :count, :distinct |
| 109 | 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 | 112 | end |
| 114 | 113 | |
| 115 | 114 | acts_as_having_boxes |
| ... | ... | @@ -121,7 +120,9 @@ class Profile < ActiveRecord::Base |
| 121 | 120 | end |
| 122 | 121 | |
| 123 | 122 | scope :visible, :conditions => { :visible => true } |
| 123 | + scope :disabled, :conditions => { :visible => false } | |
| 124 | 124 | scope :public, :conditions => { :visible => true, :public_profile => true } |
| 125 | + scope :enabled, :conditions => { :enabled => true } | |
| 125 | 126 | |
| 126 | 127 | # Subclasses must override this method |
| 127 | 128 | scope :more_popular |
| ... | ... | @@ -138,6 +139,17 @@ class Profile < ActiveRecord::Base |
| 138 | 139 | |
| 139 | 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 | 153 | def scraps(scrap=nil) |
| 142 | 154 | scrap = scrap.is_a?(Scrap) ? scrap.id : scrap |
| 143 | 155 | scrap.nil? ? Scrap.all_scraps(self) : Scrap.all_scraps(self).find(scrap) |
| ... | ... | @@ -153,6 +165,7 @@ class Profile < ActiveRecord::Base |
| 153 | 165 | settings_items :public_content, :type => :boolean, :default => true |
| 154 | 166 | settings_items :description |
| 155 | 167 | settings_items :fields_privacy, :type => :hash, :default => {} |
| 168 | + settings_items :email_suggestions, :type => :boolean, :default => false | |
| 156 | 169 | |
| 157 | 170 | validates_length_of :description, :maximum => 550, :allow_nil => true |
| 158 | 171 | |
| ... | ... | @@ -217,6 +230,8 @@ class Profile < ActiveRecord::Base |
| 217 | 230 | |
| 218 | 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 | 235 | def top_level_categorization |
| 221 | 236 | ret = {} |
| 222 | 237 | self.profile_categorizations.each do |c| |
| ... | ... | @@ -346,16 +361,17 @@ class Profile < ActiveRecord::Base |
| 346 | 361 | end |
| 347 | 362 | |
| 348 | 363 | def copy_blocks_from(profile) |
| 364 | + template_boxes = profile.boxes.select{|box| box.position} | |
| 349 | 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 | 370 | new_box.position = box.position |
| 353 | - self.boxes << new_box | |
| 354 | 371 | box.blocks.each do |block| |
| 355 | 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 | 375 | end |
| 360 | 376 | end |
| 361 | 377 | end |
| ... | ... | @@ -390,7 +406,7 @@ class Profile < ActiveRecord::Base |
| 390 | 406 | end |
| 391 | 407 | |
| 392 | 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 | 411 | include WhiteListFilter |
| 396 | 412 | filter_iframes :custom_header, :custom_footer |
| ... | ... | @@ -511,6 +527,14 @@ class Profile < ActiveRecord::Base |
| 511 | 527 | generate_url(:profile => identifier, :controller => 'profile', :action => 'index') |
| 512 | 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 | 538 | def generate_url(options) |
| 515 | 539 | url_options.merge(options) |
| 516 | 540 | end |
| ... | ... | @@ -600,7 +624,7 @@ private :generate_url, :url_options |
| 600 | 624 | end |
| 601 | 625 | |
| 602 | 626 | def copy_article_tree(article, parent=nil) |
| 603 | - return if article.is_a?(RssFeed) | |
| 627 | + return if !copy_article?(article) | |
| 604 | 628 | original_article = self.articles.find_by_name(article.name) |
| 605 | 629 | if original_article |
| 606 | 630 | num = 2 |
| ... | ... | @@ -620,6 +644,11 @@ private :generate_url, :url_options |
| 620 | 644 | end |
| 621 | 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 | 652 | # Adds a person as member of this Profile. |
| 624 | 653 | def add_member(person) |
| 625 | 654 | if self.has_members? |
| ... | ... | @@ -629,6 +658,8 @@ private :generate_url, :url_options |
| 629 | 658 | self.affiliate(person, Profile::Roles.admin(environment.id)) if members.count == 0 |
| 630 | 659 | self.affiliate(person, Profile::Roles.member(environment.id)) |
| 631 | 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 | 663 | else |
| 633 | 664 | raise _("%s can't have members") % self.class.name |
| 634 | 665 | end |
| ... | ... | @@ -766,7 +797,10 @@ private :generate_url, :url_options |
| 766 | 797 | end |
| 767 | 798 | |
| 768 | 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 | 804 | end |
| 771 | 805 | |
| 772 | 806 | def enable_contact? |
| ... | ... | @@ -774,7 +808,7 @@ private :generate_url, :url_options |
| 774 | 808 | end |
| 775 | 809 | |
| 776 | 810 | include Noosfero::Plugin::HotSpot |
| 777 | - | |
| 811 | + | |
| 778 | 812 | def folder_types |
| 779 | 813 | types = Article.folder_types |
| 780 | 814 | plugins.dispatch(:content_types).each {|type| |
| ... | ... | @@ -898,6 +932,13 @@ private :generate_url, :url_options |
| 898 | 932 | end |
| 899 | 933 | |
| 900 | 934 | def disable |
| 935 | + self.visible = false | |
| 936 | + self.save | |
| 937 | + end | |
| 938 | + | |
| 939 | + def enable | |
| 940 | + self.visible = true | |
| 941 | + self.save | |
| 901 | 942 | end |
| 902 | 943 | |
| 903 | 944 | def control_panel_settings_button |
| ... | ... | @@ -957,4 +998,14 @@ private :generate_url, :url_options |
| 957 | 998 | def preferred_login_redirection |
| 958 | 999 | redirection_after_login.blank? ? environment.redirection_after_login : redirection_after_login |
| 959 | 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 | 1011 | end | ... | ... |
app/models/profile_image_block.rb
app/models/profile_info_block.rb
app/models/profile_search_block.rb
| ... | ... | @@ -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
app/models/scrap.rb
| ... | ... | @@ -3,7 +3,7 @@ class Scrap < ActiveRecord::Base |
| 3 | 3 | attr_accessible :content, :sender_id, :receiver_id, :scrap_id |
| 4 | 4 | |
| 5 | 5 | SEARCHABLE_FIELDS = { |
| 6 | - :content => 1, | |
| 6 | + :content => {:label => _('Content'), :weight => 1}, | |
| 7 | 7 | } |
| 8 | 8 | validates_presence_of :content |
| 9 | 9 | validates_presence_of :sender_id, :receiver_id | ... | ... |
| ... | ... | @@ -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 | ... | ... |
| ... | ... | @@ -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 | ... | ... |
| ... | ... | @@ -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 < ActiveRecord::Base |
| 73 | 73 | end |
| 74 | 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 | 76 | # this method finished the task. It calls #perform, which must be overriden |
| 81 | 77 | # by subclasses. At the end a message (as returned by #finish_message) is |
| 82 | 78 | # sent to the requestor with #notify_requestor. |
| ... | ... | @@ -254,6 +250,10 @@ class Task < ActiveRecord::Base |
| 254 | 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 | 257 | def opened? |
| 258 | 258 | status == Task::Status::ACTIVE || status == Task::Status::HIDDEN |
| 259 | 259 | end |
| ... | ... | @@ -285,8 +285,9 @@ class Task < ActiveRecord::Base |
| 285 | 285 | # If |
| 286 | 286 | def send_notification(action) |
| 287 | 287 | if sends_email? |
| 288 | - if self.requestor | |
| 289 | - TaskMailer.generic_message("task_#{action}", self) | |
| 288 | + if self.requestor && !self.requestor.notification_emails.empty? | |
| 289 | + message = TaskMailer.generic_message("task_#{action}", self) | |
| 290 | + message.deliver if message | |
| 290 | 291 | end |
| 291 | 292 | end |
| 292 | 293 | end | ... | ... |