Commit 55f22c3ea607c019bd260c753e50d87ebecfa690

Authored by Paulo Meireles
2 parents 0e8830f9 1aeb0d56

[Mezuro] Merge branch 'master' into mezuro

Showing 195 changed files with 74645 additions and 67678 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 195 files displayed.

AUTHORS
... ... @@ -6,45 +6,144 @@ noosfero, that's not a problem).
6 6 Developers
7 7 ==========
8 8  
  9 +Alan Freihof Tygel <alantygel@gmail.com>
  10 +Alessandro Palmeira <alessandro.palmeira@gmail.com>
  11 +Alessandro Palmeira + Caio C. Salgado <alessandro.palmeira@gmail.com>
  12 +Alessandro Palmeira + Caio Salgado <alessandro.palmeira@gmail.com>
  13 +Alessandro Palmeira + Caio Salgado <caio.csalgado@gmail.com>
  14 +Alessandro Palmeira + Caio Salgado + Diego Araújo + João M. M. da Silva <diegoamc90@gmail.com>
  15 +Alessandro Palmeira + Carlos Morais <alessandro.palmeira@gmail.com>
  16 +Alessandro Palmeira + Diego Araújo <alessandro.palmeira@gmail.com>
  17 +Alessandro Palmeira + Diego Araújo <diegoamc90@gmail.com>
  18 +Alessandro Palmeira + Diego Araújo + Pedro Leal <diegoamc90@gmail.com>
  19 +Alessandro Palmeira + Diego Araújo + Pedro Leal + João M. M. da Silva <diegoamc90@gmail.com>
  20 +Alessandro Palmeira + Diego Araujo + Rafael Manzo <alessandro.palmeira@gmail.com>
  21 +Alessandro Palmeira + Jefferson Fernandes <alessandro.palmeira@gmail.com>
  22 +Alessandro Palmeira + João M. M. da Silva <alessandro.palmeira@gmail.com>
  23 +Alessandro Palmeira + João M. M. da Silva + Renan Teruo <alessandro.palmeira@gmail.com>
  24 +Alessandro Palmeira + João M. M. Silva <alessandro.palmeira@gmail.com>
  25 +Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com>
  26 +Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com>
9 27 Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br>
10 28 Antonio Terceiro + Paulo Meirelles <terceiro@colivre.coop.br>
11 29 Antonio Terceiro <terceiro@colivre.coop.br>
12 30 Aurelio A. Heckert <aurelio@colivre.coop.br>
13 31 Braulio Bhavamitra <brauliobo@gmail.com>
14 32 Bráulio Bhavamitra <brauliobo@gmail.com>
  33 +Caio <caio.csalgado@gmail.com>
  34 +Caio + Diego + Pedro + João <caio.csalgado@gmail.com>
  35 +Caio, Pedro <caio.csalgado@gmail.com>
  36 +Caio Salgado + Alessandro Palmeira <caio.csalgado@gmail.com>
  37 +Caio Salgado <caio.csalgado@gmail.com>
  38 +Caio Salgado + Carlos Morais + Diego Araújo + Pedro Leal <diegoamc90@gmail.com>
  39 +Caio Salgado + Diego Araujo <caio.csalgado@gmail.com>
  40 +Caio Salgado + Diego Araújo <caio.csalgado@gmail.com>
  41 +Caio Salgado + Diego Araújo <diegoamc90@gmail.com>
  42 +Caio Salgado + Diego Araújo + Jefferson Fernandes <caio.csalgado@gmail.com>
  43 +Caio Salgado + Diego Araújo + João M. M. da Silva <caio.csalgado@gmail.com>
  44 +Caio Salgado + Diego Araújo + Pedro Leal <caio.csalgado@gmail.com>
  45 +Caio Salgado + Diego Araújo + Pedro Leal <diegoamc90@gmail.com>
  46 +Caio Salgado + Diego Araújo + Rafael Manzo <diegoamc90@gmail.com>
  47 +Caio Salgado + Jefferson Fernandes <caio.csalgado@gmail.com>
  48 +Caio Salgado + Jefferson Fernandes <jeffs.fernandes@gmail.com>
  49 +Caio Salgado + Rafael Manzo <caio.csalgado@gmail.com>
  50 +Caio Salgado + Renan Teruo <caio.csalgado@gmail.com>
  51 +Caio Salgado + Renan Teruo <caio.salgado@gmail.com>
  52 +Caio Salgado + Renan Teruo + Jefferson Fernandes <jeffs.fernandes@gmail.com>
  53 +Caio Salgado + Renan Teruo <renanteruoc@gmail.com>
15 54 Caio SBA <caio@colivre.coop.br>
16 55 Carlos Morais <carlos88morais@gmail.com>
17 56 Carlos Morais + Diego Araújo <diegoamc90@gmail.com>
  57 +Carlos Morais + Eduardo Morais <carlos88morais@gmail.com>
18 58 Carlos Morais + Paulo Meirelles <carlos88morais@gmail.com>
  59 +Carlos Morais + Pedro Leal <carlos88morais@gmail.com>
19 60 Daniela Soares Feitosa <danielafeitosa@colivre.coop.br>
20 61 Daniel Cunha <daniel@colivre.coop.br>
  62 +diegoamc <diegoamc90@gmail.com>
  63 +Diego Araújo + Alessandro Palmeira <diegoamc90@gmail.com>
  64 +Diego Araujo + Caio Salgado <diegoamc90@gmail.com>
21 65 Diego Araújo <diegoamc90@gmail.com>
  66 +Diego Araújo + Jefferson Fernandes <diegoamc90@gmail.com>
  67 +Diego Araujo + Jefferson Fernandes <jeffs.fernandes@gmail.com>
  68 +Diego Araújo + João Machini <diegoamc90@gmail.com>
  69 +Diego Araújo + João Machini <digoamc90@gmail.com>
22 70 Diego Araújo + João M. M. da Silva <diegoamc90@gmail.com>
  71 +Diego Araújo + João M. M. da Silva + João Machini <diegoamc90@gmail.com>
  72 +Diego Araújo + João M. M. da Silva + Pedro Leal <diegoamc90@gmail.com>
  73 +Diego Araújo + Paulo Meirelles <diegoamc90@gmail.com>
  74 +Diego Araújo + Pedro Leal <diegoamc90@gmail.com>
  75 +Diego Araújo + Rafael Manzo <diegoamc90@gmail.com>
  76 +Diego Araújo + Renan Teruo + Alessandro Palmeira <diegoamc90@gmail.com>
  77 +Diego Araújo + Renan Teruo <diegoamc90@gmail.com>
  78 +Diego + Jefferson <diegoamc90@gmail.com>
  79 +Diego Martinez <diegoamc90@gmail.com>
  80 +Diego + Renan <renanteruoc@gmail.com>
23 81 Fernanda Lopes <nanda.listas+psl@gmail.com>
24 82 Grazieno Pellegrino <grazieno@gmail.com>
25 83 Isaac Canan <isaac@intelletto.com.br>
26 84 Italo Valcy <italo@dcc.ufba.br>
  85 +Jefferson Fernandes + Diego Araujo + Rafael Manzo <jeffs.fernandes@gmail.com>
  86 +Jefferson Fernandes + Joao M. M. da Silva <jeffs.fernandes@gmail.com>
  87 +Jefferson Fernandes + Joao M. M. Silva <jeffs.fernandes@gmail.com>
27 88 João da Silva <jaodsilv@linux.ime.usp.br>
  89 +João Marco Maciel da Silva + Rafael Manzo + Renan Teruo <jaodsilv@linux.ime.usp.br>
  90 +João M. M. da Silva + Alessandro Palmeira + Diego Araújo + Caio Salgado <jaodsilv@linux.ime.usp.br>
  91 +João M. M. da Silva + Alessandro Palmeira + Diego Araújo <jaodsilv@linux.ime.usp.br>
  92 +João M. M. da Silva + Alessandro Palmeira <jaodsilv@linux.ime.usp.br>
  93 +João M. M. da Silva + Alessandro Palmeira + João Machini <jaodsilv@linux.ime.usp.br>
  94 +João M. M. da Silva + Caio Salgado + Alessandro Palmeira <jaodsilv@linux.ime.usp.br>
  95 +João M. M. da Silva + Caio Salgado <jaodsilv@linux.ime.usp.br>
28 96 João M. M. da Silva + Carlos Morais <jaodsilv@linux.ime.usp.br>
29 97 João M. M. da Silva + Diego Araújo <diegoamc90@gmail.com>
30 98 João M. M. da Silva + Diego Araújo <jaodsilv@linux.ime.usp.br>
  99 +João M. M. da Silva + Diego Araújo + Pedro Leal <jaodsilv@linux.ime.usp.br>
  100 +João M. M. da Silva <jaodsilv@linux.ime.usp.br>
  101 +Joao M. M. da Silva + Jefferson Fernandes <jaodsilv@linux.ime.usp.br>
  102 +João M. M. da Silva + Jefferson Fernandes <jaodsilv@linux.ime.usp.br>
  103 +João M. M. da Silva + João M. Miranda <jaodsilv@linux.ime.usp.br>
31 104 João M. M. da Silva + Paulo Meirelles <jaodsilv@linux.ime.usp.br>
  105 +João M. M. da Silva + Pedro Leal <jaodsilv@linux.ime.usp.br>
  106 +João M. M. da Silva + Rafael Manzo + Diego Araújo <jaodsilv@linux.ime.usp.br>
  107 +João M. M. da Silva + Rafael Manzo <jaodsilv@linux.ime.usp.br>
  108 +João M. M. da Silva + Renan Teruo <jaodsilv@linux.ime.usp.br>
  109 +João M. M. Silva + Caio Salgado <jaodsilv@linux.ime.usp.br>
  110 +João M. M. Silva + Diego Araújo <jaodsilv@linux.ime.usp.br>
  111 +Joao M. M. Silva + Jefferson Fernandes <jaodsilv@linux.ime.usp.br>
  112 +João M. M. Silva + Paulo Meirelles <jaodsilv@linux.ime.usp.br>
  113 +João M. M. Silva + Rafael Manzo <jaodsilv@linux.ime.usp.br>
  114 +João M. M. Silva + Renan Teruo <jaodsilv@linux.ime.usp.br>
32 115 Joenio Costa <joenio@colivre.coop.br>
33 116 Josef Spillner <josef.spillner@tu-dresden.de>
34 117 Keilla Menezes <keilla@colivre.coop.br>
35 118 Larissa Reis <larissa@colivre.coop.br>
36 119 Larissa Reis <reiss.larissa@gmail.com>
37 120 Leandro Nunes dos Santos <leandronunes@gmail.com>
  121 +Leandro Nunes dos Santos <leandro.santos@serpro.gov.br>
38 122 LinguÁgil 2010 <linguagil.bahia@gmail.com>
  123 +Luis David Aguilar Carlos <ludwig9003@gmail.com>
39 124 Martín Olivera <molivera@solar.org.ar>
40 125 Moises Machado <moises@colivre.coop.br>
41 126 Nanda Lopes <nanda.listas+psl@gmail.com>
  127 +Paulo Meirelles + Alessandro Palmeira <paulo@softwarelivre.org>
42 128 Paulo Meirelles + Carlos Morais <paulo@softwarelivre.org>
  129 +Paulo Meirelles + Diego Araújo <paulo@softwarelivre.org>
  130 +Paulo Meirelles + João M. M. da Silva <paulo@softwarelivre.org>
43 131 Paulo Meirelles <paulo@softwarelivre.org>
44 132 Rafael Gomes <rafaelgomes@techfree.com.br>
  133 +Rafael Manzo + João M. M. Silva <rr.manzo@gmail.com>
45 134 Rafael Martins <rmmartins@gmail.com>
  135 +Rafael Reggiani Manzo + Caio Salgado + Jefferson Fernandes <rr.manzo@gmail.com>
  136 +Rafael Reggiani Manzo + Diego Araujo <diegoamc90@gmail.com>
  137 +Rafael Reggiani Manzo + Diego Araujo <rr.manzo@gmail.com>
  138 +Rafael Reggiani Manzo <rr.manzo@gmail.com>
46 139 Raphaël Rousseau <raph@r4f.org>
47 140 Raquel Lira <raquel.lira@gmail.com>
  141 +Renan Teruo + Caio Salgado <renanteruoc@gmail.com>
  142 +Renan Teruoc + Diego Araujo <renanteruoc@gmail.com>
  143 +Renan Teruo + Diego Araujo <renanteruoc@gmail.com>
  144 +Renan Teruo + Diego Araújo <renanteruoc@gmail.com>
  145 +Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com>
  146 +Renan Teruo + Rafael Manzo <renanteruoc@gmail.com>
48 147 Rodrigo Souto <rodrigo@colivre.coop.br>
49 148 Ronny Kursawe <kursawe.ronny@googlemail.com>
50 149 Samuel R. C. Vale <srcvale@holoscopio.com>
... ...
HACKING
1 1 = Noosfero instructions for developers
2 2  
3   -This document provides useful information to those who want to help with
4   -Noosfero development.
  3 +== A work about your the development platform
5 4  
6   -== Requirements for development
  5 +These instructions are tested and known to work on Debian stable, which is the
  6 +system that the Noosfero core developers use to work on Noosfero.
7 7  
8   -First, install all requirements listed in INSTALL. Note that you do not need to
9   -follow all the steps described there, you only need to install the packages
10   -listed in the "Requirements" section.
  8 +If you want to use another OS, read "Instructions for other systems" below.
11 9  
12   -After installing the requirements listed in INSTALL, you need to install some
13   -packages be able to run Noosfero tests. On Debian GNU/Linux and Debian-based
14   -systems, you install them with the following command:
  10 +== Instructions for Debian stable
15 11  
16   - # apt-get install libtidy-ruby libhpricot-ruby libmocha-ruby imagemagick po4a xvfb libxml2-dev libxslt-dev
  12 +Download the source code:
17 13  
18   -On other systems, they may or may not be available through your regular package
19   -management system. Below are the links to their homepages.
  14 + $ git clone git://gitorious.org/noosfero/noosfero.git
  15 + $ cd noosfero
20 16  
21   -* Mocha: http://mocha.rubyforge.org/
22   -* Tidy: http://tidy.sourceforge.net/
23   -* Hpricot: http://github.com/whymirror/hpricot
24   -* Imagemagick: http://wwwimagemagick.org/
25   -* po4a: http://po4a.alioth.debian.org/
26   -* xvfb: http://packages.debian.org/lenny/xvfb
27   -* Libxml2: http://xmlsoft.org/
28   -* Libxslt: http://xmlsoft.org/xslt
  17 +Run the quick start script:
29 18  
30   -== Boostraping a development/test environment
  19 + $ ./script/quick-start
31 20  
32   -You can copy and paste the commands below into a terminal (please review the
33   -commands and make sure you understand what you are doing):
  21 +Now you can execute the development server with:
34 22  
35   - # checkout the code from repository
36   - git clone git://gitorious.org/noosfero/noosfero.git
37   - # enter the directory
38   - cd noosfero
39   - # copy a sample config file
40   - cp config/database.yml.sqlite3 config/database.yml
41   - # create tmp directory if it doesn't exist
42   - mkdir tmp
43   - # start Solr
44   - rake solr:start
45   - # create the development database
46   - rake db:schema:load
47   - # run pending migrations
48   - rake db:migrate
49   - # compile translations:
50   - rake makemo
51   - # create some test data:
52   - ./script/sample-data
53   - # install latest requirements for running tests
54   - RAILS_ENV=cucumber rake gems:install
55   - RAILS_ENV=test rake gems:install
56   - # run the automated test suite to make sure your environment is sane:
57   - rake test
  23 + $ ./script/development
58 24  
59   -You should now be ready to go. Issue the following command to start the Rails
60   -development server:
  25 +You will be able to access Noosfero at http://localhost:3000/
61 26  
62   - ./script/server
  27 +If you want to use a different port than 3000, pass `-p <PORT>` to
  28 +./script/development
63 29  
64   -The server will be available at http://localhost:3000/ . If you want to use
65   -another port than 3000, you can use the -p option of ./script/server:
  30 +== Instructions for other systems
66 31  
67   - ./script/server -p 9999
  32 +On other OS, you have 2 options:
68 33  
69   -The above command makes the server available at http://localhost:9999/
  34 +1) using a chroot or a VM with Debian stable (easier)
70 35  
71   -The sample-data data scripts creates two administrator users with login "ze" and
72   -password "test" and login "adminuser" and password "admin".
  36 +Use a chroot (http://wiki.debian.org/Schroot) or a Virtual Machine (e.g. with
  37 +VirtualBox) with a Debian stable system and follow the instructions above for
  38 +Debian stable.
73 39  
74   -Note that some operations, like generating image thumbnails, sending e-mails,
75   -etc, are done in background in the context of a service independent from the
76   -Rails application server. To have those tasks performed in a development
77   -environment, you must run the delayed_job server like this:
  40 +2) Installing dependencies on other OS (harder)
78 41  
79   - ./script/delayed_job run
  42 +If you want to setup a development environment in another OS, you can create a
  43 +file under script/install-dependencies/, called <OS>-<CODENAME>.sh, which
  44 +installed the dependencies for your system. With this script in place,
  45 +./script/quick-start will call it at the point of installing the required
  46 +packages for Noosfero development.
80 47  
81   -This will block your terminal. To stop the delayed_job server, hit Control-C.
  48 +You can check script/install-dependencies/debian-squeeze.sh to have an idea of
  49 +what kind of stuff that script has to do.
82 50  
83   -== Enabling exceptions notification
84   -
85   -By default, exception notifications are disabled in development environment. If
86   -you want to enable it then you need to change some files:
87   -
88   -1) Add in config/environments/development.rb:
89   - config.action_controller.consider_all_requests_local = false
90   -
91   -2) Add in app/controller/application.rb:
92   - local_addresses.clear
93   -
94   -3) Add in config/noosfero.yml at development section:
95   - exception_recipients: [admin@example.com]
96   -
97   -== Releasing and building Debian package
98   -
99   -See RELEASING file.
  51 +If you write such script for your own OS, *please* share it with us at the
  52 +development mailing list so that we can include it in the official repository.
  53 +This way other people using the same OS will have to put less effort to develop
  54 +Noosfero.
... ...
INSTALL
1   -= Noosfero installation instructions
  1 += Noosfero installation instructions from source for production environments
2 2  
3   -Noosfero is written in Ruby with the "Rails framework":http://www.rubyonrails.org,
4   -so the process of setting it up is pretty similar to other Rails applications.
  3 +The instructions below can be used for setting up a Noosfero production
  4 +environment from the Noosfero sources.
5 5  
6   -Below we have the instructions for installing Noosfero dependencies and setting
7   -up a production environment. If you have problems with the setup, please feel
8   -free to ask questions in the development mailing list.
  6 +Before you start installing Noosfero manually, see the information about the
  7 +Noosfero Debian package at http://noosfero.org/Development/DebianPackage. Using
  8 +the Debian packages on a Debian stable system is the recommended method for
  9 +installing production environments.
  10 +
  11 +If you want to setup a development environment instead of a production one,
  12 +stop reading this file right now and read the file HACKING instead.
  13 +
  14 +For a complete installation guide, please see the following web page:
  15 +http://noosfero.org/Development/HowToInstall
  16 +
  17 +If you have problems with the setup, please feel free to ask questions in the
  18 +development mailing list.
9 19  
10 20 == Requirements
11 21  
  22 +DISCLAIMER: this installation procedure is tested with Debian stable, which is
  23 +currently the only recommended operating system for production usage. It is
  24 +possible that you can install it on other systems, and if you do so, please
  25 +report it on one of the Noosfero mailing lists, and please send a patch
  26 +updating these instructions.
  27 +
  28 +Noosfero is written in Ruby with the "Rails
  29 +framework":http://www.rubyonrails.org, so the process of setting it up is
  30 +pretty similar to other Rails applications.
  31 +
12 32 You need to install some packages Noosfero depends on. On Debian GNU/Linux or
13 33 Debian-based systems, all of these packages are available through the Debian
14 34 archive. You can install them with the following command:
... ... @@ -38,21 +58,9 @@ If you manage to install Noosfero successfully on other systems than Debian,
38 58 please feel free to contact the Noosfero development mailing with the
39 59 instructions for doing so, and we'll include it here.
40 60  
41   -=== Setting up a production environment
42   -
43   -DISCLAIMER: this installation procedure is tested with Debian stable, which is
44   -currently the only recommended operating system for production usage. It is
45   -possible that you can install it on other systems, and if you do so, please
46   -report it on one of the Noosfero mailing lists, and please send a patch
47   -updating these instructions.
48   -
49 61 As root user
50 62 ============
51 63  
52   -NOTE: these instructions are for seting up a *production* environment. If you
53   -are going to do Noosfero development, you don't need to do these steps. Stop
54   -here and see the HACKING file instead.
55   -
56 64 Install memcached. On Debian:
57 65  
58 66 # apt-get install memcached
... ... @@ -96,11 +104,11 @@ $ git checkout -b stable origin/stable
96 104 downloading tarball
97 105 -------------------
98 106  
99   -Note: replace 0.35.0 below from the latest stable version.
  107 +Note: replace 0.39.0 below from the latest stable version.
100 108  
101   -$ wget http://noosfero.org/pub/Development/NoosferoVersion00x35x00/noosfero-0.35.0.tar.gz
102   -$ tar -zxvf noosfero-0.35.0.tar.gz
103   -$ ln -s noosfero-0.35.0 current
  109 +$ wget http://noosfero.org/pub/Development/NoosferoVersion00x39x00/noosfero-0.39.0.tar.gz
  110 +$ tar -zxvf noosfero-0.39.0.tar.gz
  111 +$ ln -s noosfero-0.39.0 current
104 112 $ cd current
105 113  
106 114 Copy config/solr.yml.dist to config/solr.yml. You will
... ... @@ -108,7 +116,7 @@ probably not need to customize this configuration, but have a look at it.
108 116  
109 117 Create the thin configuration file:
110 118  
111   -$ thin -C config/thin.yml config
  119 +$ thin -C config/thin.yml -e production config
112 120  
113 121 Edit config/thin.yml to suit your needs. Make sure your apache
114 122 configuration matches the thin cluster configuration, specially in respect
... ... @@ -147,48 +155,7 @@ Restart postgresql:
147 155  
148 156 Noosfero needs a functional e-mail setup to work: the local mail system should
149 157 be able to deliver e-mail to the internet, either directly or through an
150   -external SMTP server.
151   -
152   -If you know mail systems well, you just need to make sure thet the local MTA,
153   -listening on localhost:25, is able to deliver e-mails to the internet. Any mail
154   -server will do it.
155   -
156   -If you are not a mail specialist, we suggest that you use the Postfix mail
157   -server, since it is easy to configure and very reliable. Just follow the
158   -instructions below.
159   -
160   -To install Postfix:
161   -
162   -# apt-get install postfix
163   -
164   -During the installation process, you will be asked a few questions. Your answer
165   -to them will vary in 2 cases:
166   -
167   -Case 1: you can send e-mails directly to the internet. This will be the case
168   -for most commercial private servers. Your answers should be:
169   -
170   - General type of mail configuration: Internet site
171   - System mail name: the name of your domain, e.g. "mysocialnetwork.com"
172   -
173   -Case 2: you cannot, or don't want to, send e-mail directly to the internet.
174   -This happens for example if your server is not allowed to make outbound
175   -connections on port 25, or if you want to concentrate all your outbound mail
176   -through a single SMTP server. Your answers in this case should be:
177   -
178   - General type of mail configuration: Internet with smarthost
179   - System mail name: the name of your domain, e.g. "mysocialnetwork.com"
180   - SMTP relay host: smtp.yourprovider.com
181   -
182   -Note that smtp.yourprovider.com must allow your server to deliver e-mails
183   -through it. You should probably ask your servive provider about this.
184   -
185   -There is another possibility: if you are installing on a shared server, and
186   -don't have permission to configure the local MTA, you can instruct Noosfero to
187   -send e-mails directly through an external server. Please note that this should
188   -be your last option, since contacting an external SMTP server directly may slow
189   -down your Noosfero application server. To configure Noosfero to send e-mails
190   -through an external SMTP server, follow the instructions on
191   -http://noosfero.org/Development/SMTPMailSending
  158 +external SMTP server. Please check the documentation at the INSTALL.email file.
192 159  
193 160 As noosfero user
194 161 ================
... ... @@ -220,19 +187,6 @@ $ ./script/dbconsole production
220 187 If it connects to your database, then everything is fine. If you got an error
221 188 message, then you have to check your database configuration.
222 189  
223   -Installing gem rack
224   -===================
225   -
226   -This gem is required if you want to run Mezuro plugin.
227   -
228   -Install RubyGem Rack 1.0.1.
229   -Others versions may not be compatible with Noosfero:
230   -
231   -# gem install rack -v 1.0.1
232   -
233   -As noosfero user
234   -================
235   -
236 190 Create the database structure:
237 191  
238 192 $ RAILS_ENV=production rake db:schema:load
... ... @@ -245,7 +199,7 @@ Run Solr:
245 199  
246 200 $ rake solr:start
247 201  
248   -Now we have to create some initial data. To create your default environment
  202 +Now we must create some initial data. To create your default environment
249 203 (the first one), run the command below:
250 204  
251 205 $ RAILS_ENV=production ./script/runner 'Environment.create!(:name => "My environment", :is_default => true)'
... ... @@ -261,10 +215,10 @@ $ RAILS_ENV=production ./script/runner &quot;Environment.default.domains &lt;&lt; Domain.ne
261 215  
262 216 Add at least one user as admin of environment:
263 217  
264   -$ RAILS_ENV=production ./script/runner "User.create(:login => 'adminuser', :email => 'admin@example.com', :password => 'admin', :password_confirmation => 'admin', :environment => Environment.default)"
  218 +$ RAILS_ENV=production ./script/runner "User.create(:login => 'adminuser', :email => 'admin@example.com', :password => 'admin', :password_confirmation => 'admin', :environment => Environment.default, :activated_at => Time.new)"
265 219  
266 220 (replace "adminuser", "admin@example.com", "admin" with the login, email
267   -and password of your environment admin)
  221 +and password of your environment administrator)
268 222  
269 223 To start the Noosfero application servers:
270 224  
... ... @@ -274,23 +228,6 @@ At this point you have a functional Noosfero installation running, the only
274 228 thing left is to configure your webserver as a reverse proxy to pass requests
275 229 to them.
276 230  
277   -Enabling exception notifications
278   -================================
279   -
280   -This is an optional step. You will need it only if you want to receive e-mail
281   -notifications when some exception occurs on Noosfero.
282   -
283   -First, install this version of the gem.
284   -Others versions may not be compatible with Noosfero:
285   -
286   -# gem install exception_notification -v 1.0.20090728
287   -
288   -You can configure the e-mails that will receive the notifications.
289   -Change the file config/noosfero.yml as the following example, replacing the
290   -e-mails by real ones:
291   -
292   - production:
293   - exception_recipients: [admin@example.com, you@example.com]
294 231  
295 232 ==================
296 233 Apache instalation
... ... @@ -384,6 +321,26 @@ Now restart your apache server (as root):
384 321  
385 322 # invoke-rc.d apache2 restart
386 323  
  324 +
  325 +Enabling exception notifications
  326 +================================
  327 +
  328 +This is an optional step. You will need it only if you want to receive e-mail
  329 +notifications when some exception occurs on Noosfero.
  330 +
  331 +First, install this version of the gem.
  332 +Others versions may not be compatible with Noosfero:
  333 +
  334 +# gem install exception_notification -v 1.0.20090728
  335 +
  336 +You can configure the e-mails that will receive the notifications.
  337 +Change the file config/noosfero.yml as the following example, replacing the
  338 +e-mails by real ones:
  339 +
  340 + production:
  341 + exception_recipients: [admin@example.com, you@example.com]
  342 +
  343 +
387 344 ============
388 345 Maintainance
389 346 ============
... ...
INSTALL.email 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 += Noosfero email setup
  2 +
  3 +If you know mail systems well, you just need to make sure that the local MTA,
  4 +listening on localhost:25, is able to deliver e-mails to the internet. Any mail
  5 +server will do it. You can stop reading now.
  6 +
  7 +If you are not an email specialist, then follow the instructions below. We
  8 +suggest that you use the Postfix mail server, since it is easy to configure and
  9 +very reliable. Just follow the instructions below.
  10 +
  11 +To install Postfix:
  12 +
  13 +# apt-get install postfix
  14 +
  15 +During the installation process, you will be asked a few questions. Your answer
  16 +to them will vary in 2 cases:
  17 +
  18 +Case 1: you can send e-mails directly to the internet. This will be the case
  19 +for most commercial private servers. Your answers should be:
  20 +
  21 + General type of mail configuration: Internet site
  22 + System mail name: the name of your domain, e.g. "mysocialnetwork.com"
  23 +
  24 +Case 2: you cannot, or don't want to, send e-mail directly to the internet.
  25 +This happens for example if your server is not allowed to make outbound
  26 +connections on port 25, or if you want to concentrate all your outbound mail
  27 +through a single SMTP server. Your answers in this case should be:
  28 +
  29 + General type of mail configuration: Internet with smarthost
  30 + System mail name: the name of your domain, e.g. "mysocialnetwork.com"
  31 + SMTP relay host: smtp.yourprovider.com
  32 +
  33 +Note that smtp.yourprovider.com must allow your server to deliver e-mails
  34 +through it. You should probably ask your servive provider about this.
  35 +
  36 +There is another possibility: if you are installing on a shared server, and
  37 +don't have permission to configure the local MTA, you can instruct Noosfero to
  38 +send e-mails directly through an external server. Please note that this should
  39 +be your last option, since contacting an external SMTP server directly may slow
  40 +down your Noosfero application server. To configure Noosfero to send e-mails
  41 +through an external SMTP server, follow the instructions on
  42 +http://noosfero.org/Development/SMTPMailSending
  43 +
... ...
INSTALL.varnish
... ... @@ -5,75 +5,63 @@ recommended. See http://www.varnish-cache.org/ for more information on Varnish.
5 5  
6 6 Varnish can be set up to use with Noosfero with the following steps:
7 7  
8   -1) setup Noosfero with apache according to the INSTALL file.
  8 +1) setup Noosfero with apache according to the INSTALL file. If you used the
  9 +Debian package to install noosfero, you don't need to do anything about this.
9 10  
10 11 2) install Varnish
11 12  
12 13 # apt-get install varnish
13 14  
14   -Noosfero was tested with Varnish 2.x. If you are using a Debian Lenny (and you
15   -should, unless Debian already released Squeeze by now), make sure you install
16   -varnish from the lenny-backports suite.
17   -
18 15 Install the RPAF apache module (or skip this step if not using apache):
19 16  
20 17 # apt-get install libapache2-mod-rpaf
21 18  
22   -3) Enable varnish logging:
23   -
24   -3a) Edit /etc/default/varnishncsa and uncomment the line that contains:
25   -
26   -VARNISHNCSA_ENABLED=1
27   -
28   -The varnish log will be written to /var/log/varnish/varnishncsa.log in an
29   -apache-compatible format. You should change your statistics generation software
30   -(e.g. awstats) to use that instead of apache logs.
31   -
32   -3b) Restart Varnish Logging service
33   -
34   - # invoke-rc.d varnishncsa start
35   -
36   -4) Change Apache to listen on port 8080 instead of 80
  19 +3) Change Apache to listen on port 8080 instead of 80
37 20  
38   -4a) Edit /etc/apache2/ports.conf, and:
  21 +3a) Edit /etc/apache2/ports.conf, and:
39 22  
40   - * change 'Listen 80' to 'Listen 127.0.0.1:8080'
41 23 * change 'NameVirtualHost *:80' to 'NameVirtualHost *:8080'
  24 + * change 'Listen 80' to 'Listen 127.0.0.1:8080'
42 25  
43   -4b) Edit /etc/apache2/sites-enabled/*, and change '<VirtualHost *:80>' to '<VirtualHost *:8080>'
  26 +3b) Edit /etc/apache2/sites-enabled/*, and change '<VirtualHost *:80>' to
  27 +'<VirtualHost *:8080>'
44 28  
45   -4c) Restart apache
  29 +3c) Restart apache
46 30  
47 31 # invoke-rc.d apache2 restart
48 32  
49   -5) Change Varnish to listen on port 80
  33 +4) Varnish configuration
50 34  
51   -5a) Edit /etc/default/varnish and change '-a :6081' to '-a :80'
  35 +4a) Edit /etc/default/varnish
52 36  
53   -5b) Restart Varnish
  37 + * change the line that says "START=no" to say "START=yes"
  38 + * change '-a :6081' to '-a :80'
54 39  
55   - # invoke-rc.d varnish restart
  40 +4b) Edit /etc/varnish/default.vcl and add the following lines at the end:
56 41  
57   -6) Configure varnish to fit noosfero
58   -(assuming Noosfero is installed in /var/lib/noosfero)
  42 + include "/etc/noosfero/varnish-noosfero.vcl";
  43 + include "/etc/noosfero/varnish-accept-language.vcl";
59 44  
60   -6a) Configure noosfero to do specific routines to varnish
  45 +On manual installations, change "/etc/noosfero/*" to
  46 +"{Rails.root}/etc/noosfero/*"
61 47  
62   -Add the following line to your /etc/varnish/default.vcl file:
  48 +4c) Restart Varnish
63 49  
64   - include "/var/lib/noosfero/etc/noosfero/varnish-noosfero.vcl";
  50 + # invoke-rc.d varnish restart
65 51  
66   -6b) Configure varnish to store separate caches for each language
  52 +5) Enable varnish logging:
67 53  
68   -Add the following line to your /etc/varnish/default.vcl file:
  54 +5a) Edit /etc/default/varnishncsa and uncomment the line that contains:
69 55  
70   - include "/var/lib/noosfero/etc/noosfero/varnish-accept-language.vcl";
  56 +VARNISHNCSA_ENABLED=1
71 57  
72   -7) Restart Varnish
  58 +The varnish log will be written to /var/log/varnish/varnishncsa.log in an
  59 +apache-compatible format. You should change your statistics generation software
  60 +(e.g. awstats) to use that instead of apache logs.
73 61  
74   - # invoke-rc.d varnish restart
  62 +5b) Restart Varnish Logging service
  63 +
  64 + # invoke-rc.d varnishncsa restart
75 65  
76 66 Thanks to Cosimo Streppone for varnish-accept-language. See
77 67 http://github.com/cosimo/varnish-accept-language for more information.
78   -
79   - -- Antonio Terceiro <terceiro@colivre.coop.br> Sat, 04 Sep 2010 17:29:27 -0300
... ...
app/controllers/my_profile/cms_controller.rb
... ... @@ -16,7 +16,13 @@ class CmsController &lt; MyProfileController
16 16  
17 17 before_filter :login_required, :except => [:suggest_an_article]
18 18  
19   - protect_if :except => [:suggest_an_article, :set_home_page, :edit, :destroy, :publish] do |c, user, profile|
  19 + protect_if :only => :upload_files do |c, user, profile|
  20 + article_id = c.params[:parent_id]
  21 + (!article_id.blank? && profile.articles.find(article_id).allow_create?(user)) ||
  22 + (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile)))
  23 + end
  24 +
  25 + protect_if :except => [:suggest_an_article, :set_home_page, :edit, :destroy, :publish, :upload_files] do |c, user, profile|
20 26 user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))
21 27 end
22 28  
... ... @@ -92,7 +98,7 @@ class CmsController &lt; MyProfileController
92 98 @article_types = []
93 99 available_article_types.each do |type|
94 100 @article_types.push({
95   - :name => type.name,
  101 + :class => type,
96 102 :short_description => type.short_description,
97 103 :description => type.description
98 104 })
... ... @@ -154,7 +160,7 @@ class CmsController &lt; MyProfileController
154 160 end
155 161 if request.post? && params[:uploaded_files]
156 162 params[:uploaded_files].each do |file|
157   - @uploaded_files << UploadedFile.create(:uploaded_data => file, :profile => profile, :parent => @parent) unless file == ''
  163 + @uploaded_files << UploadedFile.create(:uploaded_data => file, :profile => profile, :parent => @parent, :last_changed_by => user) unless file == ''
158 164 end
159 165 @errors = @uploaded_files.select { |f| f.errors.any? }
160 166 if @errors.any?
... ...
app/controllers/my_profile/tasks_controller.rb
... ... @@ -9,7 +9,7 @@ class TasksController &lt; MyProfileController
9 9 end
10 10  
11 11 def processed
12   - @tasks = Task.to(profile).finished.sort_by(&:created_at)
  12 + @tasks = Task.to(profile).closed.sort_by(&:created_at)
13 13 end
14 14  
15 15 VALID_DECISIONS = [ 'finish', 'cancel', 'skip' ]
... ...
app/controllers/public/account_controller.rb
... ... @@ -4,6 +4,7 @@ class AccountController &lt; ApplicationController
4 4  
5 5 before_filter :login_required, :only => [:activation_question, :accept_terms, :activate_enterprise]
6 6 before_filter :redirect_if_logged_in, :only => [:login, :signup]
  7 + before_filter :protect_from_bots, :only => :signup
7 8  
8 9 # say something nice, you goof! something sweet.
9 10 def index
... ... @@ -55,6 +56,11 @@ class AccountController &lt; ApplicationController
55 56 render :action => 'login', :layout => false
56 57 end
57 58  
  59 + def signup_time
  60 + key = set_signup_start_time_for_now
  61 + render :text => { :ok=>true, :key=>key }.to_json
  62 + end
  63 +
58 64 # action to register an user to the application
59 65 def signup
60 66 if @plugins.dispatch(:allow_user_registration).include?(false)
... ... @@ -62,6 +68,7 @@ class AccountController &lt; ApplicationController
62 68 session[:notice] = _("This environment doesn't allow user registration.")
63 69 end
64 70  
  71 + @block_bot = !!session[:may_be_a_bot]
65 72 @invitation_code = params[:invitation_code]
66 73 begin
67 74 if params[:user]
... ... @@ -76,19 +83,28 @@ class AccountController &lt; ApplicationController
76 83 @person = Person.new(params[:profile_data])
77 84 @person.environment = @user.environment
78 85 if request.post?
79   - @user.signup!
80   - owner_role = Role.find_by_name('owner')
81   - @user.person.affiliate(@user.person, [owner_role]) if owner_role
82   - invitation = Task.find_by_code(@invitation_code)
83   - if invitation
84   - invitation.update_attributes!({:friend => @user.person})
85   - invitation.finish
86   - end
87   - if @user.activated?
88   - self.current_user = @user
89   - redirect_to '/'
  86 + if may_be_a_bot
  87 + set_signup_start_time_for_now
  88 + @block_bot = true
  89 + session[:may_be_a_bot] = true
90 90 else
91   - @register_pending = true
  91 + if session[:may_be_a_bot]
  92 + return false unless verify_recaptcha :model=>@user, :message=>_('Captcha (the human test)')
  93 + end
  94 + @user.signup!
  95 + owner_role = Role.find_by_name('owner')
  96 + @user.person.affiliate(@user.person, [owner_role]) if owner_role
  97 + invitation = Task.find_by_code(@invitation_code)
  98 + if invitation
  99 + invitation.update_attributes!({:friend => @user.person})
  100 + invitation.finish
  101 + end
  102 + if @user.activated?
  103 + self.current_user = @user
  104 + redirect_to '/'
  105 + else
  106 + @register_pending = true
  107 + end
92 108 end
93 109 end
94 110 rescue ActiveRecord::RecordInvalid
... ... @@ -97,6 +113,7 @@ class AccountController &lt; ApplicationController
97 113 @person.errors.delete(:user_id)
98 114 render :action => 'signup'
99 115 end
  116 + clear_signup_start_time
100 117 end
101 118  
102 119 # action to perform logout from the application
... ... @@ -271,7 +288,36 @@ class AccountController &lt; ApplicationController
271 288 def no_redirect
272 289 @cannot_redirect = true
273 290 end
274   -
  291 +
  292 + def set_signup_start_time_for_now
  293 + key = 'signup_start_time_' + rand.to_s.split('.')[1]
  294 + Rails.cache.write key, Time.now
  295 + key
  296 + end
  297 +
  298 + def get_signup_start_time
  299 + Rails.cache.read params[:signup_time_key]
  300 + end
  301 +
  302 + def clear_signup_start_time
  303 + Rails.cache.delete params[:signup_time_key] if params[:signup_time_key]
  304 + end
  305 +
  306 + def may_be_a_bot
  307 + # No minimum signup delay, no bot test.
  308 + return false if environment.min_signup_delay == 0
  309 +
  310 + # answering captcha, may be human!
  311 + return false if params[:recaptcha_response_field]
  312 +
  313 + # never set signup_time, hi wget!
  314 + signup_start_time = get_signup_start_time
  315 + return true if signup_start_time.nil?
  316 +
  317 + # so fast, so bot.
  318 + signup_start_time > ( Time.now - environment.min_signup_delay.seconds )
  319 + end
  320 +
275 321 def check_answer
276 322 unless answer_correct
277 323 @enterprise.block
... ...
app/controllers/public/catalog_controller.rb
1 1 class CatalogController < PublicController
2 2 needs_profile
  3 + no_design_blocks
3 4  
4 5 before_filter :check_enterprise_and_environment
5 6  
6 7 def index
7   - @products = @profile.products.paginate(:order => 'name asc', :per_page => 9, :page => params[:page])
  8 + @category = params[:level] ? ProductCategory.find(params[:level]) : nil
  9 + @products = @profile.products.from_category(@category).paginate(:order => 'available desc, highlighted desc, name asc', :per_page => 9, :page => params[:page])
  10 + @categories = ProductCategory.on_level(params[:level])
8 11 end
9 12  
10 13 protected
... ...
app/controllers/public/content_viewer_controller.rb
... ... @@ -25,24 +25,26 @@ class ContentViewerController &lt; ApplicationController
25 25 return
26 26 end
27 27 end
28   -
29   - # page not found, give error
30   - if @page.nil?
31   - render_not_found(@path)
32   - return
33   - end
34 28 end
35 29  
36   - if !@page.display_to?(user)
37   - if profile.display_info_to?(user) || !profile.visible?
38   - message = _('You are not allowed to view this content. You can contact the owner of this profile to request access then.')
  30 + if !@page.nil? && !@page.display_to?(user)
  31 + if !profile.public?
  32 + private_profile_partial_parameters
  33 + render :template => 'profile/_private_profile.rhtml', :status => 403
  34 + else #if !profile.visible?
  35 + message = _('You are not allowed to view this content.')
  36 + message += ' ' + _('You can contact the owner of this profile to request access then.')
39 37 render_access_denied(message)
40   - elsif !profile.public?
41   - redirect_to :controller => 'profile', :action => 'index', :profile => profile.identifier
42 38 end
43 39 return
44 40 end
45 41  
  42 + # page not found, give error
  43 + if @page.nil?
  44 + render_not_found(@path)
  45 + return
  46 + end
  47 +
46 48 if request.xhr? && params[:toolbar]
47 49 render :partial => 'article_toolbar'
48 50 return
... ...
app/controllers/public/profile_controller.rb
... ... @@ -368,18 +368,13 @@ class ProfileController &lt; PublicController
368 368 end
369 369  
370 370 def private_profile
371   - if profile.person?
372   - @action = :add_friend
373   - @message = _("The content here is available to %s's friends only.") % profile.short_name
374   - else
375   - @action = :join
376   - @message = _('The contents in this community is available to members only.')
377   - end
378   - @no_design_blocks = true
  371 + private_profile_partial_parameters
379 372 end
380 373  
381 374 def invisible_profile
382   - render_access_denied(_("This profile is inaccessible. You don't have the permission to view the content here."), _("Oops ... you cannot go ahead here"))
  375 + unless profile.is_template?
  376 + render_access_denied(_("This profile is inaccessible. You don't have the permission to view the content here."), _("Oops ... you cannot go ahead here"))
  377 + end
383 378 end
384 379  
385 380 def per_page
... ...
app/helpers/application_helper.rb
... ... @@ -1284,7 +1284,7 @@ module ApplicationHelper
1284 1284 (user.already_reported?(profile) ?
1285 1285 content_tag('a', text, :class => klass + ' disabled comment-footer comment-footer-link', :title => already_reported_message) :
1286 1286 link_to(text, url, :class => klass + ' comment-footer comment-footer-link', :title => report_profile_message)
1287   - ) + content_tag('span', ' | ', :class => 'comment-footer comment-footer-hide')
  1287 + ) + content_tag('span', ' ', :class => 'comment-footer comment-footer-hide')
1288 1288 end
1289 1289 end
1290 1290  
... ... @@ -1337,11 +1337,12 @@ module ApplicationHelper
1337 1337 counter = 0
1338 1338 radios = klass.templates.map do |template|
1339 1339 counter += 1
1340   - content_tag('li', labelled_radio_button(template.name, "#{field_name}[template_id]", template.id, counter==1))
  1340 + content_tag('li', labelled_radio_button(link_to(template.name, template.url, :target => '_blank'), "#{field_name}[template_id]", template.id, counter==1))
1341 1341 end.join("\n")
1342 1342  
1343   - content_tag('div', content_tag('span', _('Template:')) +
1344   - content_tag('ul', radios, :style => 'list-style: none; padding-left: 0; margin-top: 0.5em;'),
  1343 + content_tag('div', content_tag('label', _('Profile organization'), :for => 'template-options', :class => 'formlabel') +
  1344 + content_tag('p', _('Your profile will be created according to the selected template. Click on the options to view them.'), :style => 'margin: 5px 15px;padding: 0px 10px;') +
  1345 + content_tag('ul', radios, :style => 'list-style: none; padding-left: 20px; margin-top: 0.5em;'),
1345 1346 :id => 'template-options',
1346 1347 :style => 'margin-top: 1em'
1347 1348 )
... ... @@ -1410,4 +1411,16 @@ module ApplicationHelper
1410 1411 options[:class] = "comment-footer comment-footer-link comment-footer-hide"
1411 1412 expirable_content_reference content, action, text, url, options
1412 1413 end
  1414 +
  1415 + def private_profile_partial_parameters
  1416 + if profile.person?
  1417 + @action = :add_friend
  1418 + @message = _("The content here is available to %s's friends only.") % profile.short_name
  1419 + else
  1420 + @action = :join
  1421 + @message = _('The contents in this community is available to members only.')
  1422 + end
  1423 + @no_design_blocks = true
  1424 + end
  1425 +
1413 1426 end
... ...
app/helpers/catalog_helper.rb
... ... @@ -3,4 +3,28 @@ module CatalogHelper
3 3 include DisplayHelper
4 4 include ManageProductsHelper
5 5  
  6 + def breadcrumb(category)
  7 + start = link_to(_('Start'), {:action => 'index'})
  8 + ancestors = category.ancestors.map { |c| link_to(c.name, {:action => 'index', :level => c.id}) }.reverse
  9 + current_level = content_tag('strong', category.name)
  10 + all_items = [start] + ancestors + [current_level]
  11 + content_tag('div', all_items.join(' &rarr; '), :id => 'breadcrumb')
  12 + end
  13 +
  14 + def category_link(category, sub = false)
  15 + count = profile.products.from_category(category).count
  16 + name = truncate(category.name, :length => 22 - count.to_s.size)
  17 + link_name = sub ? name : content_tag('strong', name)
  18 + link = link_to(link_name, {:action => 'index', :level => category.id}, :title => category.name)
  19 + content_tag('li', "#{link} (#{count})") if count > 0
  20 + end
  21 +
  22 + def category_sub_links(category)
  23 + sub_categories = []
  24 + category.children.each do |sub_category|
  25 + sub_categories << category_link(sub_category, true)
  26 + end
  27 + content_tag('ul', sub_categories) if sub_categories.size > 1
  28 + end
  29 +
6 30 end
... ...
app/helpers/content_viewer_helper.rb
... ... @@ -26,7 +26,7 @@ module ContentViewerHelper
26 26 end
27 27 title << content_tag('span',
28 28 content_tag('span', show_date(article.published_at), :class => 'date') +
29   - content_tag('span', [_(", by %s") % link_to(article.author_name, article.author.url)], :class => 'author') +
  29 + content_tag('span', [_(", by %s") % link_to(article.author_name, article.author_url)], :class => 'author') +
30 30 content_tag('span', comments, :class => 'comments'),
31 31 :class => 'created-at'
32 32 )
... ...
app/helpers/display_helper.rb
... ... @@ -8,6 +8,14 @@ module DisplayHelper
8 8 opts
9 9 end
10 10  
  11 + def themed_path(file)
  12 + if File.exists?(File.join(Rails.root, 'public', theme_path, file))
  13 + File.join(theme_path, file)
  14 + else
  15 + file
  16 + end
  17 + end
  18 +
11 19 def image_link_to_product(product, opts={})
12 20 return _('No product') unless product
13 21 target = product_path(product)
... ...
app/helpers/folder_helper.rb
... ... @@ -52,8 +52,8 @@ module FolderHelper
52 52 end
53 53 end
54 54  
55   - def icon_for_new_article(type)
56   - "icon-new icon-new%s" % type.constantize.icon_name
  55 + def icon_for_new_article(klass)
  56 + "icon-new icon-new%s" % klass.icon_name
57 57 end
58 58  
59 59 def custom_options_for_article(article)
... ...
app/helpers/forms_helper.rb
... ... @@ -142,6 +142,38 @@ module FormsHelper
142 142 content_tag('table',rows.join("\n"))
143 143 end
144 144  
  145 + def select_folder(label_text, field_id, collection, default_value=nil, html_options = {}, js_options = {})
  146 + root = profile ? profile.identifier : _("root")
  147 + labelled_form_field(
  148 + label_text,
  149 + select_tag(
  150 + field_id,
  151 + options_for_select(
  152 + [[root, '']] +
  153 + collection.collect {|f| [ root + '/' + f.full_name, f.id ] },
  154 + default_value
  155 + ),
  156 + html_options.merge(js_options)
  157 + )
  158 + )
  159 + end
  160 +
  161 + def select_profile_folder(label_text, field_id, profile, default_value='', html_options = {}, js_options = {})
  162 + result = labelled_form_field(
  163 + label_text,
  164 + select_tag(
  165 + field_id,
  166 + options_for_select(
  167 + [[profile.identifier, '']] +
  168 + profile.folders.collect {|f| [ profile.identifier + '/' + f.full_name, f.id ] },
  169 + default_value
  170 + ),
  171 + html_options.merge(js_options)
  172 + )
  173 + )
  174 + return result
  175 + end
  176 +
145 177 def date_field(name, value, format = '%Y-%m-%d', datepicker_options = {}, html_options = {})
146 178 datepicker_options[:disabled] ||= false
147 179 datepicker_options[:alt_field] ||= ''
... ...
app/helpers/language_helper.rb
... ... @@ -13,19 +13,20 @@ module LanguageHelper
13 13  
14 14 alias :calendar_date_select_language :tinymce_language
15 15  
16   - def language_chooser(environment, options = {})
17   - return if environment.locales.size < 2
  16 + def language_chooser(environment=nil, options = {})
  17 + locales = environment.nil? ? Noosfero.locales : environment.locales
  18 + return if locales.size < 2
18 19 current = language
19 20 separator = options[:separator] || ' &mdash; '
20 21  
21 22 if options[:element] == 'dropdown'
22 23 select_tag('lang',
23   - options_for_select(environment.locales.map{|code,name| [name, code]}, current),
  24 + options_for_select(locales.map{|code,name| [name, code]}, current),
24 25 :onchange => "document.location.href= #{url_for(params.merge(:lang => 'LANGUAGE')).inspect}.replace(/LANGUAGE/, this.value) ;",
25 26 :help => _('The language you choose here is the language used for options, buttons, etc. It does not affect the language of the content created by other users.')
26 27 )
27 28 else
28   - languages = environment.locales.map do |code,name|
  29 + languages = locales.map do |code,name|
29 30 if code == current
30 31 content_tag('strong', name)
31 32 else
... ...
app/models/approve_article.rb
... ... @@ -48,7 +48,7 @@ class ApproveArticle &lt; Task
48 48 end
49 49  
50 50 def perform
51   - article.copy!(:name => name, :abstract => abstract, :body => body, :profile => target, :reference_article => article, :parent => article_parent, :highlighted => highlighted, :source => article.source)
  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.author_id)
52 52 end
53 53  
54 54 def title
... ...
app/models/article.rb
... ... @@ -13,10 +13,18 @@ class Article &lt; ActiveRecord::Base
13 13 # xss_terminate plugin can't sanitize array fields
14 14 before_save :sanitize_tag_list
15 15  
  16 + before_create do |article|
  17 + if article.last_changed_by_id
  18 + article.author_name = Person.find(article.last_changed_by_id).name
  19 + end
  20 + end
  21 +
16 22 belongs_to :profile
17 23 validates_presence_of :profile_id, :name
18 24 validates_presence_of :slug, :path, :if => lambda { |article| !article.name.blank? }
19 25  
  26 + validates_length_of :name, :maximum => 150
  27 +
20 28 validates_uniqueness_of :slug, :scope => ['profile_id', 'parent_id'], :message => N_('The title (article name) is already being used by another article, please use another title.'), :if => lambda { |article| !article.slug.blank? }
21 29  
22 30 belongs_to :last_changed_by, :class_name => 'Person', :foreign_key => 'last_changed_by_id'
... ... @@ -289,7 +297,7 @@ class Article &lt; ActiveRecord::Base
289 297 if last_comment
290 298 {:date => last_comment.created_at, :author_name => last_comment.author_name, :author_url => last_comment.author_url}
291 299 else
292   - {:date => updated_at, :author_name => author.name, :author_url => author.url}
  300 + {:date => updated_at, :author_name => author_name, :author_url => author_url}
293 301 end
294 302 end
295 303  
... ... @@ -441,7 +449,7 @@ class Article &lt; ActiveRecord::Base
441 449 end
442 450  
443 451 def allow_post_content?(user = nil)
444   - user && (user.has_permission?('post_content', profile) || allow_publish_content?(user) && (user == self.creator))
  452 + user && (user.has_permission?('post_content', profile) || allow_publish_content?(user) && (user == author))
445 453 end
446 454  
447 455 def allow_publish_content?(user = nil)
... ... @@ -496,7 +504,6 @@ class Article &lt; ActiveRecord::Base
496 504 :slug,
497 505 :updated_at,
498 506 :created_at,
499   - :last_changed_by_id,
500 507 :version,
501 508 :lock_version,
502 509 :type,
... ... @@ -539,15 +546,24 @@ class Article &lt; ActiveRecord::Base
539 546 end
540 547  
541 548 def author
542   - if reference_article
543   - reference_article.author
  549 + if versions.empty?
  550 + last_changed_by
544 551 else
545   - last_changed_by || profile
  552 + author_id = versions.first.last_changed_by_id
  553 + Person.exists?(author_id) ? Person.find(author_id) : nil
546 554 end
547 555 end
548 556  
549 557 def author_name
550   - setting[:author_name].blank? ? author.name : setting[:author_name]
  558 + author ? author.name : (setting[:author_name] || _('Unknown'))
  559 + end
  560 +
  561 + def author_url
  562 + author ? author.url : nil
  563 + end
  564 +
  565 + def author_id
  566 + author ? author.id : nil
551 567 end
552 568  
553 569 alias :active_record_cache_key :cache_key
... ... @@ -572,11 +588,6 @@ class Article &lt; ActiveRecord::Base
572 588 truncate sanitize_html(self.lead), :length => 170, :omission => '...'
573 589 end
574 590  
575   - def creator
576   - creator_id = versions[0][:last_changed_by_id]
577   - creator_id && Profile.find(creator_id)
578   - end
579   -
580 591 def notifiable?
581 592 false
582 593 end
... ...
app/models/category.rb
... ... @@ -13,6 +13,16 @@ class Category &lt; ActiveRecord::Base
13 13 {:conditions => ['parent_id is null and environment_id = ?', environment.id ]}
14 14 }
15 15  
  16 + named_scope :on_level, lambda { |parent| {:conditions => {:parent_id => parent}} }
  17 +
  18 + named_scope :sub_categories, lambda { |category|
  19 + {:conditions => ['categories.path LIKE ? AND categories.id != ?', "%#{category.slug}%", category.id]}
  20 + }
  21 +
  22 + named_scope :sub_tree, lambda { |category|
  23 + {:conditions => ['categories.path LIKE ?', "%#{category.slug}%"]}
  24 + }
  25 +
16 26 acts_as_filesystem
17 27  
18 28 has_many :article_categorizations, :dependent => :destroy
... ...
app/models/comment.rb
... ... @@ -74,6 +74,10 @@ class Comment &lt; ActiveRecord::Base
74 74 self.find(:all, :order => 'created_at desc, id desc', :limit => limit)
75 75 end
76 76  
  77 + def notification_emails
  78 + self.article.profile.notification_emails - [self.author_email || self.email]
  79 + end
  80 +
77 81 after_save :notify_article
78 82 after_destroy :notify_article
79 83 def notify_article
... ... @@ -114,7 +118,7 @@ class Comment &lt; ActiveRecord::Base
114 118  
115 119 def notify_by_mail
116 120 if source.kind_of?(Article) && article.notify_comments?
117   - if !article.profile.notification_emails.empty?
  121 + if !notification_emails.empty?
118 122 Comment::Notifier.deliver_mail(self)
119 123 end
120 124 emails = article.followers - [author_email]
... ... @@ -174,7 +178,7 @@ class Comment &lt; ActiveRecord::Base
174 178 class Notifier < ActionMailer::Base
175 179 def mail(comment)
176 180 profile = comment.article.profile
177   - recipients profile.notification_emails
  181 + recipients comment.notification_emails
178 182 from "#{profile.environment.name} <#{profile.environment.contact_email}>"
179 183 subject _("[%s] you got a new comment!") % [profile.environment.name]
180 184 body :recipient => profile.nickname || profile.name,
... ... @@ -224,6 +228,7 @@ class Comment &lt; ActiveRecord::Base
224 228 def spam!
225 229 self.spam = true
226 230 self.save!
  231 + SpammerLogger.log(ip_address, self)
227 232 Delayed::Job.enqueue(CommentHandler.new(self.id, :marked_as_spam))
228 233 self
229 234 end
... ...
app/models/enterprise_homepage.rb
... ... @@ -5,7 +5,7 @@ class EnterpriseHomepage &lt; Article
5 5 end
6 6  
7 7 def self.short_description
8   - __('Enterprise homepage.')
  8 + __('Enterprise homepage')
9 9 end
10 10  
11 11 def self.description
... ...
app/models/environment.rb
... ... @@ -233,8 +233,10 @@ class Environment &lt; ActiveRecord::Base
233 233 settings[:message_for_member_invitation] || InviteMember.mail_template
234 234 end
235 235  
  236 + settings_items :min_signup_delay, :type => Integer, :default => 3 #seconds
236 237 settings_items :activation_blocked_text, :type => String
237   - settings_items :message_for_disabled_enterprise, :type => String
  238 + settings_items :message_for_disabled_enterprise, :type => String,
  239 + :default => _('This enterprise needs to be enabled.')
238 240 settings_items :location, :type => String
239 241 settings_items :layout_template, :type => String, :default => 'default'
240 242 settings_items :homepage, :type => String
... ...
app/models/event.rb
... ... @@ -38,7 +38,7 @@ class Event &lt; Article
38 38 filter_iframes :body, :link, :address, :whitelist => lambda { profile && profile.environment && profile.environment.trusted_sites_for_iframe }
39 39  
40 40 def self.description
41   - _('A calendar event')
  41 + _('A calendar event.')
42 42 end
43 43  
44 44 def self.short_description
... ...
app/models/organization.rb
... ... @@ -78,6 +78,8 @@ class Organization &lt; Profile
78 78 country
79 79 tag_list
80 80 template_id
  81 + district
  82 + address_reference
81 83 ]
82 84  
83 85 def self.fields
... ... @@ -96,8 +98,8 @@ class Organization &lt; Profile
96 98 []
97 99 end
98 100  
99   - N_('Display name'); N_('Description'); N_('Contact person'); N_('Contact email'); N_('Acronym'); N_('Foundation year'); N_('Legal form'); N_('Economic activity'); N_('Management information'); N_('Tag list')
100   - settings_items :display_name, :description, :contact_person, :contact_email, :acronym, :foundation_year, :legal_form, :economic_activity, :management_information
  101 + N_('Display name'); N_('Description'); N_('Contact person'); N_('Contact email'); N_('Acronym'); N_('Foundation year'); N_('Legal form'); N_('Economic activity'); N_('Management information'); N_('Tag list'); N_('District'); N_('Address reference')
  102 + settings_items :display_name, :description, :contact_person, :contact_email, :acronym, :foundation_year, :legal_form, :economic_activity, :management_information, :district, :address_reference
101 103  
102 104 validates_format_of :foundation_year, :with => Noosfero::Constants::INTEGER_FORMAT
103 105 validates_format_of :contact_email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda { |org| !org.contact_email.blank? })
... ...
app/models/person.rb
... ... @@ -67,6 +67,9 @@ class Person &lt; Profile
67 67 :order => 'total DESC',
68 68 :conditions => ['action_tracker.created_at >= ? OR action_tracker.id IS NULL', ActionTracker::Record::RECENT_DELAY.days.ago]
69 69  
  70 + named_scope :abusers, :joins => :abuse_complaints, :conditions => ['tasks.status = 3'], :select => 'DISTINCT profiles.*'
  71 + named_scope :non_abusers, :joins => "LEFT JOIN tasks ON profiles.id = tasks.requestor_id AND tasks.type='AbuseComplaint'", :conditions => ["tasks.status != 3 OR tasks.id is NULL"], :select => "DISTINCT profiles.*"
  72 +
70 73 after_destroy do |person|
71 74 Friendship.find(:all, :conditions => { :friend_id => person.id}).each { |friendship| friendship.destroy }
72 75 end
... ... @@ -144,6 +147,9 @@ class Person &lt; Profile
144 147 contact_phone
145 148 contact_information
146 149 description
  150 + image
  151 + district
  152 + address_reference
147 153 ]
148 154  
149 155 validates_multiparameter_assignments
... ... @@ -198,8 +204,8 @@ class Person &lt; Profile
198 204 N_('Education'); N_('Custom education'); N_('Custom area of study');
199 205 settings_items :formation, :custom_formation, :custom_area_of_study
200 206  
201   - N_('Contact information'); N_('City'); N_('State'); N_('Country'); N_('Sex'); N_('Zip code')
202   - settings_items :photo, :contact_information, :sex, :city, :state, :country, :zip_code
  207 + N_('Contact information'); N_('City'); N_('State'); N_('Country'); N_('Sex'); N_('Zip code'); N_('District'); N_('Address reference')
  208 + settings_items :photo, :contact_information, :sex, :city, :state, :country, :zip_code, :district, :address_reference
203 209  
204 210 extend SetProfileRegionFromCityState::ClassMethods
205 211 set_profile_region_from_city_state
... ... @@ -440,6 +446,10 @@ class Person &lt; Profile
440 446 abuse_report.save!
441 447 end
442 448  
  449 + def abuser?
  450 + AbuseComplaint.finished.where(:requestor_id => self).count > 0
  451 + end
  452 +
443 453 def control_panel_settings_button
444 454 {:title => _('Edit Profile'), :icon => 'edit-profile'}
445 455 end
... ...
app/models/product.rb
... ... @@ -23,6 +23,10 @@ class Product &lt; ActiveRecord::Base
23 23  
24 24 named_scope :more_recent, :order => "created_at DESC"
25 25  
  26 + named_scope :from_category, lambda { |category|
  27 + {:joins => :product_category, :conditions => ['categories.path LIKE ?', "%#{category.slug}%"]} if category
  28 + }
  29 +
26 30 after_update :save_image
27 31  
28 32 def lat
... ...
app/models/profile.rb
... ... @@ -141,6 +141,10 @@ class Profile &lt; ActiveRecord::Base
141 141  
142 142 acts_as_having_settings :field => :data
143 143  
  144 + def settings
  145 + data
  146 + end
  147 +
144 148 settings_items :redirect_l10n, :type => :boolean, :default => false
145 149 settings_items :public_content, :type => :boolean, :default => true
146 150 settings_items :description
... ... @@ -229,7 +233,7 @@ class Profile &lt; ActiveRecord::Base
229 233 if myregion
230 234 myregion.hierarchy.reverse.first(2).map(&:name).join(separator)
231 235 else
232   - %w[address city state country_name zip_code ].map {|item| (self.respond_to?(item) && !self.send(item).blank?) ? self.send(item) : nil }.compact.join(separator)
  236 + %w[address district city state country_name zip_code ].map {|item| (self.respond_to?(item) && !self.send(item).blank?) ? self.send(item) : nil }.compact.join(separator)
233 237 end
234 238 end
235 239  
... ... @@ -463,6 +467,10 @@ class Profile &lt; ActiveRecord::Base
463 467 { :profile => identifier, :controller => 'profile_editor', :action => 'index' }
464 468 end
465 469  
  470 + def tasks_url
  471 + { :profile => identifier, :controller => 'tasks', :action => 'index', :host => default_hostname }
  472 + end
  473 +
466 474 def leave_url(reload = false)
467 475 { :profile => identifier, :controller => 'profile', :action => 'leave', :reload => reload }
468 476 end
... ... @@ -694,7 +702,7 @@ private :generate_url, :url_options
694 702 def custom_footer_expanded
695 703 footer = custom_footer
696 704 if footer
697   - %w[contact_person contact_email contact_phone location address economic_activity city state country zip_code].each do |att|
  705 + %w[contact_person contact_email contact_phone location address district address_reference economic_activity city state country zip_code].each do |att|
698 706 if self.respond_to?(att) && footer.match(/\{[^{]*#{att}\}/)
699 707 if !self.send(att).nil? && !self.send(att).blank?
700 708 footer = footer.gsub(/\{([^{]*)#{att}\}/, '\1' + self.send(att))
... ...
app/models/raw_html_article.rb
... ... @@ -5,11 +5,11 @@ class RawHTMLArticle &lt; TextArticle
5 5 end
6 6  
7 7 def self.short_description
8   - _('Raw HTML text article.')
  8 + _('Raw HTML text article')
9 9 end
10 10  
11 11 def self.description
12   - _('Allows HTML without filter (only for admins)')
  12 + _('Allows HTML without filter (only for admins).')
13 13 end
14 14  
15 15 xss_terminate :only => [ ]
... ...
app/models/spammer_logger.rb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +class SpammerLogger < Logger
  2 + @logpath = File.join(Rails.root, 'log', "#{ENV['RAILS_ENV']}_spammers.log")
  3 + @logger = new(@logpath)
  4 +
  5 + def self.log(spammer_ip, object=nil)
  6 + if object
  7 + if object.kind_of?(Comment)
  8 + @logger << "[#{Time.now.strftime("%F %T %z")}] Comment-id: #{object.id} IP: #{spammer_ip}\n"
  9 + end
  10 + else
  11 + @logger << "[#{Time.now.strftime("%F %T %z")}] IP: #{spammer_ip}\n"
  12 + end
  13 + end
  14 +
  15 + def self.clean_log
  16 + File.delete(@logpath) if File.exists?(@logpath)
  17 + end
  18 +
  19 + def self.reload_log
  20 + clean_log
  21 + @logger = new(@logpath)
  22 + end
  23 +
  24 +end
... ...
app/models/task.rb
... ... @@ -267,7 +267,10 @@ class Task &lt; ActiveRecord::Base
267 267 end
268 268  
269 269 named_scope :pending, :conditions => { :status => Task::Status::ACTIVE }
270   - named_scope :finished, :conditions => { :status => [Task::Status::CANCELLED, Task::Status::FINISHED] }
  270 + named_scope :hidden, :conditions => { :status => Task::Status::HIDDEN }
  271 + named_scope :finished, :conditions => { :status => Task::Status::FINISHED }
  272 + named_scope :canceled, :conditions => { :status => Task::Status::CANCELLED }
  273 + named_scope :closed, :conditions => { :status => [Task::Status::CANCELLED, Task::Status::FINISHED] }
271 274 named_scope :opened, :conditions => { :status => [Task::Status::ACTIVE, Task::Status::HIDDEN] }
272 275 named_scope :of, lambda { |type| conditions = type ? "type LIKE '#{type}'" : "1=1"; {:conditions => [conditions]} }
273 276 named_scope :order_by, lambda { |attribute, ord| {:order => "#{attribute} #{ord}"} }
... ...
app/models/task_mailer.rb
... ... @@ -14,7 +14,7 @@ class TaskMailer &lt; ActionMailer::Base
14 14  
15 15 recipients task.target.notification_emails
16 16  
17   - url_for_tasks_list = task.target.kind_of?(Environment) ? '' : url_for(task.target.url.merge(:controller => 'tasks', :action => 'index'))
  17 + url_for_tasks_list = task.target.kind_of?(Environment) ? '' : url_for(task.target.tasks_url)
18 18  
19 19 from self.class.generate_from(task)
20 20 subject '[%s] %s' % [task.environment.name, task.target_notification_description]
... ...
app/models/tiny_mce_article.rb
1 1 class TinyMceArticle < TextArticle
2 2  
3 3 def self.short_description
4   - _('Text article with visual editor.')
  4 + _('Text article with visual editor')
5 5 end
6 6  
7 7 def self.description
... ...
app/models/user.rb
... ... @@ -31,7 +31,7 @@ class User &lt; ActiveRecord::Base
31 31 after_create do |user|
32 32 user.person ||= Person.new
33 33 user.person.attributes = user.person_data.merge(:identifier => user.login, :user => user, :environment_id => user.environment_id)
34   - user.person.name ||= user.login
  34 + user.person.name ||= user.name
35 35 user.person.visible = false unless user.activated?
36 36 user.person.save!
37 37 if user.environment.enabled?('skip_new_user_email_confirmation')
... ...
app/views/account/_signup_form.rhtml
1   -<%= error_messages_for :user, :person %>
  1 +<% if @block_bot %>
  2 + <div class="atention" style="font-size: 150%;">
  3 + <strong><%=_('Are you a robot?')%></strong> <br />
  4 + <%=_('Please, prove that you are human by filling the captcha.')%>
  5 + </div>
  6 +<% end %>
  7 +
  8 +<% @profile_data = @person %>
  9 +
  10 +<%= error_messages_for :user, :person, :header_message => _('The account could not be created') %>
  11 +
  12 +<% labelled_form_for :user, @user, :html => { :multipart => true, :id => 'signup-form', :honeypot => true } do |f| %>
2 13  
3   -<% labelled_form_for :user, @user, :html => { :multipart => true, :id => 'signup-form' } do |f| %>
  14 +<input type="hidden" id="signup_time_key" name="signup_time_key" />
  15 +<script type="text/javascript">
  16 + jQuery.ajax({
  17 + type: "POST",
  18 + url: "<%= url_for :controller=>'account', :action=>'signup_time' %>",
  19 + dataType: 'json',
  20 + success: function(data) {
  21 + if (data.ok) jQuery('#signup_time_key').val(data.key);
  22 + }
  23 + });
  24 +</script>
4 25  
5 26 <%= hidden_field_tag :invitation_code, @invitation_code %>
6 27  
7 28 <div id='signup-form-header'>
8 29  
9   - <span id="signup-domain"><%= environment.default_hostname %>/</span>
10   - <div id='signup-login'>
11   - <div id='signup-login-field'>
12   - <%= required f.text_field(:login, :onchange => 'this.value = convToValidLogin(this.value);', :rel => s_('signup|Login')) %>
13   - <div id='url-check'><p>&nbsp;</p></div>
  30 + <div id='signup-formfield-group'>
  31 + <%= label(:user, :login, _('Username'), {:class => 'formlabel'}) %>
  32 + <span id="signup-domain"><%= environment.default_hostname %>/</span>
  33 + <div id='signup-login'>
  34 + <div id='signup-login-field' class='formfield'>
  35 + <%= required text_field(:user, :login, :id => 'user_login', :onchange => 'this.value = convToValidLogin(this.value);') %>
  36 + <div id='url-check'><p>&nbsp;</p></div>
  37 + </div>
  38 + <%= content_tag(:small, _('Choose your login name carefully! It will be your network access and you will not be able to change it later.'), :id => 'signup-balloon') %>
  39 + <br style="clear: both;" />
14 40 </div>
15   - <%= content_tag(:small, _('Choose your login name carefully! It will be your network access and you will not be able to change it later.'), :id => 'signup-balloon') %>
16   - <br style="clear: both;" />
17 41 </div>
18 42 <%= observe_field 'user_login',
19 43 :url => { :action => 'check_url' },
... ... @@ -26,20 +50,19 @@
26 50  
27 51 <div id='signup-password'>
28 52 <%= required f.password_field(:password, :id => 'user_pw') %>
29   - <%= f.text_field(:password_clear, :value => _('password')) %>
30 53 <%= content_tag(:small,_('Choose a password that you can remember easily. It must have at least 4 characters.'), :id => 'password-balloon') %>
31 54 <div id='fake-check'><p>&nbsp;</p></div>
32 55 </div>
33 56  
34 57 <div id='signup-password-confirmation'>
35 58 <%= required f.password_field(:password_confirmation) %>
36   - <%= f.text_field(:password_confirmation_clear, :value => _('password confirmation')) %>
  59 + <%= content_tag(:small,_('We need to be sure that you filled in your password correctly. Confirm you password.'), :id => 'password-confirmation-balloon') %>
37 60 <div id='password-check'><p>&nbsp;</p></div>
38 61 </div>
39 62  
40 63 <div id='signup-email'>
41   - <%= required f.text_field(:email, :rel => _('e-Mail')) %>
42   - <%= content_tag(:small,_('This e-mail address will be used to contact you.')) %>
  64 + <%= required f.text_field(:email) %>
  65 + <%= content_tag(:small,_('This e-mail address will be used to contact you.'), :id => 'email-balloon') %>
43 66 <div id='email-check'><p>&nbsp;</p></div>
44 67 </div>
45 68 <%= observe_field "user_email",
... ... @@ -62,21 +85,23 @@
62 85 }"
63 86 %>
64 87  
65   - <%= label :profile_data, :name %>
66   - <%= required text_field(:profile_data, :name, :rel => _('Full name')) %>
  88 + <div id='signup-name'>
  89 + <%= labelled_form_field(_('Full name'), text_field(:profile_data, :name)) %>
  90 + <%= content_tag(:small,_('Tell us your name, it will be used to identify yourself.'), :id => 'name-balloon') %>
  91 + </div>
67 92  
68 93 </div>
69 94  
70 95 <div id="signup-form-profile">
71 96  
72   - <%= template_options(Person, 'profile_data') %>
73   -
74 97 <% labelled_fields_for :profile_data, @person do |f| %>
75 98 <%= render :partial => 'profile_editor/person_form', :locals => {:f => f} %>
76 99 <% end %>
77 100  
78 101 <%= @plugins.dispatch(:signup_extra_contents).collect { |content| instance_eval(&content) }.join("") %>
79 102  
  103 + <%= template_options(Person, 'profile_data') %>
  104 +
80 105 <% unless @terms_of_use.blank? %>
81 106 <div id='terms-of-use-box' class='formfieldline'>
82 107 <%= labelled_check_box(_('I accept the %s') % link_to(_('terms of use'), {:controller => 'home', :action => 'terms'}, :target => '_blank'), 'user[terms_accepted]') %>
... ... @@ -91,6 +116,8 @@
91 116 <% end %>
92 117 </div>
93 118  
  119 +<%= recaptcha_tags :ajax => true, :display => {:theme => 'clean'} if @block_bot %>
  120 +
94 121 <p style="text-align: center">
95 122 <%= submit_button('save', _('Create my account')) %>
96 123 </p>
... ... @@ -99,70 +126,59 @@
99 126  
100 127 <script type="text/javascript">
101 128 jQuery(function($) {
  129 +
  130 + $('#signup-form #user_login').css('width', 335 - $('#signup-domain').outerWidth());
  131 +
102 132 $('#signup-form input[type=text], #signup-form textarea').each(function() {
103   - if ($(this).attr('rel')) var default_value = $(this).attr('rel').toLowerCase();
104   - if ($(this).val() == '') $(this).val(default_value);
105   - $(this).bind('focus', function() {
106   - if ($(this).val() == default_value) $(this).val('');
107   - });
108 133 $(this).bind('blur', function() {
109 134 if ($(this).val() == '') {
110   - $(this).val(default_value);
111 135 $(this).removeClass('filled-in');
112 136 }
113 137 else $(this).addClass('filled-in');
114 138 });
115 139 });
116 140  
117   - $('#signup-form').bind('submit', function() {
118   - $('#signup-form input[type=text], #signup-form textarea').each(function() {
119   - if ($(this).attr('rel')) var default_value = $(this).attr('rel').toLowerCase();
120   - if ($(this).val() == default_value) $(this).val('');
121   - });
122   - return true;
123   - });
124   -
125   - $('#user_password_clear, #user_password_confirmation_clear').show();
126   - $('#user_password_clear, #user_password_confirmation_clear').unbind();
127   - $('#user_pw, #user_password_confirmation').hide();
128   - $('#user_password_clear').focus(function() {
129   - $(this).hide();
130   - $('#user_pw').show();
131   - $('#user_pw').focus();
132   - });
133 141 $('#user_pw').focus(function() {
134 142 $('#password-balloon').fadeIn('slow');
135 143 });
136   - $('#user_pw').blur(function() {
137   - if ($(this).val() == '') {
138   - $('#user_password_clear').show();
139   - $(this).hide();
140   - }
141   - });
142   - $('#user_password_confirmation_clear').focus(function() {
143   - $(this).hide();
144   - $('#user_password_confirmation').show();
145   - $('#user_password_confirmation').focus();
  144 + $('#user_password_confirmation').focus(function() {
  145 + $('#password-confirmation-balloon').fadeIn('slow');
146 146 });
147 147 $('#user_password_confirmation, #user_pw').blur(function() {
148   - if ($('#user_password_confirmation').val() == '') {
149   - $('#user_password_confirmation_clear').show();
150   - $('#user_password_confirmation').hide();
151   - } else if ($('#user_password_confirmation').val() == $('#user_pw').val()) {
152   - $('#user_password_confirmation').addClass('validated').removeClass('invalid');
153   - $('#user_pw').removeClass('invalid_input').addClass('valid_input');
154   - $('#password-check').html("<p>&nbsp;</p>");
155   - } else if ($('#user_password_confirmation').val() != $('#user_pw').val()) {
156   - $('#user_password_confirmation').removeClass('validated').addClass('invalid');
157   - $('#user_pw').addClass('invalid_input').removeClass('valid_input');
158   - $('#password-check').html("<p><span class='invalid'><%= _('Passwords don\'t match') %></span></p>");
  148 + if ($('#user_password_confirmation').val() != '') {
  149 + if ($('#user_password_confirmation').val() == $('#user_pw').val()) {
  150 + $('#user_password_confirmation').addClass('validated').removeClass('invalid');
  151 + $('#user_pw').removeClass('invalid_input').addClass('valid_input');
  152 + $('#password-check').html("<p>&nbsp;</p>");
  153 + } else if ($('#user_password_confirmation').val() != $('#user_pw').val()) {
  154 + $('#user_password_confirmation').removeClass('validated').addClass('invalid');
  155 + $('#user_pw').addClass('invalid_input').removeClass('valid_input');
  156 + $('#password-check').html("<p><span class='invalid'><%= _('Passwords don\'t match') %></span></p>");
  157 + }
159 158 }
160 159 $('#password-balloon').fadeOut('slow');
  160 + $('#password-confirmation-balloon').fadeOut('slow');
161 161 });
162 162 $('#user_login').focus(function() {
163 163 $('#signup-balloon').fadeIn('slow');
164 164 });
165 165 $('#user_login').blur(function() { $('#signup-balloon').fadeOut('slow'); });
166 166 $('#signup-form').validate({ rules: { 'user[email]': { email: true } }, messages: { 'user[email]' : '' } });
  167 + $('#user_email').focus(function() {
  168 + $('#email-balloon').fadeIn('slow');
  169 + });
  170 + $('#user_email').blur(function() {
  171 + $('#email-balloon').fadeOut('slow');
  172 + });
  173 + $('#profile_data_name').focus(function() {
  174 + $('#name-balloon').fadeIn('slow');
  175 + });
  176 + $('#profile_data_name').blur(function() {
  177 + $('#name-balloon').fadeOut('slow');
  178 + if ($(this).val() == '') {
  179 + $(this).removeClass('validated');
  180 + }
  181 + else $(this).addClass('validated');
  182 + });
167 183 });
168 184 </script>
... ...
app/views/catalog/index.rhtml
1 1 <% extra_content = [] %>
2 2 <% extra_content_list = [] %>
3 3  
4   -<ul id="product-list">
5   - <li><h1><%= _('Products/Services') %></h1></li>
  4 +<h1><%= _('Products/Services') %></h1>
6 5  
  6 +<%= breadcrumb(@category) if params[:level] %>
  7 +
  8 +<div class='l-sidebar-left-bar'>
  9 + <ul>
  10 + <% if @categories.size > 0 %>
  11 + <% @categories.each do |category| %>
  12 + <%= category_link(category) %>
  13 + <%= category_sub_links(category) %>
  14 + <% end %>
  15 + <% else %>
  16 + <%= content_tag('li', _('There are no sub-categories for %s') % @category.name, :style => 'color: #555753; padding-bottom: 0.5em;') %>
  17 + <% end %>
  18 + </ul>
  19 +</div>
  20 +
  21 +<ul id="product-list" class="l-sidebar-left-content">
7 22 <% @products.each do |product| %>
8 23 <% extra_content = @plugins.dispatch(:catalog_item_extras, product).collect { |content| instance_eval(&content) } %>
9 24 <% extra_content_list = @plugins.dispatch(:catalog_list_item_extras, product).collect { |content| instance_eval(&content) } %>
10 25  
11   - <li class="product <%= "not-available" unless product.available %>">
  26 + <% status = [] %>
  27 + <% status << 'not-available' if !product.available %>
  28 + <% status << 'highlighted' if product.highlighted %>
  29 +
  30 + <li id="product-<%= product.id %>" class="product <%= status.join(' ') %>">
12 31 <ul>
13 32 <li class="product-image-link">
  33 + <% if product.highlighted? %>
  34 + <%= link_to image_tag(themed_path('/images/star.png'), :class => 'star', :alt => _('Highlighted product')), product_path(product) %>
  35 + <% end %>
14 36 <% if product.image %>
15 37 <div class="zoomable-image">
16 38 <%= link_to_product product, :class => 'product-big', :style => "background-image: url(#{product.default_image(:big)})" %>
... ...
app/views/cms/_blog.rhtml
... ... @@ -4,7 +4,7 @@
4 4  
5 5 <%= render :file => 'shared/tiny_mce' %>
6 6  
7   -<%= required f.text_field(:name, :size => '64', :onchange => "updateUrlField(this, 'article_slug')") %>
  7 +<%= required f.text_field(:name, :size => '64', :maxlength => 150, :onchange => "updateUrlField(this, 'article_slug')") %>
8 8  
9 9 <%= render :partial => 'general_fields' %>
10 10  
... ...
app/views/cms/_event.rhtml
... ... @@ -3,7 +3,7 @@
3 3 <%# TODO add Textile help here %>
4 4 <%= render :file => 'shared/tiny_mce' %>
5 5  
6   -<%= required f.text_field('name', :size => '64') %>
  6 +<%= required f.text_field('name', :size => '64', :maxlength => 150) %>
7 7  
8 8 <%= render :partial => 'general_fields' %>
9 9 <%= render :partial => 'translatable' %>
... ...
app/views/cms/_folder.rhtml
1 1 <%= required_fields_message %>
2 2  
3   -<%= required f.text_field('name', :size => '64') %>
  3 +<%= required f.text_field('name', :size => '64', :maxlength => 150) %>
4 4 <%= render :partial => 'general_fields' %>
5 5  
6 6 <%= labelled_form_field(_('Description:'), text_area(:article, :body, :rows => 3, :cols => 64)) %>
... ...
app/views/cms/_forum.rhtml
... ... @@ -4,7 +4,7 @@
4 4  
5 5 <%= render :file => 'shared/tiny_mce' %>
6 6  
7   -<%= required f.text_field(:name, :size => '64', :onchange => "updateUrlField(this, 'article_slug')") %>
  7 +<%= required f.text_field(:name, :size => '64', :maxlength => 150, :onchange => "updateUrlField(this, 'article_slug')") %>
8 8  
9 9 <%= render :partial => 'general_fields' %>
10 10  
... ...
app/views/cms/_gallery.rhtml
1 1 <%= required_fields_message %>
2 2  
3   -<%= required f.text_field('name', :size => '64') %>
  3 +<%= required f.text_field('name', :size => '64', :maxlength => 150) %>
4 4  
5 5 <%= render :partial => 'general_fields' %>
6 6  
... ...
app/views/cms/_published_article.rhtml
1   -<%= f.text_field 'name', :size => '64' %>
  1 +<%= f.text_field 'name', :size => '64', :maxlength => 150 %>
2 2 <%= render :partial => 'general_fields' %>
3 3  
4 4 <p><%= _('This is a republication of "%s", by %s.') % [link_to(h(@article.reference_article.name), @article.reference_article.url), @article.reference_article.profile.name] %></p>
... ...
app/views/cms/_raw_html_article.rhtml
1 1 <%= required_fields_message %>
2 2  
3   -<%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64')) %>
  3 +<%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64', :maxlength => 150)) %>
4 4  
5 5 <%= render :partial => 'general_fields' %>
6 6 <%= render :partial => 'translatable' %>
... ...
app/views/cms/_textile_article.rhtml
... ... @@ -2,7 +2,7 @@
2 2  
3 3 <%# TODO add Textile help here %>
4 4  
5   -<%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '72')) %>
  5 +<%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '72', :maxlength => 150)) %>
6 6  
7 7 <%= render :partial => 'general_fields' %>
8 8 <%= render :partial => 'translatable' %>
... ...
app/views/cms/_tiny_mce_article.rhtml
... ... @@ -3,7 +3,7 @@
3 3 <%= render :file => 'shared/tiny_mce' %>
4 4  
5 5 <div>
6   - <%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64')) %>
  6 + <%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64', :maxlength => 150)) %>
7 7  
8 8 <%= render :partial => 'general_fields' %>
9 9 <%= render :partial => 'translatable' %>
... ...
app/views/cms/select_article_type.rhtml
... ... @@ -2,9 +2,9 @@
2 2  
3 3 <ul id="article_types">
4 4 <% for type in @article_types %>
5   - <% action = type[:name] == 'UploadedFile' ? {:action => 'upload_files'} : {:action => 'new', :type => type[:name]} %>
  5 + <% action = type[:class].name == 'UploadedFile' ? {:action => 'upload_files'} : {:action => 'new', :type => type[:class].name} %>
6 6 <% content_tag('a', :href => url_for(action.merge(:parent_id => @parent_id, :back_to => @back_to))) do %>
7   - <li class="<%= icon_for_new_article(type[:name]) %>" onmouseover="javascript: jQuery(this).addClass('mouseover')" onmouseout="jQuery(this).removeClass('mouseover')">
  7 + <li class="<%= icon_for_new_article(type[:class]) %>" onmouseover="javascript: jQuery(this).addClass('mouseover')" onmouseout="jQuery(this).removeClass('mouseover')">
8 8 <strong><%= type[:short_description] %></strong>
9 9 <div class='description'><%= type[:description] %></div>
10 10 </li>
... ...
app/views/content_viewer/_article_toolbar.rhtml
... ... @@ -34,11 +34,11 @@
34 34 <%= expirable_button @page, :locale, content, url %>
35 35 <% end %>
36 36  
37   - <%= colorbox_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) %>
  37 + <%= colorbox_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) unless remove_content_button(:new) %>
38 38 <% end %>
39 39  
40 40 <% if @page.accept_uploads? && @page.allow_create?(user) %>
41   - <%= button('upload-file', _('Upload files'), profile.admin_url.merge(:controller => 'cms', :action => 'upload_files', :parent_id => (@page.folder? ? @page : @page.parent))) %>
  41 + <%= button('upload-file', _('Upload files'), profile.admin_url.merge(:controller => 'cms', :action => 'upload_files', :parent_id => (@page.folder? ? @page : @page.parent))) unless remove_content_button(:upload)%>
42 42 <% end %>
43 43  
44 44 <% if !@page.allow_create?(user) && profile.community? && (@page.blog? || @page.parent && @page.parent.blog?) && !remove_content_button(:suggest) %>
... ...
app/views/profile/_create_article.rhtml
... ... @@ -9,7 +9,7 @@
9 9 <div class='profile-activity-lead'>
10 10 <div class='article-name'><%= link_to(activity.params['name'], activity.params['url']) %></div>
11 11 <span title='<%= activity.target.class.short_description %>' class='profile-activity-icon icon-new icon-new<%= activity.target.class.icon_name %>'></span>
12   - <%= image_tag(activity.params['first_image']) unless activity.params['first_image'].blank? %><%= strip_tags(truncate(activity.params['lead'], :length => 1000, :ommision => '...')).gsub(/(\xA0|\xC2|\s)+/, ' ').gsub(/^\s+/, '') %> <small><%= link_to(_('See more'), activity.params['url']) unless activity.get_lead.blank? %></small>
  12 + <%= image_tag(activity.params['first_image']) unless activity.params['first_image'].blank? %><%= strip_tags(truncate(activity.params['lead'], :length => 1000, :ommision => '...')).gsub(/(\xC2\xA0|\s)+/, ' ').gsub(/^\s+/, '') %> <small><%= link_to(_('See more'), activity.params['url']) unless activity.get_lead.blank? %></small>
13 13 </div>
14 14 <%= content_tag(:p, link_to(_('See complete forum'), activity.get_url), :class => 'see-forum') if activity.target.is_a?(Forum) %>
15 15 <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p>
... ...
app/views/profile/communities.rhtml
... ... @@ -5,7 +5,7 @@
5 5 <% cache_timeout(profile.communities_cache_key(params), 4.hour) do %>
6 6 <ul class='profile-list'>
7 7 <% @communities.each do |community| %>
8   - <li><%= profile_image_link(community)%></li>
  8 + <%= profile_image_link(community)%>
9 9 <% end %>
10 10 </ul>
11 11  
... ...
app/views/profile_editor/_person_form.rhtml
... ... @@ -21,6 +21,8 @@
21 21 <%= optional_field(@person, 'city', f.text_field(:city, :rel => _('City'))) %>
22 22 <%= optional_field(@person, 'zip_code', labelled_form_field(_('ZIP code'), text_field(:profile_data, :zip_code, :rel => _('ZIP code')))) %>
23 23 <%= optional_field(@person, 'address', labelled_form_field(_('Address (street and number)'), text_field(:profile_data, :address, :rel => _('Address')))) %>
  24 +<%= optional_field(@person, 'address_reference', labelled_form_field(_('Address reference'), text_field(:profile_data, :address_reference, :rel => _('Address reference')))) %>
  25 +<%= optional_field(@person, 'district', labelled_form_field(_('District'), text_field(:profile_data, :district, :rel => _('District')))) %>
24 26  
25 27 <% optional_field(@person, 'schooling') do %>
26 28 <div class="formfieldline">
... ...
app/views/search/_product.rhtml
1   -<% extra_content = @plugins.dispatch(:asset_product_extras, product, product.enterprise).collect { |content| instance_eval(&content) } %>
  1 +<% extra_content = @plugins.dispatch(:asset_product_extras, product).collect { |content| instance_eval(&content) } %>
2 2 <% extra_properties = @plugins.dispatch(:asset_product_properties, product)%>
3 3  
4   -<li class="search-product-item">
  4 +<li class="search-product-item <%= 'highlighted' if product.highlighted? %>">
5 5  
6 6 <div class="search-product-item-first-column">
7 7 <%= render :partial => 'search/image', :object => product %>
... ...
app/views/shared/_organization_custom_fields.rhtml
... ... @@ -10,6 +10,8 @@
10 10 <%= optional_field(profile, 'economic_activity', f.text_field(:economic_activity)) %>
11 11 <%= optional_field(profile, 'management_information', f.text_area(:management_information, :rows => 5)) %>
12 12 <%= optional_field(profile, 'address', labelled_form_field(_('Address (street and number)'), text_field(object_name, :address))) %>
  13 +<%= optional_field(profile, 'address_reference', labelled_form_field(_('Address reference'), text_field(object_name, :address_reference))) %>
  14 +<%= optional_field(profile, 'district', labelled_form_field(_('District'), text_field(object_name, :district))) %>
13 15 <%= optional_field(profile, 'zip_code', labelled_form_field(_('ZIP code'), text_field(object_name, :zip_code))) %>
14 16 <%= optional_field(profile, 'city', f.text_field(:city)) %>
15 17 <%= optional_field(profile, 'state', f.text_field(:state)) %>
... ...
config/initializers/plugins.rb
... ... @@ -3,4 +3,5 @@ require &#39;noosfero/plugin/hot_spot&#39;
3 3 require 'noosfero/plugin/manager'
4 4 require 'noosfero/plugin/active_record'
5 5 require 'noosfero/plugin/mailer_base'
  6 +require 'noosfero/plugin/settings'
6 7 Noosfero::Plugin.init_system if $NOOSFERO_LOAD_PLUGINS
... ...
db/migrate/20130111232201_aggressive_indexing_strategy3.rb 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +class AggressiveIndexingStrategy3 < ActiveRecord::Migration
  2 + def self.up
  3 + add_index :articles, :slug
  4 + add_index :articles, :parent_id
  5 + add_index :articles, :profile_id
  6 + add_index :articles, :name
  7 +
  8 + add_index :article_versions, :article_id
  9 +
  10 + add_index :comments, [:source_id, :spam]
  11 +
  12 + add_index :profiles, :identifier
  13 +
  14 + add_index :friendships, :person_id
  15 + add_index :friendships, :friend_id
  16 + add_index :friendships, [:person_id, :friend_id], :uniq => true
  17 +
  18 + add_index :external_feeds, :blog_id
  19 + end
  20 +
  21 + def self.down
  22 + remove_index :articles, :slug
  23 + remove_index :articles, :parent_id
  24 + remove_index :articles, :profile_id
  25 + remove_index :articles, :name
  26 +
  27 + remove_index :article_versions, :article_id
  28 +
  29 + remove_index :comments, [:source_id, :spam]
  30 +
  31 + remove_index :profiles, :identifier
  32 +
  33 + remove_index :friendships, :person_id
  34 + remove_index :friendships, :friend_id
  35 + remove_index :friendships, [:person_id, :friend_id]
  36 +
  37 + remove_index :external_feeds, :blog_id
  38 + end
  39 +end
... ...
db/schema.rb
... ... @@ -9,7 +9,7 @@
9 9 #
10 10 # It's strongly recommended to check this file into your version control system.
11 11  
12   -ActiveRecord::Schema.define(:version => 20121008185303) do
  12 +ActiveRecord::Schema.define(:version => 20130111232201) do
13 13  
14 14 create_table "abuse_reports", :force => true do |t|
15 15 t.integer "reporter_id"
... ... @@ -88,6 +88,8 @@ ActiveRecord::Schema.define(:version =&gt; 20121008185303) do
88 88 t.integer "license_id"
89 89 end
90 90  
  91 + add_index "article_versions", ["article_id"], :name => "index_article_versions_on_article_id"
  92 +
91 93 create_table "articles", :force => true do |t|
92 94 t.string "name"
93 95 t.string "slug"
... ... @@ -129,6 +131,10 @@ ActiveRecord::Schema.define(:version =&gt; 20121008185303) do
129 131 t.integer "license_id"
130 132 end
131 133  
  134 + add_index "articles", ["name"], :name => "index_articles_on_name"
  135 + add_index "articles", ["parent_id"], :name => "index_articles_on_parent_id"
  136 + add_index "articles", ["profile_id"], :name => "index_articles_on_profile_id"
  137 + add_index "articles", ["slug"], :name => "index_articles_on_slug"
132 138 add_index "articles", ["translation_of_id"], :name => "index_articles_on_translation_of_id"
133 139  
134 140 create_table "articles_categories", :id => false, :force => true do |t|
... ... @@ -217,6 +223,8 @@ ActiveRecord::Schema.define(:version =&gt; 20121008185303) do
217 223 t.string "referrer"
218 224 end
219 225  
  226 + add_index "comments", ["source_id", "spam"], :name => "index_comments_on_source_id_and_spam"
  227 +
220 228 create_table "contact_lists", :force => true do |t|
221 229 t.text "list"
222 230 t.string "error_fetching"
... ... @@ -280,6 +288,7 @@ ActiveRecord::Schema.define(:version =&gt; 20121008185303) do
280 288 t.integer "update_errors", :default => 0
281 289 end
282 290  
  291 + add_index "external_feeds", ["blog_id"], :name => "index_external_feeds_on_blog_id"
283 292 add_index "external_feeds", ["enabled"], :name => "index_external_feeds_on_enabled"
284 293 add_index "external_feeds", ["fetched_at"], :name => "index_external_feeds_on_fetched_at"
285 294  
... ... @@ -295,6 +304,10 @@ ActiveRecord::Schema.define(:version =&gt; 20121008185303) do
295 304 t.string "group"
296 305 end
297 306  
  307 + add_index "friendships", ["friend_id"], :name => "index_friendships_on_friend_id"
  308 + add_index "friendships", ["person_id", "friend_id"], :name => "index_friendships_on_person_id_and_friend_id"
  309 + add_index "friendships", ["person_id"], :name => "index_friendships_on_person_id"
  310 +
298 311 create_table "images", :force => true do |t|
299 312 t.integer "parent_id"
300 313 t.string "content_type"
... ... @@ -446,6 +459,7 @@ ActiveRecord::Schema.define(:version =&gt; 20121008185303) do
446 459 end
447 460  
448 461 add_index "profiles", ["environment_id"], :name => "index_profiles_on_environment_id"
  462 + add_index "profiles", ["identifier"], :name => "index_profiles_on_identifier"
449 463 add_index "profiles", ["region_id"], :name => "index_profiles_on_region_id"
450 464  
451 465 create_table "qualifier_certifiers", :force => true do |t|
... ...
debian/changelog
  1 +noosfero (0.41.0) unstable; urgency=low
  2 +
  3 + * Features version with anti spam-bot measures
  4 +
  5 + -- Rodrigo Souto <rodrigo@colivre.coop.br> Mon, 28 Jan 2013 10:20:21 +0000
  6 +
  7 +noosfero (0.40.0) unstable; urgency=low
  8 +
  9 + * Features version release
  10 +
  11 + -- Rodrigo Souto <rodrigo@colivre.coop.br> Sat, 19 Jan 2013 21:58:06 +0000
  12 +
  13 +noosfero (0.39.3) unstable; urgency=low
  14 +
  15 + * Bugfixes release
  16 +
  17 + -- Rodrigo Souto <rodrigo@colivre.coop.br> Sat, 19 Jan 2013 19:27:04 +0000
  18 +
  19 +noosfero (0.39.2) unstable; urgency=low
  20 +
  21 + * Bugfixes release
  22 +
  23 + -- Rodrigo Souto <rodrigo@colivre.coop.br> Sat, 12 Jan 2013 10:13:46 +0000
  24 +
  25 +noosfero (0.39.1) unstable; urgency=low
  26 +
  27 + * Bugfixes release
  28 +
  29 + -- Daniela Soares Feitosa <daniela@colivre.coop.br> Thu, 20 Dec 2012 15:47:55 -0200
  30 +
  31 +noosfero (0.39.0) unstable; urgency=low
  32 +
  33 + * Features version release
  34 +
  35 + -- Daniela Soares Feitosa <daniela@colivre.coop.br> Fri, 07 Dec 2012 09:53:42 -0200
  36 +
1 37 noosfero (0.39.0~1) UNRELEASED; urgency=low
2 38  
3 39 * Pre-release to test the antispam mechanism.
4 40  
5 41 -- Antonio Terceiro <terceiro@debian.org> Thu, 30 Aug 2012 14:55:10 -0300
6 42  
  43 +noosfero (0.38.3) unstable; urgency=low
  44 +
  45 + * Bugfixes release
  46 +
  47 + -- Daniela Soares Feitosa <daniela@colivre.coop.br> Wed, 07 Nov 2012 20:25:51 -0200
  48 +
7 49 noosfero (0.38.2) unstable; urgency=low
8 50  
9 51 * Bugfixes release
... ...
debian/noosfero.install
... ... @@ -32,6 +32,8 @@ debian/solr.yml etc/noosfero
32 32 debian/thin.yml etc/noosfero
33 33 etc/logrotate.d/noosfero etc/logrotate.d
34 34 debian/noosfero.yml etc/noosfero
  35 +etc/noosfero/varnish-accept-language.vcl etc/noosfero
  36 +etc/noosfero/varnish-noosfero.vcl etc/noosfero
35 37  
36 38 locale usr/share/noosfero
37 39 doc/noosfero usr/share/noosfero/doc
... ...
etc/noosfero/varnish-noosfero.vcl
1 1 sub vcl_recv {
2 2 if (req.request == "GET" || req.request == "HEAD") {
3 3 if (req.http.Cookie) {
4   - # We only care about the "_noosfero_session.*" cookie, used for
5   - # authentication.
6   - if (req.http.Cookie !~ "_noosfero_session.*" ) {
  4 + # We only care about the "_noosfero_.*" cookies, used by Noosfero
  5 + if (req.http.Cookie !~ "_noosfero_.*" ) {
7 6 # strip all cookies
8 7 unset req.http.Cookie;
9 8 }
... ...
features/signup.feature
... ... @@ -12,7 +12,7 @@ Feature: signup
12 12 | Username | josesilva |
13 13 | Password | secret |
14 14 | Password confirmation | secret |
15   - | Name | José da Silva |
  15 + | Full name | José da Silva |
16 16 And I press "Create my account"
17 17 Then I should not be logged in
18 18 And I should receive an e-mail on josesilva@example.com
... ...
gitignore.example
... ... @@ -19,9 +19,10 @@ public/image_uploads
19 19 public/thumbnails
20 20 public/user_themes
21 21 public/designs/themes/default
  22 +public/designs/themes/*
22 23 public/designs/icons/default
23   -public/javascripts/cache*.js
24   -public/stylesheets/cache*.css
  24 +public/javascripts/cache*
  25 +public/stylesheets/cache*
25 26 public/plugins
26 27 db/development.db
27 28 db/production.db
... ...
lib/noosfero.rb
... ... @@ -2,7 +2,7 @@ require &#39;fast_gettext&#39;
2 2  
3 3 module Noosfero
4 4 PROJECT = 'noosfero'
5   - VERSION = '0.39.0~1'
  5 + VERSION = '0.41.0'
6 6  
7 7 def self.pattern_for_controllers_in_directory(dir)
8 8 disjunction = controllers_in_directory(dir).join('|')
... ...
lib/noosfero/plugin.rb
... ... @@ -130,7 +130,7 @@ class Noosfero::Plugin
130 130 end
131 131  
132 132 # -> Adds plugin-specific content types to CMS
133   - # returns = { content type class }
  133 + # returns = [ContentClass1, ContentClass2, ...]
134 134 def content_types
135 135 nil
136 136 end
... ... @@ -161,7 +161,7 @@ class Noosfero::Plugin
161 161  
162 162 # -> Adds content to products on asset list
163 163 # returns = lambda block that creates html code
164   - def asset_product_extras(product, enterprise)
  164 + def asset_product_extras(product)
165 165 nil
166 166 end
167 167  
... ... @@ -384,7 +384,9 @@ class Noosfero::Plugin
384 384 private
385 385  
386 386 def content_actions
387   - %w[edit delete spread locale suggest home]
  387 + #FIXME 'new' and 'upload' only works for content_remove. It should work for
  388 + #content_expire too.
  389 + %w[edit delete spread locale suggest home new upload]
388 390 end
389 391  
390 392 end
... ...
lib/noosfero/plugin/settings.rb 0 → 100644
... ... @@ -0,0 +1,45 @@
  1 +class Noosfero::Plugin::Settings
  2 +
  3 + def initialize(base, plugin, attributes = nil)
  4 + @base = base
  5 + @plugin = plugin
  6 + attributes ||= {}
  7 + attributes.each do |k,v|
  8 + self.send("#{k}=", v)
  9 + end
  10 + end
  11 +
  12 + def settings
  13 + @base.settings["#{@plugin.public_name}_plugin".to_sym] ||= {}
  14 + end
  15 +
  16 + def method_missing(method, *args, &block)
  17 + if method.to_s =~ /^(.+)=$/
  18 + set_setting($1, args.first)
  19 + elsif method.to_s =~ /^(.+)$/
  20 + get_setting($1)
  21 + end
  22 + end
  23 +
  24 + def get_setting(name)
  25 + if settings[name.to_sym].nil?
  26 + if @plugin.respond_to?("#{name}_default_setting")
  27 + @plugin.send("#{name}_default_setting")
  28 + else
  29 + nil
  30 + end
  31 + else
  32 + settings[name.to_sym]
  33 + end
  34 + end
  35 +
  36 + def set_setting(name, value)
  37 + settings[name.to_sym] = value
  38 + end
  39 +
  40 + def save!
  41 + @base.save!
  42 + end
  43 +
  44 +end
  45 +
... ...
plugins/anti_spam/README 0 → 100644
... ... @@ -0,0 +1,33 @@
  1 +README - AntiSpam (AntiSpam Plugin)
  2 +=======================================
  3 +
  4 +Plugin that checks comments against a spam checking service compatible
  5 +with the Akismet API.
  6 +
  7 +
  8 +Enable Plugin
  9 +-------------
  10 +
  11 +Also, you need to enable AntiSpam Plugin at your Noosfero:
  12 +
  13 +cd <your_noosfero_dir>
  14 +./script/noosfero-plugins enable anti_spam
  15 +
  16 +
  17 +Activate Plugin
  18 +-------------
  19 +
  20 +As a Noosfero administrator user, go to administrator panel:
  21 +
  22 +- Click on "Plugins" option
  23 +- Click on "AntiSpam Plugin" check-box
  24 +
  25 +Configure Plugin
  26 +----------------
  27 +
  28 +As a Noosfero administrator user, go to administrator panel:
  29 +
  30 +- Click on "Configuration" below the "AntiSpam Plugin"
  31 +- Fill in the "API key" field with the key generated after signing up to
  32 + akismet: https://akismet.com/signup/
  33 +- Save your changes
... ...
plugins/anti_spam/controllers/anti_spam_plugin_admin_controller.rb
... ... @@ -2,7 +2,7 @@ class AntiSpamPluginAdminController &lt; AdminController
2 2 append_view_path File.join(File.dirname(__FILE__) + '/../views')
3 3  
4 4 def index
5   - @settings = AntiSpamPlugin::Settings.new(environment, params[:settings])
  5 + @settings = Noosfero::Plugin::Settings.new(environment, AntiSpamPlugin, params[:settings])
6 6 if request.post?
7 7 @settings.save!
8 8 redirect_to :action => 'index'
... ...
plugins/anti_spam/lib/anti_spam_plugin.rb
... ... @@ -8,6 +8,10 @@ class AntiSpamPlugin &lt; Noosfero::Plugin
8 8 _("Checks comments against a spam checking service compatible with the Akismet API")
9 9 end
10 10  
  11 + def self.host_default_setting
  12 + 'api.antispam.typepad.com'
  13 + end
  14 +
11 15 def check_comment_for_spam(comment)
12 16 if rakismet_call(comment, :spam?)
13 17 comment.spam = true
... ... @@ -26,7 +30,7 @@ class AntiSpamPlugin &lt; Noosfero::Plugin
26 30 protected
27 31  
28 32 def rakismet_call(comment, op)
29   - settings = AntiSpamPlugin::Settings.new(comment.environment)
  33 + settings = Noosfero::Plugin::Settings.new(comment.environment, self.class)
30 34  
31 35 Rakismet.host = settings.host
32 36 Rakismet.key = settings.api_key
... ...
plugins/anti_spam/lib/anti_spam_plugin/settings.rb
... ... @@ -1,35 +0,0 @@
1   -class AntiSpamPlugin::Settings
2   -
3   - def initialize(environment, attributes = nil)
4   - @environment = environment
5   - attributes ||= {}
6   - attributes.each do |k,v|
7   - self.send("#{k}=", v)
8   - end
9   - end
10   -
11   - def settings
12   - @environment.settings[:anti_spam_plugin] ||= {}
13   - end
14   -
15   - def host
16   - settings[:host] ||= 'api.antispam.typepad.com'
17   - end
18   -
19   - def host=(value)
20   - settings[:host] = value
21   - end
22   -
23   - def api_key
24   - settings[:api_key]
25   - end
26   -
27   - def api_key=(value)
28   - settings[:api_key] = value
29   - end
30   -
31   - def save!
32   - @environment.save!
33   - end
34   -
35   -end
plugins/anti_spam/lib/anti_spam_plugin/spaminator.rb
... ... @@ -1,115 +0,0 @@
1   -require 'benchmark'
2   -
3   -class AntiSpamPlugin::Spaminator
4   -
5   - class << self
6   - def run(environment)
7   - instance = new(environment)
8   - instance.run
9   - end
10   -
11   - def benchmark(environment)
12   - puts Benchmark.measure { run(environment) }
13   - end
14   - end
15   -
16   -
17   - def initialize(environment)
18   - @environment = environment
19   - end
20   -
21   - def run
22   - start_time = Time.now
23   -
24   - process_all_comments
25   - process_all_people
26   - process_people_without_network
27   -
28   - finish(start_time)
29   - end
30   -
31   - protected
32   -
33   - def finish(start_time)
34   - @environment.settings[:spaminator_last_run] = start_time
35   - @environment.save!
36   - end
37   -
38   - def conditions(table)
39   - last_run = @environment.settings[:spaminator_last_run]
40   - if last_run
41   - ["profiles.environment_id = ? AND #{table}.created_at > ?", @environment.id, last_run]
42   - else
43   - [ "profiles.environment_id = ?", @environment.id]
44   - end
45   - end
46   -
47   - def process_all_comments
48   - puts 'Processing comments ...'
49   - i = 0
50   - comments = Comment.joins("JOIN articles ON (comments.source_id = articles.id AND comments.source_type = 'Article') JOIN profiles ON (profiles.id = articles.profile_id)").where(conditions(:comments))
51   - total = comments.count
52   - comments.find_each do |comment|
53   - puts "Comment #{i += 1}/#{total} (#{100*i/total}%)"
54   - process_comment(comment)
55   - end
56   - end
57   -
58   - def process_all_people
59   - puts 'Processing people ...'
60   - i = 0
61   - people = Person.where(conditions(:profiles))
62   - total = people.count
63   - people.find_each do |person|
64   - puts "Person #{i += 1}/#{total} (#{100*i/total}%)"
65   - process_person(person)
66   - end
67   - end
68   -
69   - def process_comment(comment)
70   - comment.check_for_spam
71   -
72   - # TODO several comments with the same content:
73   - # → disable author
74   - # → mark all of them as spam
75   -
76   - # TODO check comments that contains URL's
77   - end
78   -
79   - def process_person(person)
80   - # person is author of more than 2 comments marked as spam
81   - # → burn
82   - #
83   - number_of_spam_comments = Comment.spam.where(author_id => person.id).count
84   - if number_of_spam_comments > 2
85   - mark_as_spammer(person)
86   - end
87   - end
88   -
89   - def process_people_without_network
90   - # people who signed up more than one month ago, have no friends and <= 1
91   - # communities
92   - #
93   - # → burn
94   - # → mark their comments as spam
95   - #
96   - Person.where(:environment_id => @environment.id).where(['created_at < ?', Time.now - 1.month]).find_each do |person|
97   - # TODO progress indicator - see process_all_people above
98   - number_of_friends = person.friends.count
99   - number_of_communities = person.communities.count
100   - if number_of_friends == 0 && number_of_communities <= 1
101   - mark_as_spammer(person)
102   - Comment.where(:author_id => person.id).find_each do |comment|
103   - comment.spam!
104   - end
105   - end
106   - end
107   - end
108   -
109   - def mark_as_spammer(person)
110   - # FIXME create an AbuseComplaint and finish instead of calling
111   - # Person#disable directly
112   - person.disable
113   - end
114   -
115   -end
plugins/anti_spam/test/unit/anti_spam_plugin/settings_test.rb
... ... @@ -1,29 +0,0 @@
1   -require 'test_helper'
2   -
3   -class AntiSpamSettingsTest < ActiveSupport::TestCase
4   -
5   - def setup
6   - @environment = Environment.new
7   - @settings = AntiSpamPlugin::Settings.new(@environment)
8   - end
9   -
10   - should 'store setttings in environment' do
11   - @settings.host = 'foo.com'
12   - @settings.api_key = '1234567890'
13   - assert_equal 'foo.com', @environment.settings[:anti_spam_plugin][:host]
14   - assert_equal '1234567890', @environment.settings[:anti_spam_plugin][:api_key]
15   - assert_equal 'foo.com', @settings.host
16   - assert_equal '1234567890', @settings.api_key
17   - end
18   -
19   - should 'save environment on save' do
20   - @environment.expects(:save!)
21   - @settings.save!
22   - end
23   -
24   - should 'use TypePad AntiSpam by default' do
25   - assert_equal 'api.antispam.typepad.com', @settings.host
26   - end
27   -
28   -
29   -end
plugins/anti_spam/test/unit/anti_spam_plugin/spaminator_test.rb
... ... @@ -1,53 +0,0 @@
1   -require 'test_helper'
2   -
3   -class AntiSpamPluginSpaminatorTest < ActiveSupport::TestCase
4   -
5   - def setup
6   - @environment = Environment.new
7   - @environment.id = 99
8   - @spaminator = AntiSpamPlugin::Spaminator.new(@environment)
9   - @spaminator.stubs(:puts)
10   - @now = Time.now
11   - Time.stubs(:now).returns(@now)
12   - end
13   -
14   - should 'search everything in the first run' do
15   - assert_equal(['profiles.environment_id = ?',99], @spaminator.send(:conditions, nil))
16   - end
17   -
18   - should 'search using recorded last date' do
19   - @environment.settings[:spaminator_last_run] = @now
20   - assert_equal(['profiles.environment_id = ? AND table.created_at > ?', 99, @now], @spaminator.send(:conditions, 'table'))
21   - end
22   -
23   - should 'record time of last run in environment' do
24   - @spaminator.expects(:process_all_comments)
25   - @spaminator.expects(:process_all_people)
26   - @environment.stubs(:save!)
27   - @spaminator.run
28   - assert_equal @now, @environment.settings[:spaminator_last_run]
29   - end
30   -
31   - should 'find all comments' do
32   - @spaminator.stubs(:process_comment)
33   - @spaminator.send :process_all_comments
34   - end
35   -
36   - should 'find all people' do
37   - @spaminator.stubs(:process_person)
38   - @spaminator.send :process_all_people
39   - end
40   -
41   - should 'find all comments newer than a date' do
42   - @environment.settings[:spaminator_last_run] = Time.now - 1.month
43   - @spaminator.stubs(:process_comment)
44   - @spaminator.send :process_all_comments
45   - end
46   -
47   - should 'find all people newer than a date' do
48   - @environment.settings[:spaminator_last_run] = Time.now - 1.month
49   - @spaminator.stubs(:process_person)
50   - @spaminator.send :process_all_people
51   - end
52   -
53   -end
plugins/anti_spam/test/unit/anti_spam_plugin_test.rb
... ... @@ -7,7 +7,7 @@ class AntiSpamPluginTest &lt; ActiveSupport::TestCase
7 7 article = fast_create(TextileArticle, :profile_id => profile.id)
8 8 @comment = fast_create(Comment, :source_id => article.id, :source_type => 'Article')
9 9  
10   - @settings = AntiSpamPlugin::Settings.new(@comment.environment)
  10 + @settings = Noosfero::Plugin::Settings.new(@comment.environment, AntiSpamPlugin)
11 11 @settings.api_key = 'b8b80ddb8084062d0c9119c945ce3bc3'
12 12 @settings.save!
13 13  
... ...
plugins/bsc/test/functional/bsc_plugin_admin_controller_test.rb
1 1 require File.dirname(__FILE__) + '/../../../../test/test_helper'
2 2 require File.dirname(__FILE__) + '/../../controllers/bsc_plugin_admin_controller'
3   -require File.dirname(__FILE__) + '/../../../../app/models/uploaded_file'
4 3  
5 4 # Re-raise errors caught by the controller.
6 5 class BscPluginAdminController; def rescue_action(e) raise e end; end
... ...
plugins/bsc/test/functional/bsc_plugin_myprofile_controller_test.rb
1 1 require File.dirname(__FILE__) + '/../../../../test/test_helper'
2 2 require File.dirname(__FILE__) + '/../../controllers/bsc_plugin_myprofile_controller'
3   -require File.dirname(__FILE__) + '/../../../../app/models/uploaded_file'
4 3  
5 4 # Re-raise errors caught by the controller.
6 5 class BscPluginMyprofileController; def rescue_action(e) raise e end; end
... ...
plugins/bsc/test/unit/bsc_plugin/associate_enterprise_test.rb
1 1 require File.dirname(__FILE__) + '/../../../../../test/test_helper'
2   -require File.dirname(__FILE__) + '/../../../../../app/models/uploaded_file'
3 2  
4 3 class BscPlugin::AssociateEnterpriseTest < ActiveSupport::TestCase
5 4 VALID_CNPJ = '94.132.024/0001-48'
... ...
plugins/bsc/test/unit/bsc_plugin/contract_test.rb
1 1 require File.dirname(__FILE__) + '/../../../../../test/test_helper'
2   -#require File.dirname(__FILE__) + '/../../../../../app/models/uploaded_file'
3   -#require File.dirname(__FILE__) + '/../../../lib/ext/enterprise'
4 2  
5 3 class BscPlugin::ContractTest < ActiveSupport::TestCase
6 4 def setup
... ...
plugins/bsc/test/unit/ext/product_test.rb
1 1 require File.dirname(__FILE__) + '/../../../../../test/test_helper'
2   -require File.dirname(__FILE__) + '/../../../../../app/models/uploaded_file'
3 2  
4 3 class ProductTest < ActiveSupport::TestCase
5 4 VALID_CNPJ = '94.132.024/0001-48'
... ...
plugins/mezuro/dependencies.rb
  1 +require 'rubygems'
1 2 require 'savon'
2 3 require 'googlecharts'
... ...
plugins/shopping_cart/controllers/shopping_cart_plugin_controller.rb 0 → 100644
... ... @@ -0,0 +1,320 @@
  1 +require 'base64'
  2 +
  3 +class ShoppingCartPluginController < PublicController
  4 +
  5 + include ShoppingCartPlugin::CartHelper
  6 + helper ShoppingCartPlugin::CartHelper
  7 +
  8 + append_view_path File.join(File.dirname(__FILE__) + '/../views')
  9 + before_filter :login_required, :only => []
  10 +
  11 + before_filter :login_required, :only => []
  12 +
  13 + def get
  14 + config =
  15 + if cart.nil?
  16 + { 'enterprise_id' => nil, 'hasProducts' => false }
  17 + else
  18 + { 'enterprise_id' => cart[:enterprise_id], 'hasProducts' => (cart[:items].keys.size > 0) }
  19 + end
  20 + render :text => config.to_json
  21 + end
  22 +
  23 + def add
  24 + product = find_product(params[:id])
  25 + if product && enterprise = validate_same_enterprise(product)
  26 + self.cart = { :enterprise_id => enterprise.id, :items => {} } if self.cart.nil?
  27 + self.cart[:items][product.id] = 0 if self.cart[:items][product.id].nil?
  28 + self.cart[:items][product.id] += 1
  29 + render :text => {
  30 + :ok => true,
  31 + :error => {:code => 0},
  32 + :products => [{
  33 + :id => product.id,
  34 + :name => product.name,
  35 + :price => get_price(product, enterprise.environment),
  36 + :description => product.description,
  37 + :picture => product.default_image(:minor),
  38 + :quantity => self.cart[:items][product.id]
  39 + }]
  40 + }.to_json
  41 + end
  42 + end
  43 +
  44 + def remove
  45 + id = params[:id].to_i
  46 + if validate_cart_presence && validate_cart_has_product(id)
  47 + self.cart[:items].delete(id)
  48 + self.cart = nil if self.cart[:items].empty?
  49 + render :text => {
  50 + :ok => true,
  51 + :error => {:code => 0},
  52 + :product_id => id
  53 + }.to_json
  54 + end
  55 + end
  56 +
  57 + def list
  58 + if validate_cart_presence
  59 + products = self.cart[:items].collect do |id, quantity|
  60 + product = Product.find(id)
  61 + { :id => product.id,
  62 + :name => product.name,
  63 + :price => get_price(product, product.enterprise.environment),
  64 + :description => product.description,
  65 + :picture => product.default_image(:minor),
  66 + :quantity => quantity
  67 + }
  68 + end
  69 + render :text => {
  70 + :ok => true,
  71 + :error => {:code => 0},
  72 + :products => products
  73 + }.to_json
  74 + end
  75 + end
  76 +
  77 + def update_quantity
  78 + quantity = params[:quantity].to_i
  79 + id = params[:id].to_i
  80 + if validate_cart_presence && validate_cart_has_product(id) && validate_item_quantity(quantity)
  81 + product = Product.find(id)
  82 + self.cart[:items][product.id] = quantity
  83 + render :text => {
  84 + :ok => true,
  85 + :error => {:code => 0},
  86 + :product_id => id,
  87 + :quantity => quantity
  88 + }.to_json
  89 + end
  90 + end
  91 +
  92 + def clean
  93 + self.cart = nil
  94 + render :text => {
  95 + :ok => true,
  96 + :error => {:code => 0}
  97 + }.to_json
  98 + end
  99 +
  100 + def buy
  101 + @cart = cart
  102 + @enterprise = environment.enterprises.find(cart[:enterprise_id])
  103 + @settings = Noosfero::Plugin::Settings.new(@enterprise, ShoppingCartPlugin)
  104 + render :layout => false
  105 + end
  106 +
  107 + def send_request
  108 + register_order(params[:customer], self.cart[:items])
  109 + begin
  110 + enterprise = environment.enterprises.find(cart[:enterprise_id])
  111 + ShoppingCartPlugin::Mailer.deliver_customer_notification(params[:customer], enterprise, self.cart[:items], params[:delivery_option])
  112 + ShoppingCartPlugin::Mailer.deliver_supplier_notification(params[:customer], enterprise, self.cart[:items], params[:delivery_option])
  113 + self.cart = nil
  114 + render :text => {
  115 + :ok => true,
  116 + :message => _('Request sent successfully. Check your email.'),
  117 + :error => {:code => 0}
  118 + }.to_json
  119 + rescue ActiveRecord::ActiveRecordError
  120 + render :text => {
  121 + :ok => false,
  122 + :error => {
  123 + :code => 6,
  124 + :message => exception.message
  125 + }
  126 + }.to_json
  127 + end
  128 + end
  129 +
  130 + def visibility
  131 + render :text => self.cart.has_key?(:visibility) ? self.cart[:visibility].to_json : true.to_json
  132 + end
  133 +
  134 + def show
  135 + begin
  136 + self.cart[:visibility] = true
  137 + render :text => {
  138 + :ok => true,
  139 + :message => _('Basket displayed.'),
  140 + :error => {:code => 0}
  141 + }.to_json
  142 + rescue Exception => exception
  143 + render :text => {
  144 + :ok => false,
  145 + :error => {
  146 + :code => 7,
  147 + :message => exception.message
  148 + }
  149 + }.to_json
  150 + end
  151 + end
  152 +
  153 + def hide
  154 + begin
  155 + self.cart[:visibility] = false
  156 + render :text => {
  157 + :ok => true,
  158 + :message => _('Basket hidden.'),
  159 + :error => {:code => 0}
  160 + }.to_json
  161 + rescue Exception => exception
  162 + render :text => {
  163 + :ok => false,
  164 + :error => {
  165 + :code => 8,
  166 + :message => exception.message
  167 + }
  168 + }.to_json
  169 + end
  170 + end
  171 +
  172 + def update_delivery_option
  173 + enterprise = environment.enterprises.find(cart[:enterprise_id])
  174 + settings = Noosfero::Plugin::Settings.new(enterprise, ShoppingCartPlugin)
  175 + delivery_price = settings.delivery_options[params[:delivery_option]]
  176 + delivery = Product.new(:name => params[:delivery_option], :price => delivery_price)
  177 + delivery.save(false)
  178 + items = self.cart[:items].clone
  179 + items[delivery.id] = 1
  180 + total_price = get_total_on_currency(items, environment)
  181 + delivery.destroy
  182 + render :text => {
  183 + :ok => true,
  184 + :delivery_price => float_to_currency_cart(delivery_price, environment),
  185 + :total_price => total_price,
  186 + :message => _('Delivery option updated.'),
  187 + :error => {:code => 0}
  188 + }.to_json
  189 + end
  190 +
  191 + private
  192 +
  193 + def validate_same_enterprise(product)
  194 + if self.cart && self.cart[:enterprise_id] && product.enterprise_id != self.cart[:enterprise_id]
  195 + render :text => {
  196 + :ok => false,
  197 + :error => {
  198 + :code => 1,
  199 + :message => _("Can't join items from different enterprises.")
  200 + }
  201 + }.to_json
  202 + return nil
  203 + end
  204 + product.enterprise
  205 + end
  206 +
  207 + def validate_cart_presence
  208 + if self.cart.nil?
  209 + render :text => {
  210 + :ok => false,
  211 + :error => {
  212 + :code => 2,
  213 + :message => _("There is no basket.")
  214 + }
  215 + }.to_json
  216 + return false
  217 + end
  218 + true
  219 + end
  220 +
  221 + def find_product(id)
  222 + begin
  223 + product = Product.find(id)
  224 + rescue ActiveRecord::RecordNotFound
  225 + render :text => {
  226 + :ok => false,
  227 + :error => {
  228 + :code => 3,
  229 + :message => _("This enterprise doesn't have this product.")
  230 + }
  231 + }.to_json
  232 + return nil
  233 + end
  234 + product
  235 + end
  236 +
  237 + def validate_cart_has_product(id)
  238 + if !self.cart[:items].has_key?(id)
  239 + render :text => {
  240 + :ok => false,
  241 + :error => {
  242 + :code => 4,
  243 + :message => _("The basket doesn't have this product.")
  244 + }
  245 + }.to_json
  246 + return false
  247 + end
  248 + true
  249 + end
  250 +
  251 + def validate_item_quantity(quantity)
  252 + if quantity.to_i < 1
  253 + render :text => {
  254 + :ok => false,
  255 + :error => {
  256 + :code => 5,
  257 + :message => _("Invalid quantity.")
  258 + }
  259 + }.to_json
  260 + return false
  261 + end
  262 + true
  263 + end
  264 +
  265 + def register_order(custumer, items)
  266 + new_items = {}
  267 + items.each do |id, quantity|
  268 + product = Product.find(id)
  269 + price = product.price || 0
  270 + new_items[id] = {:quantity => quantity, :price => price, :name => product.name}
  271 + end
  272 + ShoppingCartPlugin::PurchaseOrder.create!(
  273 + :seller => Enterprise.find(cart[:enterprise_id]),
  274 + :customer => user,
  275 + :status => ShoppingCartPlugin::PurchaseOrder::Status::OPENED,
  276 + :products_list => new_items,
  277 + :customer_delivery_option => params[:delivery_option],
  278 + :customer_payment => params[:customer][:payment],
  279 + :customer_change => params[:customer][:change],
  280 + :customer_name => params[:customer][:name],
  281 + :customer_email => params[:customer][:email],
  282 + :customer_contact_phone => params[:customer][:contact_phone],
  283 + :customer_address => params[:customer][:address],
  284 + :customer_district => params[:customer][:district],
  285 + :customer_city => params[:customer][:city],
  286 + :customer_zip_code => params[:customer][:zip_code]
  287 + )
  288 + end
  289 +
  290 + protected
  291 +
  292 + def cart
  293 + @cart ||=
  294 + begin
  295 + cookies[cookie_key] && YAML.load(Base64.decode64(cookies[cookie_key])) || nil
  296 + end
  297 + @cart
  298 + end
  299 +
  300 + def cart=(data)
  301 + @cart = data
  302 + end
  303 +
  304 + after_filter :save_cookie
  305 + def save_cookie
  306 + if @cart.nil?
  307 + cookies.delete(cookie_key, :path => '/plugin/shopping_cart')
  308 + else
  309 + cookies[cookie_key] = {
  310 + :value => Base64.encode64(@cart.to_yaml),
  311 + :path => "/plugin/shopping_cart"
  312 + }
  313 + end
  314 + end
  315 +
  316 + def cookie_key
  317 + :_noosfero_plugin_shopping_cart
  318 + end
  319 +
  320 +end
... ...
plugins/shopping_cart/controllers/shopping_cart_plugin_myprofile_controller.rb
... ... @@ -4,9 +4,12 @@ class ShoppingCartPluginMyprofileController &lt; MyProfileController
4 4 append_view_path File.join(File.dirname(__FILE__) + '/../views')
5 5  
6 6 def edit
  7 + params[:settings] = treat_cart_options(params[:settings])
  8 +
  9 + @settings = Noosfero::Plugin::Settings.new(profile, ShoppingCartPlugin, params[:settings])
7 10 if request.post?
8 11 begin
9   - profile.update_attributes!(params[:profile_attr])
  12 + @settings.save!
10 13 session[:notice] = _('Option updated successfully.')
11 14 rescue Exception => exception
12 15 session[:notice] = _('Option wasn\'t updated successfully.')
... ... @@ -46,4 +49,25 @@ class ShoppingCartPluginMyprofileController &lt; MyProfileController
46 49 order.save!
47 50 redirect_to :action => 'reports', :from => params[:context_from], :to => params[:context_to], :filter_status => params[:context_status]
48 51 end
  52 +
  53 + private
  54 +
  55 + def treat_cart_options(settings)
  56 + return if settings.blank?
  57 + settings[:enabled] = settings[:enabled] == '1'
  58 + settings[:delivery] = settings[:delivery] == '1'
  59 + settings[:free_delivery_price] = settings[:free_delivery_price].blank? ? nil : settings[:free_delivery_price].to_d
  60 + settings[:delivery_options] = treat_delivery_options(settings[:delivery_options])
  61 + settings
  62 + end
  63 +
  64 + def treat_delivery_options(params)
  65 + result = {}
  66 + params[:options].size.times do |counter|
  67 + if params[:options][counter].present? && params[:prices][counter].present?
  68 + result[params[:options][counter]] = params[:prices][counter]
  69 + end
  70 + end
  71 + result
  72 + end
49 73 end
... ...
plugins/shopping_cart/controllers/shopping_cart_plugin_profile_controller.rb
... ... @@ -1,248 +0,0 @@
1   -include ShoppingCartPlugin::CartHelper
2   -
3   -class ShoppingCartPluginProfileController < ProfileController
4   - append_view_path File.join(File.dirname(__FILE__) + '/../views')
5   - before_filter :login_required, :only => []
6   -
7   - before_filter :login_required, :only => []
8   -
9   - def add
10   - session[:cart] = { :enterprise_id => profile.id, :items => {} } if session[:cart].nil?
11   - if validate_same_enterprise && product = validate_enterprise_has_product(params[:id])
12   - session[:cart][:items][product.id] = 0 if session[:cart][:items][product.id].nil?
13   - session[:cart][:items][product.id] += 1
14   - render :text => {
15   - :ok => true,
16   - :error => {:code => 0},
17   - :products => [{
18   - :id => product.id,
19   - :name => product.name,
20   - :price => get_price(product, profile.environment),
21   - :description => product.description,
22   - :picture => product.default_image(:minor),
23   - :quantity => session[:cart][:items][product.id]
24   - }]
25   - }.to_json
26   - end
27   - end
28   -
29   - def remove
30   - id = params[:id].to_i
31   - if validate_cart_presence && validate_cart_has_product(id)
32   - session[:cart][:items].delete(id)
33   - session[:cart] = nil if session[:cart][:items].empty?
34   - render :text => {
35   - :ok => true,
36   - :error => {:code => 0},
37   - :product_id => id
38   - }.to_json
39   - end
40   - end
41   -
42   - def list
43   - if validate_cart_presence
44   - products = session[:cart][:items].collect do |id, quantity|
45   - product = Product.find(id)
46   - { :id => product.id,
47   - :name => product.name,
48   - :price => get_price(product, profile.environment),
49   - :description => product.description,
50   - :picture => product.default_image(:minor),
51   - :quantity => quantity
52   - }
53   - end
54   - render :text => {
55   - :ok => true,
56   - :error => {:code => 0},
57   - :enterprise => Enterprise.find(session[:cart][:enterprise_id]).identifier,
58   - :products => products
59   - }.to_json
60   - end
61   - end
62   -
63   - def update_quantity
64   - quantity = params[:quantity].to_i
65   - id = params[:id].to_i
66   - if validate_cart_presence && validate_cart_has_product(id) && validate_item_quantity(quantity)
67   - product = Product.find(id)
68   - session[:cart][:items][product.id] = quantity
69   - render :text => {
70   - :ok => true,
71   - :error => {:code => 0},
72   - :product_id => id,
73   - :quantity => quantity
74   - }.to_json
75   - end
76   - end
77   -
78   - def clean
79   - session[:cart] = nil
80   - render :text => {
81   - :ok => true,
82   - :error => {:code => 0}
83   - }.to_json
84   - end
85   -
86   - def buy
87   - @environment = profile.environment
88   - render :layout => false
89   - end
90   -
91   - def send_request
92   - register_order(params[:customer], session[:cart][:items])
93   - begin
94   - ShoppingCartPlugin::Mailer.deliver_customer_notification(params[:customer], profile, session[:cart][:items])
95   - ShoppingCartPlugin::Mailer.deliver_supplier_notification(params[:customer], profile, session[:cart][:items])
96   - render :text => {
97   - :ok => true,
98   - :message => _('Request sent successfully. Check your email.'),
99   - :error => {:code => 0}
100   - }.to_json
101   - rescue Exception => exception
102   - render :text => {
103   - :ok => false,
104   - :error => {
105   - :code => 6,
106   - :message => exception.message
107   - }
108   - }.to_json
109   - end
110   - end
111   -
112   - def visibility
113   - render :text => session[:cart].has_key?(:visibility) ? session[:cart][:visibility].to_json : true.to_json
114   - end
115   -
116   - def show
117   - begin
118   - session[:cart][:visibility] = true
119   - render :text => {
120   - :ok => true,
121   - :message => _('Basket displayed.'),
122   - :error => {:code => 0}
123   - }.to_json
124   - rescue Exception => exception
125   - render :text => {
126   - :ok => false,
127   - :error => {
128   - :code => 7,
129   - :message => exception.message
130   - }
131   - }.to_json
132   - end
133   - end
134   -
135   - def hide
136   - begin
137   - session[:cart][:visibility] = false
138   - render :text => {
139   - :ok => true,
140   - :message => _('Basket hidden.'),
141   - :error => {:code => 0}
142   - }.to_json
143   - rescue Exception => exception
144   - render :text => {
145   - :ok => false,
146   - :error => {
147   - :code => 8,
148   - :message => exception.message
149   - }
150   - }.to_json
151   - end
152   - end
153   -
154   - private
155   -
156   - def validate_same_enterprise
157   - if profile.id != session[:cart][:enterprise_id]
158   - render :text => {
159   - :ok => false,
160   - :error => {
161   - :code => 1,
162   - :message => _("Can't join items from different enterprises.")
163   - }
164   - }.to_json
165   - return false
166   - end
167   - true
168   - end
169   -
170   - def validate_cart_presence
171   - if session[:cart].nil?
172   - render :text => {
173   - :ok => false,
174   - :error => {
175   - :code => 2,
176   - :message => _("There is no basket.")
177   - }
178   - }.to_json
179   - return false
180   - end
181   - true
182   - end
183   -
184   - def validate_enterprise_has_product(id)
185   - begin
186   - product = profile.products.find(id)
187   - rescue
188   - render :text => {
189   - :ok => false,
190   - :error => {
191   - :code => 3,
192   - :message => _("This enterprise doesn't have this product.")
193   - }
194   - }.to_json
195   - return nil
196   - end
197   - product
198   - end
199   -
200   - def validate_cart_has_product(id)
201   - if !session[:cart][:items].has_key?(id)
202   - render :text => {
203   - :ok => false,
204   - :error => {
205   - :code => 4,
206   - :message => _("The basket doesn't have this product.")
207   - }
208   - }.to_json
209   - return false
210   - end
211   - true
212   - end
213   -
214   - def validate_item_quantity(quantity)
215   - if quantity.to_i < 1
216   - render :text => {
217   - :ok => false,
218   - :error => {
219   - :code => 5,
220   - :message => _("Invalid quantity.")
221   - }
222   - }.to_json
223   - return false
224   - end
225   - true
226   - end
227   -
228   - def register_order(custumer, items)
229   - new_items = {}
230   - items.each do |id, quantity|
231   - product = Product.find(id)
232   - price = product.price || 0
233   - new_items[id] = {:quantity => quantity, :price => price, :name => product.name}
234   - end
235   - ShoppingCartPlugin::PurchaseOrder.create!(
236   - :seller => profile,
237   - :customer => user,
238   - :status => ShoppingCartPlugin::PurchaseOrder::Status::OPENED,
239   - :products_list => new_items,
240   - :customer_name => params[:customer][:name],
241   - :customer_email => params[:customer][:email],
242   - :customer_contact_phone => params[:customer][:contact_phone],
243   - :customer_address => params[:customer][:address],
244   - :customer_city => params[:customer][:city],
245   - :customer_zip_code => params[:customer][:zip_code]
246   - )
247   - end
248   -end
plugins/shopping_cart/db/migrate/20121022190819_move_fields_included_on_profiles_table_to_settings.rb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +class MoveFieldsIncludedOnProfilesTableToSettings < ActiveRecord::Migration
  2 + def self.up
  3 + Profile.find_each do |profile|
  4 + settings = Noosfero::Plugin::Settings.new(profile, ShoppingCartPlugin)
  5 + settings.enabled = profile.shopping_cart
  6 + settings.delivery = profile.shopping_cart_delivery
  7 + settings.delivery_price = profile.shopping_cart_delivery_price
  8 + settings.save!
  9 + end
  10 +
  11 + remove_column :profiles, :shopping_cart
  12 + remove_column :profiles, :shopping_cart_delivery
  13 + remove_column :profiles, :shopping_cart_delivery_price
  14 + end
  15 +
  16 + def self.down
  17 + say "This migration can not be reverted!"
  18 + end
  19 +end
... ...
plugins/shopping_cart/lib/shopping_cart_plugin.rb
... ... @@ -3,20 +3,40 @@ require_dependency &#39;shopping_cart_plugin/ext/person&#39;
3 3  
4 4 class ShoppingCartPlugin < Noosfero::Plugin
5 5  
6   - def self.plugin_name
  6 + class << self
  7 + def plugin_name
7 8 "Shopping Basket"
8   - end
  9 + end
  10 +
  11 + def plugin_description
  12 + _("A shopping basket feature for enterprises")
  13 + end
  14 +
  15 + def enabled_default_setting
  16 + true
  17 + end
9 18  
10   - def self.plugin_description
11   - _("A shopping basket feature for enterprises")
  19 + def delivery_default_setting
  20 + false
  21 + end
  22 +
  23 + def delivery_price_default_setting
  24 + 0
  25 + end
  26 +
  27 + def delivery_options_default_setting
  28 + {}
  29 + end
12 30 end
13 31  
14   - def add_to_cart_button(item, enterprise = context.profile)
15   - if enterprise.shopping_cart && item.available
  32 + def add_to_cart_button(item)
  33 + enterprise = item.enterprise
  34 + settings = Noosfero::Plugin::Settings.new(enterprise, ShoppingCartPlugin)
  35 + if settings.enabled && item.available
16 36 lambda {
17 37 link_to(_('Add to basket'), "add:#{item.name}",
18 38 :class => 'cart-add-item',
19   - :onclick => "Cart.addItem('#{enterprise.identifier}', #{item.id}, this); return false"
  39 + :onclick => "Cart.addItem(#{item.id}, this); return false"
20 40 )
21 41 }
22 42 end
... ... @@ -35,15 +55,16 @@ class ShoppingCartPlugin &lt; Noosfero::Plugin
35 55 end
36 56  
37 57 def body_beginning
38   - expanded_template('cart.html.erb',{:cart => context.session[:cart]})
  58 + expanded_template('cart.html.erb')
39 59 end
40 60  
41 61 def control_panel_buttons
  62 + settings = Noosfero::Plugin::Settings.new(context.profile, ShoppingCartPlugin)
42 63 buttons = []
43 64 if context.profile.enterprise?
44 65 buttons << { :title => _('Shopping basket'), :icon => 'shopping-cart-icon', :url => {:controller => 'shopping_cart_plugin_myprofile', :action => 'edit'} }
45 66 end
46   - if context.profile.enterprise? && context.profile.shopping_cart
  67 + if context.profile.enterprise? && settings.enabled
47 68 buttons << { :title => _('Purchase reports'), :icon => 'shopping-cart-purchase-report', :url => {:controller => 'shopping_cart_plugin_myprofile', :action => 'reports'} }
48 69 end
49 70  
... ...
plugins/shopping_cart/lib/shopping_cart_plugin/cart_helper.rb
1 1 module ShoppingCartPlugin::CartHelper
2 2  
3 3 include ActionView::Helpers::NumberHelper
  4 + include ActionView::Helpers::TagHelper
4 5  
5 6 def sell_price(product)
6 7 return 0 if product.price.nil?
7 8 product.discount ? product.price_with_discount : product.price
8 9 end
9 10  
10   - def get_price(product, environment)
11   - float_to_currency_cart(sell_price(product), environment)
  11 + def get_price(product, environment, quantity=1)
  12 + float_to_currency_cart(price_with_quantity(product,quantity), environment)
12 13 end
13 14  
14   - def get_total(items, environment)
15   - float_to_currency_cart(items.map { |id, quantity| sell_price(Product.find(id)) * quantity}.sum, environment)
  15 + def price_with_quantity(product, quantity=1)
  16 + quantity = 1 if !quantity.kind_of?(Numeric)
  17 + sell_price(product)*quantity
16 18 end
17 19  
18   - def items_table(items, profile, by_mail = false)
  20 + def get_total(items)
  21 + items.map { |id, quantity| price_with_quantity(Product.find(id),quantity)}.sum
  22 + end
  23 +
  24 + def get_total_on_currency(items, environment)
  25 + float_to_currency_cart(get_total(items), environment)
  26 + end
  27 +
  28 + def items_table(items, profile, delivery_option = nil, by_mail = false)
19 29 environment = profile.environment
  30 + settings = Noosfero::Plugin::Settings.new(profile, ShoppingCartPlugin)
20 31 items = items.to_a
21   - if profile.shopping_cart_delivery
22   - delivery = Product.create!(:name => _('Delivery'), :price => profile.shopping_cart_delivery_price, :product_category => ProductCategory.last)
23   - items << [delivery.id, 1]
24   - end
25 32  
26 33 quantity_opts = { :class => 'cart-table-quantity' }
27 34 quantity_opts.merge!({:align => 'center'}) if by_mail
28 35 price_opts = {:class => 'cart-table-price'}
29 36 price_opts.merge!({:align => 'right'}) if by_mail
  37 + items.sort! {|a, b| Product.find(a.first).name <=> Product.find(b.first).name}
  38 +
  39 + if settings.delivery
  40 + if settings.free_delivery_price && get_total(items) >= settings.free_delivery_price
  41 + delivery = Product.new(:name => _('Free delivery'), :price => 0)
  42 + else
  43 + delivery = Product.new(:name => delivery_option || _('Delivery'), :price => settings.delivery_options[delivery_option])
  44 + end
  45 + delivery.save(false)
  46 + items << [delivery.id, '']
  47 + end
30 48  
31 49 table = '<table id="cart-items-table" cellpadding="2" cellspacing="0"
32 50 border="'+(by_mail ? '1' : '0')+'"
33 51 style="'+(by_mail ? 'border-collapse:collapse' : '')+'">' +
34 52 content_tag('tr',
35   - content_tag('th', _('Item name')) +
36   - content_tag('th', by_mail ? '&nbsp;#&nbsp;' : '#') +
37   - content_tag('th', _('Price'))
  53 + content_tag('th', _('Item name')) +
  54 + content_tag('th', by_mail ? '&nbsp;#&nbsp;' : '#') +
  55 + content_tag('th', _('Price'))
38 56 ) +
39 57 items.map do |id, quantity|
40 58 product = Product.find(id)
  59 + name_opts = {}
  60 + is_delivery = quantity.kind_of?(String)
  61 + if is_delivery
  62 + price_opts.merge!({:id => 'delivery-price'})
  63 + name_opts.merge!({:id => 'delivery-name'})
  64 + end
41 65 content_tag('tr',
42   - content_tag('td', product.name) +
43   - content_tag('td', quantity, quantity_opts ) +
44   - content_tag('td', get_price(product, environment), price_opts )
45   - )
  66 + content_tag('td', product.name, name_opts) +
  67 + content_tag('td', quantity, quantity_opts ) +
  68 + content_tag('td', get_price(product, environment, quantity), price_opts)
  69 + )
46 70 end.join("\n")
47 71  
48   - total = get_total(items, environment)
49   - delivery.destroy if profile.shopping_cart_delivery
  72 + total = get_total_on_currency(items, environment)
  73 + delivery.destroy if settings.delivery
50 74  
51 75 table +
52 76 content_tag('th', _('Total:'), :colspan => 2, :class => 'cart-table-total-label') +
... ... @@ -55,6 +79,14 @@ module ShoppingCartPlugin::CartHelper
55 79 end
56 80  
57 81 def float_to_currency_cart(value, environment)
58   - number_to_currency(value, :unit => environment.currency_unit, :separator => environment.currency_separator, :delimiter => environment.currency_delimiter, :precision => 2, :format => "%u %n")
  82 + number_to_currency(value, :unit => environment.currency_unit, :separator => environment.currency_separator, :delimiter => environment.currency_delimiter, :precision => 2, :format => "%u%n")
  83 + end
  84 +
  85 + def select_delivery_options(options, environment)
  86 + result = options.map do |option, price|
  87 + ["#{option} (#{float_to_currency_cart(price, environment)})", option]
  88 + end
  89 + result << ["#{_('Delivery')} (#{float_to_currency_cart(0, environment)})", 'delivery'] if result.empty?
  90 + result
59 91 end
60 92 end
... ...
plugins/shopping_cart/lib/shopping_cart_plugin/mailer.rb
1 1 class ShoppingCartPlugin::Mailer < Noosfero::Plugin::MailerBase
2 2  
3   - def customer_notification(customer, supplier, items)
  3 + include ShoppingCartPlugin::CartHelper
  4 +
  5 + def customer_notification(customer, supplier, items, delivery_option)
4 6 domain = supplier.hostname || supplier.environment.default_hostname
5 7 recipients customer[:email]
6 8 from 'no-reply@' + domain
... ... @@ -10,10 +12,12 @@ class ShoppingCartPlugin::Mailer &lt; Noosfero::Plugin::MailerBase
10 12 body :customer => customer,
11 13 :supplier => supplier,
12 14 :items => items,
13   - :environment => supplier.environment
  15 + :environment => supplier.environment,
  16 + :helper => self,
  17 + :delivery_option => delivery_option
14 18 end
15 19  
16   - def supplier_notification(customer, supplier, items)
  20 + def supplier_notification(customer, supplier, items, delivery_option)
17 21 domain = supplier.environment.default_hostname
18 22 recipients supplier.contact_email
19 23 from 'no-reply@' + domain
... ... @@ -23,6 +27,8 @@ class ShoppingCartPlugin::Mailer &lt; Noosfero::Plugin::MailerBase
23 27 body :customer => customer,
24 28 :supplier => supplier,
25 29 :items => items,
26   - :environment => supplier.environment
  30 + :environment => supplier.environment,
  31 + :helper => self,
  32 + :delivery_option => delivery_option
27 33 end
28 34 end
... ...
plugins/shopping_cart/lib/shopping_cart_plugin/purchase_order.rb
... ... @@ -12,8 +12,12 @@ class ShoppingCartPlugin::PurchaseOrder &lt; Noosfero::Plugin::ActiveRecord
12 12 settings_items :customer_email, :type => String
13 13 settings_items :customer_contact_phone, :type => String
14 14 settings_items :customer_address, :type => String
  15 + settings_items :customer_district, :type => String
15 16 settings_items :customer_city, :type => String
16 17 settings_items :customer_zip_code, :type => String
  18 + settings_items :customer_delivery_option, :type => String
  19 + settings_items :customer_payment, :type => String
  20 + settings_items :customer_change, :type => String
17 21  
18 22 before_create do |order|
19 23 order.created_at = Time.now.utc
... ...
plugins/shopping_cart/public/buy.js 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +jQuery(document).ready(function(){
  2 + jQuery("#cart-request-form").validate({
  3 + submitHandler: function(form) {
  4 + jQuery(form).find('input.submit').attr('disabled', true);
  5 + jQuery('#cboxLoadingOverlay').show().addClass('loading');
  6 + jQuery('#cboxLoadingGraphic').show().addClass('loading');
  7 + }
  8 + });
  9 +});
  10 +
  11 +jQuery('#delivery_option').change(function(){
  12 + jQuery('#cboxLoadingGraphic').show();
  13 + me = this;
  14 + enterprise = jQuery(me).attr('data-profile-identifier');
  15 + option = jQuery(me).val();
  16 + jQuery.ajax({
  17 + url: '/plugin/shopping_cart/update_delivery_option',
  18 + dataType: "json",
  19 + data: 'delivery_option='+option,
  20 + success: function(data, st, ajax) {
  21 + jQuery('#delivery-price').text(data.delivery_price);
  22 + jQuery('.cart-table-total-value').text(data.total_price);
  23 + jQuery('#delivery-name').text(option);
  24 + jQuery('#cboxLoadingGraphic').hide();
  25 + },
  26 + error: function(ajax, st, errorThrown) {
  27 + alert('Update delivery option - HTTP '+st+': '+errorThrown);
  28 + },
  29 + });
  30 +});
  31 +
  32 +jQuery('#customer_payment').change(function(){
  33 + jQuery(this).closest('.formfieldline').next().slideToggle('fast');
  34 +});
... ...
plugins/shopping_cart/public/cart.js
... ... @@ -11,10 +11,9 @@ function Cart(config) {
11 11 $(".cart-buy", this.cartElem).button({ icons: { primary: 'ui-icon-cart'} });
12 12 if (!this.empty) {
13 13 $(this.cartElem).show();
14   - this.enterprise = config.enterprise;
15 14 me = this;
16 15 $.ajax({
17   - url: '/profile/'+ this.enterprise +'/plugins/shopping_cart/visibility',
  16 + url: '/plugin/shopping_cart/visibility',
18 17 dataType: 'json',
19 18 success: function(data, status, ajax){
20 19 me.visible = /^true$/i.test(data);
... ... @@ -25,7 +24,7 @@ function Cart(config) {
25 24 alert('Visibility - HTTP '+status+': '+errorThrown);
26 25 }
27 26 });
28   - $(".cart-buy", this.cartElem).colorbox({href: '/profile/' + this.enterprise + '/plugins/shopping_cart/buy'});
  27 + $(".cart-buy", this.cartElem).colorbox({ href: '/plugin/shopping_cart/buy' });
29 28 }
30 29 }
31 30  
... ... @@ -34,7 +33,7 @@ function Cart(config) {
34 33 Cart.prototype.listProducts = function() {
35 34 var me = this;
36 35 $.ajax({
37   - url: '/profile/'+ this.enterprise +'/plugins/shopping_cart/list',
  36 + url: '/plugin/shopping_cart/list',
38 37 dataType: 'json',
39 38 success: function(data, ststus, ajax){
40 39 if ( !data.ok ) alert(data.error.message);
... ... @@ -61,7 +60,7 @@ function Cart(config) {
61 60 '<span class="item-name">'+ item.name +'</span>' +
62 61 '<div class="item-price">' +
63 62 '<input size="1" value="'+item.quantity+'" />'+ (item.price ? '&times; '+ item.price : '') +'</div>' +
64   - ' <a href="remove:'+item.name+'" onclick="Cart.removeItem(\''+this.enterprise+'\', '+item.id+'); return false"' +
  63 + ' <a href="remove:'+item.name+'" onclick="Cart.removeItem('+item.id+'); return false"' +
65 64 ' class="button icon-remove"><span>remove</span></a>'
66 65 ).appendTo(li);
67 66 var input = $("input", li)[0];
... ... @@ -100,7 +99,7 @@ function Cart(config) {
100 99 var me = this;
101 100 if( quantity == NaN ) return input.value = input.lastValue;
102 101 $.ajax({
103   - url: '/profile/'+ this.enterprise +'/plugins/shopping_cart/update_quantity/'+ itemId +'?quantity='+ quantity,
  102 + url: '/plugin/shopping_cart/update_quantity/'+ itemId +'?quantity='+ quantity,
104 103 dataType: 'json',
105 104 success: function(data, status, ajax){
106 105 if ( !data.ok ) {
... ... @@ -132,8 +131,7 @@ function Cart(config) {
132 131 this.updateTotal();
133 132 }
134 133  
135   - Cart.addItem = function(enterprise, itemId, link) {
136   - // on the future, the instance may be found by the enterprise identifier.
  134 + Cart.addItem = function(itemId, link) {
137 135 link.intervalId = setInterval(function() {
138 136 steps = ['w', 'n', 'e', 's'];
139 137 if( !link.step || link.step==3 ) link.step = 0;
... ... @@ -144,18 +142,13 @@ function Cart(config) {
144 142 clearInterval(link.intervalId);
145 143 $(link).button({ icons: { primary: 'ui-icon-cart'}, disable: false });
146 144 };
147   - this.instance.addItem(enterprise, itemId, stopBtLoading);
  145 + this.instance.addItem(itemId, stopBtLoading);
148 146 }
149 147  
150   - Cart.prototype.addItem = function(enterprise, itemId, callback) {
151   - if(!this.enterprise) {
152   - this.enterprise = enterprise;
153   - $(".cart-buy", this.cartElem).colorbox({href: '/profile/' + this.enterprise + '/plugins/shopping_cart/buy'});
154   -// $(this.cartElem).show();
155   - }
  148 + Cart.prototype.addItem = function(itemId, callback) {
156 149 var me = this;
157 150 $.ajax({
158   - url: '/profile/'+ enterprise +'/plugins/shopping_cart/add/'+ itemId,
  151 + url: '/plugin/shopping_cart/add/'+ itemId,
159 152 dataType: 'json',
160 153 success: function(data, status, ajax){
161 154 if ( !data.ok ) alert(data.error.message);
... ... @@ -169,16 +162,16 @@ function Cart(config) {
169 162 });
170 163 }
171 164  
172   - Cart.removeItem = function(enterprise, itemId) {
  165 + Cart.removeItem = function(itemId) {
173 166 var message = this.instance.cartElem.getAttribute('data-l10nRemoveItem');
174   - if( confirm(message) ) this.instance.removeItem(enterprise, itemId);
  167 + if( confirm(message) ) this.instance.removeItem(itemId);
175 168 }
176 169  
177   - Cart.prototype.removeItem = function(enterprise, itemId) {
  170 + Cart.prototype.removeItem = function(itemId) {
178 171 if ($("li", this.itemsBox).size() < 2) return this.clean();
179 172 var me = this;
180 173 $.ajax({
181   - url: '/profile/'+ enterprise +'/plugins/shopping_cart/remove/'+ itemId,
  174 + url: '/plugin/shopping_cart/remove/'+ itemId,
182 175 dataType: 'json',
183 176 success: function(data, status, ajax){
184 177 if ( !data.ok ) alert(data.error.message);
... ... @@ -200,7 +193,7 @@ function Cart(config) {
200 193  
201 194 Cart.prototype.show = function() {
202 195 $.ajax({
203   - url: '/profile/'+ this.enterprise +'/plugins/shopping_cart/show',
  196 + url: '/plugin/shopping_cart/show',
204 197 dataType: 'json',
205 198 cache: false,
206 199 error: function(ajax, status, errorThrown) {
... ... @@ -215,7 +208,7 @@ function Cart(config) {
215 208 }
216 209 Cart.prototype.hide = function() {
217 210 $.ajax({
218   - url: '/profile/'+ this.enterprise +'/plugins/shopping_cart/hide',
  211 + url: '/plugin/shopping_cart/hide',
219 212 dataType: 'json',
220 213 cache: false,
221 214 error: function(ajax, status, errorThrown) {
... ... @@ -252,7 +245,7 @@ function Cart(config) {
252 245 Cart.prototype.clean = function() {
253 246 var me = this;
254 247 $.ajax({
255   - url: '/profile/'+ me.enterprise +'/plugins/shopping_cart/clean',
  248 + url: '/plugin/shopping_cart/clean',
256 249 dataType: 'json',
257 250 success: function(data, status, ajax){
258 251 if ( !data.ok ) alert(data.error.message);
... ... @@ -261,7 +254,6 @@ function Cart(config) {
261 254 $(me.cartElem).slideUp(500, function() {
262 255 $(me.itemsBox).empty();
263 256 me.hide();
264   - me.enterprise = null;
265 257 me.updateTotal();
266 258 me.empty = true;
267 259 });
... ... @@ -284,7 +276,7 @@ function Cart(config) {
284 276 var me = this;
285 277 $.ajax({
286 278 type: 'POST',
287   - url: '/profile/'+ me.enterprise +'/plugins/shopping_cart/send_request',
  279 + url: '/plugin/shopping_cart/send_request',
288 280 data: params,
289 281 dataType: 'json',
290 282 success: function(data, status, ajax){
... ... @@ -309,7 +301,19 @@ function Cart(config) {
309 301 }
310 302  
311 303 $(function(){
312   - $('.cart-add-item').button({ icons: { primary: 'ui-icon-cart'} })
  304 +
  305 + $.ajax({
  306 + url: "/plugin/shopping_cart/get",
  307 + dataType: 'json',
  308 + success: function(data) {
  309 + new Cart(data);
  310 + $('.cart-add-item').button({ icons: { primary: 'ui-icon-cart'} })
  311 + },
  312 + cache: false,
  313 + error: function(ajax, status, errorThrown) {
  314 + alert('Error getting shopping cart - HTTP '+status+': '+errorThrown);
  315 + }
  316 + });
313 317 });
314 318  
315 319 })(jQuery);
... ...
plugins/shopping_cart/public/edit.js 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +jQuery('#settings_delivery').click(function(){
  2 + jQuery('#delivery_settings').toggle('fast');
  3 +});
  4 +
  5 +jQuery('#add-new-option').click(function(){
  6 + new_option = jQuery('#empty-option').clone();
  7 + new_option.removeAttr('id');
  8 + jQuery('#add-new-option-row').before(new_option);
  9 + new_option.show();
  10 + return false;
  11 +});
  12 +
  13 +jQuery('.remove-option').live('click', function(){
  14 + jQuery(this).closest('tr').remove();
  15 + return false;
  16 +});
... ...
plugins/shopping_cart/public/style.css
1   -@import url(colorbox/colorbox.css);
2   -
3 1 .cart-add-item .ui-icon-cart {
4 2 background: url("/plugins/shopping_cart/images/button-icon.png") no-repeat scroll left center transparent;
5 3 width: 22px;
... ...
plugins/shopping_cart/test/functional/shopping_cart_plugin_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,225 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../../controllers/shopping_cart_plugin_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class ShoppingCartPluginController; def rescue_action(e) raise e end; end
  6 +
  7 +class ShoppingCartPluginControllerTest < ActionController::TestCase
  8 +
  9 + def setup
  10 + @controller = ShoppingCartPluginController.new
  11 + @request = ActionController::TestRequest.new
  12 + @response = ActionController::TestResponse.new
  13 + @enterprise = fast_create(Enterprise)
  14 + @product = fast_create(Product, :enterprise_id => @enterprise.id)
  15 + end
  16 + attr_reader :enterprise
  17 + attr_reader :product
  18 +
  19 + should 'force cookie expiration with explicit path for an empty cart' do
  20 + get :get
  21 + assert @response.headers['Set-Cookie'].any? { |c| c =~ /_noosfero_plugin_shopping_cart=; path=\/plugin\/shopping_cart; expires=.*-1970/}
  22 + end
  23 +
  24 + should 'add a new product to cart' do
  25 + get :add, :id => product.id
  26 +
  27 + assert product_in_cart?(product)
  28 + assert_equal 1, product_quantity(product)
  29 + end
  30 +
  31 + should 'grow quantity through add' do
  32 + get :add, :id => product.id
  33 + assert_equal 1, product_quantity(product)
  34 +
  35 + get :add, :id => product.id
  36 + assert_equal 2, product_quantity(product)
  37 + end
  38 +
  39 + should 'not add product to cart if it does not exists' do
  40 + assert_nothing_raised { get :add, :id => 9999 }
  41 +
  42 + assert !product_in_cart?(product)
  43 + assert !response_ok?
  44 + assert 3, reponse_error_code
  45 + end
  46 +
  47 + should 'remove cart if the product being removed is the last one' do
  48 + get :add, :id => product.id
  49 + assert cart?
  50 +
  51 + get :remove, :id => product.id
  52 + assert !cart?
  53 + end
  54 +
  55 + should 'not try to remove a product if there is no cart' do
  56 + instantiate_cart
  57 + assert !cart?
  58 +
  59 + assert_nothing_raised { get :remove, :id => 9999 }
  60 + assert !response_ok?
  61 + assert_equal 2, reponse_error_code
  62 + end
  63 +
  64 + should 'just remove product if there are other products on cart' do
  65 + another_product = fast_create(Product, :enterprise_id => enterprise.id)
  66 + get :add, :id => product.id
  67 + get :add, :id => another_product.id
  68 +
  69 + get :remove, :id => product.id
  70 + assert cart?
  71 + assert !product_in_cart?(product)
  72 + end
  73 +
  74 + should 'not try to remove a product that is not in the cart' do
  75 + get :add, :id => product.id
  76 + assert cart?
  77 + assert_nothing_raised { get :remove, :id => 9999 }
  78 +
  79 + assert !response_ok?
  80 + assert_equal 4, reponse_error_code
  81 + end
  82 +
  83 + should 'not try to list the cart if there is no cart' do
  84 + instantiate_cart
  85 + assert !cart?
  86 +
  87 + assert_nothing_raised { get :list }
  88 + assert !response_ok?
  89 + assert_equal 2, reponse_error_code
  90 + end
  91 +
  92 + should 'list products without errors' do
  93 + get :add, :id => product.id
  94 +
  95 + assert_nothing_raised { get :list }
  96 + assert response_ok?
  97 + end
  98 +
  99 + should 'update the quantity of a product' do
  100 + get :add, :id => product.id
  101 + assert 1, product_quantity(product)
  102 +
  103 + get :update_quantity, :id => product.id, :quantity => 3
  104 + assert 3, product_quantity(product)
  105 + end
  106 +
  107 + should 'not try to update quantity the quantity of a product if there is no cart' do
  108 + instantiate_cart
  109 + assert !cart?
  110 +
  111 + assert_nothing_raised { get :update_quantity, :id => 9999, :quantity => 3 }
  112 + assert !response_ok?
  113 + assert_equal 2, reponse_error_code
  114 + end
  115 +
  116 + should 'not try to update the quantity of a product that is not in the cart' do
  117 + get :add, :id => product.id
  118 + assert cart?
  119 + assert_nothing_raised { get :update_quantity, :id => 9999, :quantity => 3 }
  120 +
  121 + assert !response_ok?
  122 + assert_equal 4, reponse_error_code
  123 + end
  124 +
  125 + should 'not update the quantity of a product with a invalid value' do
  126 + get :add, :id => product.id
  127 +
  128 + assert_nothing_raised { get :update_quantity, :id => product.id, :quantity => -1}
  129 + assert !response_ok?
  130 + assert_equal 5, reponse_error_code
  131 +
  132 + assert_nothing_raised { get :update_quantity, :id => product.id, :quantity => 'asdf'}
  133 + assert !response_ok?
  134 + assert_equal 5, reponse_error_code
  135 + end
  136 +
  137 + should 'clean the cart' do
  138 + another_product = fast_create(Product, :enterprise_id => enterprise.id)
  139 + get :add, :id => product.id
  140 + get :add, :id => another_product.id
  141 +
  142 + assert_nothing_raised { get :clean }
  143 + assert !cart?
  144 + end
  145 +
  146 + should 'not crash if there is no cart' do
  147 + instantiate_cart
  148 + assert !cart?
  149 + assert_nothing_raised { get :clean }
  150 + end
  151 +
  152 + should 'register order on send request' do
  153 + product1 = fast_create(Product, :enterprise_id => enterprise.id, :price => 1.99)
  154 + product2 = fast_create(Product, :enterprise_id => enterprise.id, :price => 2.23)
  155 + @controller.stubs(:cart).returns({ :enterprise_id => enterprise.id, :items => {product1.id => 1, product2.id => 2}})
  156 + assert_difference ShoppingCartPlugin::PurchaseOrder, :count, 1 do
  157 + post :send_request,
  158 + :customer => {:name => "Manuel", :email => "manuel@ceu.com"}
  159 + end
  160 +
  161 + order = ShoppingCartPlugin::PurchaseOrder.last
  162 +
  163 + assert_equal 1.99, order.products_list[product1.id][:price]
  164 + assert_equal 1, order.products_list[product1.id][:quantity]
  165 + assert_equal 2.23, order.products_list[product2.id][:price]
  166 + assert_equal 2, order.products_list[product2.id][:quantity]
  167 + assert_equal ShoppingCartPlugin::PurchaseOrder::Status::OPENED, order.status
  168 + end
  169 +
  170 + should 'register order on send request and not crash if product is not defined' do
  171 + product1 = fast_create(Product, :enterprise_id => enterprise.id)
  172 + @controller.stubs(:cart).returns({ :enterprise_id => enterprise.id, :items => {product1.id => 1}})
  173 + assert_difference ShoppingCartPlugin::PurchaseOrder, :count, 1 do
  174 + post :send_request,
  175 + :customer => {:name => "Manuel", :email => "manuel@ceu.com"}
  176 + end
  177 +
  178 + order = ShoppingCartPlugin::PurchaseOrder.last
  179 +
  180 + assert_equal 0, order.products_list[product1.id][:price]
  181 + end
  182 +
  183 + should 'clean the cart after placing the order' do
  184 + product1 = fast_create(Product, :enterprise_id => enterprise.id)
  185 + post :add, :id => product1.id
  186 + post :send_request, :customer => { :name => "Manuel", :email => "manuel@ceu.com" }
  187 + assert !cart?, "cart expected to be empty!"
  188 + end
  189 +
  190 + private
  191 +
  192 + def json_response
  193 + ActiveSupport::JSON.decode @response.body
  194 + end
  195 +
  196 + def cart?
  197 + !@controller.send(:cart).nil?
  198 + end
  199 +
  200 + def product_in_cart?(product)
  201 + @controller.send(:cart) &&
  202 + @controller.send(:cart)[:items] &&
  203 + @controller.send(:cart)[:items].has_key?(product.id)
  204 + end
  205 +
  206 + def product_quantity(product)
  207 + @controller.send(:cart)[:items][product.id]
  208 + end
  209 +
  210 + def response_ok?
  211 + json_response['ok']
  212 + end
  213 +
  214 + def reponse_error_code
  215 + json_response['error']['code']
  216 + end
  217 +
  218 + # temporary hack...if I don't do this the session stays as an Array instead
  219 + # of a TestSession
  220 + def instantiate_cart
  221 + get :add, :id => product.id
  222 + get :remove, :id => product.id
  223 + end
  224 +
  225 +end
... ...
plugins/shopping_cart/test/functional/shopping_cart_plugin_myprofile_controller_test.rb
... ... @@ -13,46 +13,42 @@ class ShoppingCartPluginMyprofileControllerTest &lt; ActionController::TestCase
13 13 attr_reader :enterprise
14 14  
15 15 should 'be able to enable shopping cart' do
16   - enterprise.shopping_cart = false
17   - enterprise.save
18   - post :edit, :profile => enterprise.identifier, :profile_attr => {:shopping_cart => '1'}
19   - enterprise.reload
  16 + settings.enabled = false
  17 + settings.save!
  18 + post :edit, :profile => enterprise.identifier, :settings => {:enabled => '1'}
20 19  
21   - assert enterprise.shopping_cart
  20 + assert settings.enabled
22 21 end
23 22  
24 23 should 'be able to disable shopping cart' do
25   - enterprise.shopping_cart = true
26   - enterprise.save
27   - post :edit, :profile => enterprise.identifier, :profile_attr => {:shopping_cart => '0'}
28   - enterprise.reload
  24 + settings.enabled = true
  25 + settings.save!
  26 + post :edit, :profile => enterprise.identifier, :settings => {:enabled => '0'}
29 27  
30   - assert !enterprise.shopping_cart
  28 + assert !settings.enabled
31 29 end
32 30  
33 31 should 'be able to enable shopping cart delivery' do
34   - enterprise.shopping_cart_delivery = false
35   - enterprise.save
36   - post :edit, :profile => enterprise.identifier, :profile_attr => {:shopping_cart_delivery => '1'}
37   - enterprise.reload
  32 + settings.delivery = false
  33 + settings.save!
  34 + post :edit, :profile => enterprise.identifier, :settings => {:delivery => '1'}
38 35  
39   - assert enterprise.shopping_cart_delivery
  36 + assert settings.delivery
40 37 end
41 38  
42 39 should 'be able to disable shopping cart delivery' do
43   - enterprise.shopping_cart_delivery = true
44   - enterprise.save
45   - post :edit, :profile => enterprise.identifier, :profile_attr => {:shopping_cart_delivery => '0'}
46   - enterprise.reload
  40 + settings.delivery = true
  41 + settings.save!
  42 + post :edit, :profile => enterprise.identifier, :settings => {:delivery => '0'}
47 43  
48   - assert !enterprise.shopping_cart_delivery
  44 + assert !settings.delivery
49 45 end
50 46  
51 47 should 'be able to choose the delivery price' do
52 48 price = 4.35
53   - post :edit, :profile => enterprise.identifier, :profile_attr => {:shopping_cart_delivery_price => price}
54   - enterprise.reload
55   - assert enterprise.shopping_cart_delivery_price == price
  49 + post :edit, :profile => enterprise.identifier, :settings => {:delivery_price => price}
  50 +
  51 + assert settings.delivery_price == price
56 52 end
57 53  
58 54 should 'filter the reports correctly' do
... ... @@ -112,4 +108,11 @@ class ShoppingCartPluginMyprofileControllerTest &lt; ActionController::TestCase
112 108 po.reload
113 109 assert_equal ShoppingCartPlugin::PurchaseOrder::Status::CONFIRMED, po.status
114 110 end
  111 +
  112 + private
  113 +
  114 + def settings
  115 + @enterprise.reload
  116 + Noosfero::Plugin::Settings.new(@enterprise, ShoppingCartPlugin)
  117 + end
115 118 end
... ...
plugins/shopping_cart/test/functional/shopping_cart_plugin_profile_controller_test.rb
... ... @@ -1,213 +0,0 @@
1   -require File.dirname(__FILE__) + '/../../../../test/test_helper'
2   -require File.dirname(__FILE__) + '/../../controllers/shopping_cart_plugin_profile_controller'
3   -
4   -# Re-raise errors caught by the controller.
5   -class ShoppingCartPluginProfileController; def rescue_action(e) raise e end; end
6   -
7   -class ShoppingCartPluginProfileControllerTest < ActionController::TestCase
8   -
9   - def setup
10   - @controller = ShoppingCartPluginProfileController.new
11   - @request = ActionController::TestRequest.new
12   - @response = ActionController::TestResponse.new
13   - @enterprise = fast_create(Enterprise)
14   - @product = fast_create(Product, :enterprise_id => @enterprise.id)
15   - end
16   - attr_reader :enterprise
17   - attr_reader :product
18   -
19   - should 'add a new product to cart' do
20   - get :add, :profile => enterprise.identifier, :id => product.id
21   -
22   - assert product_in_cart?(product)
23   - assert_equal 1, product_quantity(product)
24   - end
25   -
26   - should 'grow quantity through add' do
27   - get :add, :profile => enterprise.identifier, :id => product.id
28   - assert_equal 1, product_quantity(product)
29   -
30   - get :add, :profile => enterprise.identifier, :id => product.id
31   - assert_equal 2, product_quantity(product)
32   - end
33   -
34   - should 'not add product to cart if it does not exists' do
35   - assert_nothing_raised { get :add, :profile => enterprise.identifier, :id => 9999 }
36   -
37   - assert !product_in_cart?(product)
38   - assert !response_ok?
39   - assert 3, reponse_error_code
40   - end
41   -
42   - should 'remove cart if the product being removed is the last one' do
43   - get :add, :profile => enterprise.identifier, :id => product.id
44   - assert cart?
45   -
46   - get :remove, :profile => enterprise.identifier, :id => product.id
47   - assert !cart?
48   - end
49   -
50   - should 'not try to remove a product if there is no cart' do
51   - instantiate_session
52   - assert !cart?
53   -
54   - assert_nothing_raised { get :remove, :profile => enterprise.identifier, :id => 9999 }
55   - assert !response_ok?
56   - assert_equal 2, reponse_error_code
57   - end
58   -
59   - should 'just remove product if there are other products on cart' do
60   - another_product = fast_create(Product, :enterprise_id => enterprise.id)
61   - get :add, :profile => enterprise.identifier, :id => product.id
62   - get :add, :profile => enterprise.identifier, :id => another_product.id
63   -
64   - get :remove, :profile => enterprise.identifier, :id => product.id
65   - assert cart?
66   - assert !product_in_cart?(product)
67   - end
68   -
69   - should 'not try to remove a product that is not in the cart' do
70   - get :add, :profile => enterprise.identifier, :id => product.id
71   - assert cart?
72   - assert_nothing_raised { get :remove, :profile => enterprise.identifier, :id => 9999 }
73   -
74   - assert !response_ok?
75   - assert_equal 4, reponse_error_code
76   - end
77   -
78   - should 'not try to list the cart if there is no cart' do
79   - instantiate_session
80   - assert !cart?
81   -
82   - assert_nothing_raised { get :list, :profile => enterprise.identifier }
83   - assert !response_ok?
84   - assert_equal 2, reponse_error_code
85   - end
86   -
87   - should 'list products without errors' do
88   - get :add, :profile => enterprise.identifier, :id => product.id
89   -
90   - assert_nothing_raised { get :list, :profile => enterprise.identifier }
91   - assert response_ok?
92   - end
93   -
94   - should 'update the quantity of a product' do
95   - get :add, :profile => enterprise.identifier, :id => product.id
96   - assert 1, product_quantity(product)
97   -
98   - get :update_quantity, :profile => enterprise.identifier, :id => product.id, :quantity => 3
99   - assert 3, product_quantity(product)
100   - end
101   -
102   - should 'not try to update quantity the quantity of a product if there is no cart' do
103   - instantiate_session
104   - assert !cart?
105   -
106   - assert_nothing_raised { get :update_quantity, :profile => enterprise.identifier, :id => 9999, :quantity => 3 }
107   - assert !response_ok?
108   - assert_equal 2, reponse_error_code
109   - end
110   -
111   - should 'not try to update the quantity of a product that is not in the cart' do
112   - get :add, :profile => enterprise.identifier, :id => product.id
113   - assert cart?
114   - assert_nothing_raised { get :update_quantity, :profile => enterprise.identifier, :id => 9999, :quantity => 3 }
115   -
116   - assert !response_ok?
117   - assert_equal 4, reponse_error_code
118   - end
119   -
120   - should 'not update the quantity of a product with a invalid value' do
121   - get :add, :profile => enterprise.identifier, :id => product.id
122   -
123   - assert_nothing_raised { get :update_quantity, :profile => enterprise.identifier, :id => product.id, :quantity => -1}
124   - assert !response_ok?
125   - assert_equal 5, reponse_error_code
126   -
127   - assert_nothing_raised { get :update_quantity, :profile => enterprise.identifier, :id => product.id, :quantity => 'asdf'}
128   - assert !response_ok?
129   - assert_equal 5, reponse_error_code
130   - end
131   -
132   - should 'clean the cart' do
133   - another_product = fast_create(Product, :enterprise_id => enterprise.id)
134   - get :add, :profile => enterprise.identifier, :id => product.id
135   - get :add, :profile => enterprise.identifier, :id => another_product.id
136   -
137   - assert_nothing_raised { get :clean, :profile => enterprise.identifier }
138   - assert !cart?
139   - end
140   -
141   - should 'not crash if there is no cart' do
142   - instantiate_session
143   - assert !cart?
144   - assert_nothing_raised { get :clean, :profile => enterprise.identifier }
145   - end
146   -
147   - should 'register order on send request' do
148   - product1 = fast_create(Product, :enterprise_id => enterprise.id, :price => 1.99)
149   - product2 = fast_create(Product, :enterprise_id => enterprise.id, :price => 2.23)
150   - @controller.stubs(:session).returns({:cart => {:items => {product1.id => 1, product2.id => 2}}})
151   - assert_difference ShoppingCartPlugin::PurchaseOrder, :count, 1 do
152   - post :send_request,
153   - :customer => {:name => "Manuel", :email => "manuel@ceu.com"},
154   - :profile => enterprise.identifier
155   - end
156   -
157   - order = ShoppingCartPlugin::PurchaseOrder.last
158   -
159   - assert_equal 1.99, order.products_list[product1.id][:price]
160   - assert_equal 1, order.products_list[product1.id][:quantity]
161   - assert_equal 2.23, order.products_list[product2.id][:price]
162   - assert_equal 2, order.products_list[product2.id][:quantity]
163   - assert_equal ShoppingCartPlugin::PurchaseOrder::Status::OPENED, order.status
164   - end
165   -
166   - should 'register order on send request and not crash if product is not defined' do
167   - product1 = fast_create(Product, :enterprise_id => enterprise.id)
168   - @controller.stubs(:session).returns({:cart => {:items => {product1.id => 1}}})
169   - assert_difference ShoppingCartPlugin::PurchaseOrder, :count, 1 do
170   - post :send_request,
171   - :customer => {:name => "Manuel", :email => "manuel@ceu.com"},
172   - :profile => enterprise.identifier
173   - end
174   -
175   - order = ShoppingCartPlugin::PurchaseOrder.last
176   -
177   - assert_equal 0, order.products_list[product1.id][:price]
178   - end
179   -
180   - private
181   -
182   - def json_response
183   - ActiveSupport::JSON.decode @response.body
184   - end
185   -
186   - def cart?
187   - !session[:cart].nil?
188   - end
189   -
190   - def product_in_cart?(product)
191   - session[:cart][:items].has_key?(product.id)
192   - end
193   -
194   - def product_quantity(product)
195   - session[:cart][:items][product.id]
196   - end
197   -
198   - def response_ok?
199   - json_response['ok']
200   - end
201   -
202   - def reponse_error_code
203   - json_response['error']['code']
204   - end
205   -
206   - # temporary hack...if I don't do this the session stays as an Array instead
207   - # of a TestSession
208   - def instantiate_session
209   - get :add, :profile => enterprise.identifier, :id => product.id
210   - get :remove, :profile => enterprise.identifier, :id => product.id
211   - end
212   -
213   -end
plugins/shopping_cart/test/unit/shopping_cart_plugin_test.rb
... ... @@ -7,7 +7,6 @@ class ShoppingCartPluginTest &lt; ActiveSupport::TestCase
7 7 @context = mock()
8 8 @profile = mock()
9 9 @profile.stubs(:identifier).returns('random-user')
10   - @context.stubs(:profile).returns(@profile)
11 10 @shopping_cart.context = @context
12 11 @shopping_cart.stubs(:profile).returns(@profile)
13 12 end
... ... @@ -23,7 +22,8 @@ class ShoppingCartPluginTest &lt; ActiveSupport::TestCase
23 22 product = fast_create(Product, :available => false)
24 23 enterprise = mock()
25 24 enterprise.stubs(:shopping_cart).returns(true)
  25 + product.stubs(:enterprise).returns(enterprise)
26 26  
27   - assert_nil shopping_cart.add_to_cart_button(product, enterprise)
  27 + assert_nil shopping_cart.add_to_cart_button(product)
28 28 end
29 29 end
... ...
plugins/shopping_cart/views/cart.html.erb
... ... @@ -7,7 +7,7 @@
7 7 <a href="cart:clean" onclick="Cart.clean(this); return false" class="cart-clean"><%=_('Clean basket')%></a>
8 8 <ul class="cart-items"></ul>
9 9 <div class="cart-total"><%=_('Total:')%> <b></b></div>
10   - <a href="cart:buy" class="cart-buy"><%=_('Shopping checkout')%></a>
  10 + <a href="/plugin/shopping_cart/buy" class="cart-buy"><%=_('Shopping checkout')%></a>
11 11 </div>
12 12 <a href="#" onclick="Cart.toggle(this); return false" class="cart-toggle">
13 13 <span class="str-show"><%=_('Show basket')%></span>
... ... @@ -15,9 +15,3 @@
15 15 </a>
16 16 </div>
17 17 </div>
18   -
19   -<script type="text/javascript">
20   -//<![CDATA[
21   - new Cart({hasProducts:<%= !locals[:cart].nil? ? "true, enterprise:'#{Enterprise.find(locals[:cart][:enterprise_id]).identifier}'" : "false" %>});
22   -//]]>
23   -</script>
... ...
plugins/shopping_cart/views/shopping_cart_plugin/buy.html.erb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +<% person = user.nil? ? Person.new : user %>
  2 +<div id='cart-request-box'>
  3 + <% form_for(:customer, person, :url => {:action => 'send_request'},
  4 + :html => {:onsubmit => "return Cart.send_request(this)", :id => 'cart-request-form' }) do |f| %>
  5 + <div id="cart-form-main">
  6 + <%= labelled_form_field('* ' + _("Name"), f.text_field(:name, :class => 'required') ) %>
  7 + <%= labelled_form_field('* ' + _("Email"), f.text_field(:email, :class => 'required email') ) %>
  8 + <%= labelled_form_field('* ' + _("Contact phone"), f.text_field(:contact_phone, :class => 'required') ) %>
  9 + <%= labelled_form_field(_('Delivery option'), select_tag(:delivery_option, options_for_select(select_delivery_options(@settings.delivery_options, environment)), 'data-profile-identifier' => @enterprise.identifier)) unless !@settings.delivery || (@settings.free_delivery_price && get_total(@cart[:items]) >= @settings.free_delivery_price) %>
  10 + <%= labelled_form_field(_('Payment'), select_tag('customer[payment]', options_for_select([[_("Money"), :money],[_('Check'), :check]]))) %>
  11 + <%= labelled_form_field(_('Change'), text_field_tag('customer[change]')) %>
  12 + </div>
  13 + <% if @settings.delivery %>
  14 + <fieldset><legend><%=_('Delivery Address')%></legend>
  15 + <%= labelled_form_field(_('Address (street and number)'), f.text_field(:address)) %>
  16 + <%= labelled_form_field(_('Address reference'), f.text_field(:address_reference)) %>
  17 + <%= labelled_form_field(_('District'), f.text_field(:district)) %>
  18 + <%= labelled_form_field( _("City"), f.text_field(:city)) %>
  19 + <%= labelled_form_field(_('ZIP code'), f.text_field(:zip_code)) %>
  20 + </fieldset>
  21 + <% end %>
  22 + <div id="cart-form-actions">
  23 + <%= submit_button(:send, _('Send buy request')) %>
  24 + </div>
  25 + <% end %>
  26 + <% delivery_option = @settings.delivery_options.first && @settings.delivery_options.first.first %>
  27 + <%= items_table(@cart[:items], @enterprise, delivery_option) %>
  28 + <%= link_to '', '#', :onclick => "Cart.colorbox_close(this);", :class => 'cart-box-close icon-cancel' %>
  29 +</div>
  30 +
  31 +<%= javascript_include_tag '../plugins/shopping_cart/buy' %>
... ...
plugins/shopping_cart/views/shopping_cart_plugin/mailer/customer_notification.html.erb
... ... @@ -17,26 +17,35 @@
17 17 <li><b><%= _('Full name') %>: </b><%= @customer[:name] %></li>
18 18 <li><b><%= _('Email') %>: </b><%= @customer[:email] %></li>
19 19 <li><b><%= _('Phone number') %>: </b><%= @customer[:contact_phone] %></li>
20   - <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? %>
  20 + <li><b><%= _('Payment') %>: </b><%= @customer[:payment] == 'money' ? _('Money') : _('Check') %></li>
  21 + <% if @customer[:payment] == 'money' %>
  22 + <li><b><%= _('Change') %>: </b><%= @customer[:change] %></li>
  23 + <% end %>
  24 + <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? || !@customer[:district].blank? || !@customer[:address_reference].blank? %>
21 25 <li><b><%= _('Address') %>:</b>
22 26 <% end %>
23 27 <% if !@customer[:address].blank? %>
24 28 <%= @customer[:address] %><br \>
25 29 <% end %>
  30 + <% if !@customer[:district].blank? %>
  31 + <%= @customer[:district] %><br \>
  32 + <% end %>
26 33 <% if !@customer[:city].blank? %>
27 34 <%= @customer[:city] %><br \>
28 35 <% end %>
29 36 <% if !@customer[:zip_code].blank? %>
30 37 <%= @customer[:zip_code] %>
31 38 <% end %>
32   - <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? %>
  39 + <% if !@customer[:address_reference].blank? %>
  40 + <%= @customer[:address_reference] %><br \>
  41 + <% end %>
  42 + <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? || !@customer[:district].blank? || !@customer[:address_reference].blank? %>
33 43 </li>
34 44 <% end %>
35 45 </ul>
36 46  
37 47 <p><%=_('Here are the products you bought:')%></p>
38   - <%= items_table(@items, @supplier, true) %>
39   -
  48 + <%= @helper.items_table(@items, @supplier, @delivery_option, true) %>
40 49 <p>
41 50 --<br/>
42 51 <%=_('Thanks for buying with us!')%><br/>
... ...
plugins/shopping_cart/views/shopping_cart_plugin/mailer/supplier_notification.html.erb
... ... @@ -15,26 +15,35 @@
15 15 <li><b><%= _('Full name') %>: </b><%= @customer[:name] %></li>
16 16 <li><b><%= _('Email') %>: </b><%= @customer[:email] %></li>
17 17 <li><b><%= _('Phone number') %>: </b><%= @customer[:contact_phone] %></li>
18   - <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? %>
  18 + <li><b><%= _('Payment') %>: </b><%= @customer[:payment] == 'money' ? _('Money') : _('Check') %></li>
  19 + <% if @customer[:payment] == 'money' %>
  20 + <li><b><%= _('Change') %>: </b><%= @customer[:change] %></li>
  21 + <% end %>
  22 + <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? || !@customer[:district].blank? || !@customer[:address_reference].blank? %>
19 23 <li><b><%= _('Address') %>:</b>
20 24 <% end %>
21 25 <% if !@customer[:address].blank? %>
22 26 <%= @customer[:address] %><br \>
23 27 <% end %>
  28 + <% if !@customer[:district].blank? %>
  29 + <%= @customer[:district] %><br \>
  30 + <% end %>
24 31 <% if !@customer[:city].blank? %>
25 32 <%= @customer[:city] %><br \>
26 33 <% end %>
27 34 <% if !@customer[:zip_code].blank? %>
28 35 <%= @customer[:zip_code] %>
29 36 <% end %>
30   - <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? %>
  37 + <% if !@customer[:address_reference].blank? %>
  38 + <%= @customer[:address_reference] %><br \>
  39 + <% end %>
  40 + <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? || !@customer[:district].blank? || !@customer[:address_reference].blank? %>
31 41 </li>
32 42 <% end %>
33 43 </ul>
34 44  
35 45 <p><%=_('And here are the items bought by this customer:')%></p>
36   - <%= items_table(@items, @supplier, true) %>
37   -
  46 + <%= @helper.items_table(@items, @supplier, @delivery_option, true) %>
38 47 <p>
39 48 --<br/>
40 49 <%=_('If there are any problems with this email contact the admin of %s.') % @environment.name %>
... ...
plugins/shopping_cart/views/shopping_cart_plugin/send_request.html.erb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +<%= _("Request sent successfully check your email.")%>
... ...
plugins/shopping_cart/views/shopping_cart_plugin_myprofile/_orders_list.html.erb
... ... @@ -32,7 +32,9 @@
32 32 <td class="order-info" colspan="5">
33 33 <div style="display:none">
34 34 <ul class="customer-details">
35   - <% { 'name' =>_('Name'), 'email' => _('E-mail'), 'contact_phone' => _('Contact phone'), 'address' => _('Address'), 'city' => _('City'), 'zip_code' => _('Zip code')}.each do |attribute, name| %>
  35 + <% [['name', _('Name')], ['email', _('E-mail')], ['contact_phone', _('Contact phone')], ['address', _('Address')], ['district', _('District')], ['city', _('City')], ['zip_code', _('Zip code')], ['delivery_option', _('Delivery option')], ['payment', _('Payment')], ['change', _('Change')]].each do |field| %>
  36 + <% attribute = field.first %>
  37 + <% name = field.last %>
36 38 <%= content_tag('li', content_tag('strong', name+': ') + order.send('customer_'+attribute)) if !order.send('customer_'+attribute).blank? %>
37 39 <% end %>
38 40 </ul>
... ...