Commit 1e0d9a57a82715de413e761eef7acfe380dce483

Authored by Antonio Terceiro
2 parents 1d97315c a125dc04

Merge branch 'master' into rails-2.3.5

Conflicts:
	app/controllers/application.rb
	lib/noosfero.rb
	lib/noosfero/i18n.rb
	test/test_helper.rb
	test/unit/consumption_test.rb
Showing 1595 changed files with 105574 additions and 92651 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 1595 files displayed.

.gitignore.example
... ... @@ -1,28 +0,0 @@
1   -.gitignore
2   -vendor/rails
3   -doc/api
4   -doc/plugins
5   -tmp
6   -config/database.yml
7   -config/session.secret
8   -config/mail.yml
9   -config/ferret_server.yml
10   -config/mongrel_cluster.yml
11   -index
12   -locale
13   -log
14   -public/articles
15   -public/images/0000
16   -public/thumbnails
17   -public/user_themes
18   -public/designs/themes/default
19   -public/javascripts/cache*.js
20   -public/stylesheets/cache*.css
21   -db/development.db
22   -db/production.db
23   -db/test.db
24   -doc/noosfero/*.xhtml
25   -doc/noosfero/*/*.xhtml
26   -tags
27   -pkg
28   -*~
.mailmap
1 1 Antonio Terceiro <terceiro@colivre.coop.br> <AntonioTerceiro@3f533792-8f58-4932-b0fe-aaf55b0a4547>
2 2 Antonio Terceiro <terceiro@colivre.coop.br> <terceiro@softwarelivre.org>
3 3 Aurelio A. Heckert <aurelio@colivre.coop.br> <AurelioAHeckert@3f533792-8f58-4932-b0fe-aaf55b0a4547>
  4 +Aurelio A. Heckert <aurelio@colivre.coop.br> <aurelio@colivre.coop.br>
  5 +Aurelio A. Heckert <aurelio@colivre.coop.br> <aurium@gmail.com>
  6 +Caio SBA <caiosba@gmail.com> <caiosba@safernet.org.br>
4 7 Daniela Soares Feitosa <danielafeitosa@colivre.coop.br> <DanielaFeitosa@3f533792-8f58-4932-b0fe-aaf55b0a4547>
5 8 Daniela Soares Feitosa <danielafeitosa@colivre.coop.br> <danielafeitosa@colivre.coop.br>
6 9 Daniela Soares Feitosa <danielafeitosa@colivre.coop.br> <daniela@sede.colivre.coop.br>
7 10 Daniela Soares Feitosa <danielafeitosa@colivre.coop.br> <dani@lindinha.(none)>
  11 +Daniel Cunha <daniel@colivre.coop.br> <daniel.ccunha@gmail.com>
  12 +Grazieno Pellegrino <grazieno@gmail.com> <GrazienoPellegrino@3f533792-8f58-4932-b0fe-aaf55b0a4547>
8 13 Joenio Costa <joenio@colivre.coop.br> <JoenioCosta@3f533792-8f58-4932-b0fe-aaf55b0a4547>
9 14 Joenio Costa <joenio@colivre.coop.br> <joenio@perl.org.br>
10   -Grazieno Pellegrino <grazieno@gmail.com> <GrazienoPellegrino@3f533792-8f58-4932-b0fe-aaf55b0a4547>
11 15 Leandro Nunes dos Santos <leandronunes@gmail.com> <LeandroNunes@3f533792-8f58-4932-b0fe-aaf55b0a4547>
12 16 Moises Machado <moises@colivre.coop.br> <MoisesMachado@3f533792-8f58-4932-b0fe-aaf55b0a4547>
13 17 Valessio Brito <valessio@gmail.com> <ValessioBrito@3f533792-8f58-4932-b0fe-aaf55b0a4547>
... ...
AUTHORS
... ... @@ -8,13 +8,18 @@ Developers
8 8  
9 9 Antonio Terceiro <terceiro@colivre.coop.br>
10 10 Aurelio A. Heckert <aurelio@colivre.coop.br>
  11 +Bráulio Bhavamitra <brauliobo@gmail.com>
  12 +Caio SBA <caiosba@gmail.com>
11 13 Daniela Soares Feitosa <danielafeitosa@colivre.coop.br>
  14 +Daniel Cunha <daniel@colivre.coop.br>
12 15 Fernanda Lopes <nanda.listas+psl@gmail.com>
13 16 Grazieno Pellegrino <grazieno@gmail.com>
14 17 Italo Valcy <italo@dcc.ufba.br>
15 18 Joenio Costa <joenio@colivre.coop.br>
16 19 Josef Spillner <josef.spillner@tu-dresden.de>
  20 +Keilla Menezes <keilla@colivre.coop.br>
17 21 Leandro Nunes dos Santos <leandronunes@gmail.com>
  22 +LinguÁgil 2010 <linguagil.bahia@gmail.com>
18 23 Martín Olivera <molivera@solar.org.ar>
19 24 Moises Machado <moises@colivre.coop.br>
20 25 Nanda Lopes <nanda.listas+psl@gmail.com>
... ... @@ -22,6 +27,7 @@ Raphaël Rousseau &lt;raph@r4f.org&gt;
22 27 Raquel Lira <raquel.lira@gmail.com>
23 28 Rodrigo Souto <rodrigo@colivre.coop.br>
24 29 Ronny Kursawe <kursawe.ronny@googlemail.com>
  30 +Samuel R. C. Vale <srcvale@holoscopio.com>
25 31 Valessio Brito <valessio@gmail.com>
26 32 Yann Lugrin <yann.lugrin@liquid-concept.ch>
27 33  
... ...
HACKING
... ... @@ -61,3 +61,26 @@ The above command makes the server available at http://localhost:9999/
61 61  
62 62 The sample-data data scripts creates one administrator user with login "ze" and
63 63 password "test".
  64 +
  65 +Note that some operations, like generating image thumbnails, sending e-mails,
  66 +etc, are done in background in the context of a service independent from the
  67 +Rails application server. To have those tasks performed in a development
  68 +environment, you must run the delayed_job server like this:
  69 +
  70 + ./script/delayed_job run
  71 +
  72 +This will block your terminal. To stop the delayed_job server, hit Control-C.
  73 +
  74 +== Enabling exceptions notification
  75 +
  76 +By default, exception notifications are disabled in development environment. If
  77 +you want to enable it then you need to change some files:
  78 +
  79 +1) Add in config/environments/development.rb:
  80 + config.action_controller.consider_all_requests_local = false
  81 +
  82 +2) Add in app/controller/application.rb:
  83 + local_addresses.clear
  84 +
  85 +3) Add in config/noosfero.yml at development section:
  86 + exception_recipients: [admin@example.com]
... ...
INSTALL
... ... @@ -13,13 +13,14 @@ You need to install some packages Noosfero depends on. On Debian GNU/Linux or
13 13 Debian-based systems, all of these packages are available through the Debian
14 14 archive. You can install them with the following command:
15 15  
16   - # aptitude install ruby rake libgettext-ruby1.8 libsqlite3-ruby rcov librmagick-ruby libredcloth-ruby libwill-paginate-ruby iso-codes libfeedparser-ruby libferret-ruby libdaemons-ruby mongrel mongrel-cluster tango-icon-theme
  16 + # apt-get install ruby rake po4a libgettext-ruby-util libgettext-ruby-data libgettext-ruby1.8 libsqlite3-ruby rcov librmagick-ruby libredcloth-ruby libwill-paginate-ruby iso-codes libfeedparser-ruby libferret-ruby libdaemons-ruby mongrel mongrel-cluster tango-icon-theme libhpricot-ruby
17 17  
18 18 On other systems, they may or may not be available through your regular package
19 19 management system. Below are the links to their homepages.
20 20  
21 21 * Ruby: http://www.ruby-lang.org/
22 22 * Rake: http://rake.rubyforge.org/
  23 +* po4a: http://po4a.alioth.debian.org/
23 24 * Ruby-GetText: http://www.yotabanana.com/hiki/ruby-gettext.html?ruby-gettext (at least version 1.9.0)
24 25 * Ruby-sqlite3: http://rubyforge.org/projects/sqlite-ruby
25 26 * rcov: http://eigenclass.org/hiki/rcov
... ... @@ -32,79 +33,268 @@ management system. Below are the links to their homepages.
32 33 * Daemons - http://daemons.rubyforge.org/
33 34 * Mongrel: http://mongrel.rubyforge.org/
34 35 * tango-icon-theme: http://tango.freedesktop.org/Tango_Icon_Library
  36 +* Hpricot: http://hpricot.com/
35 37  
36   -If you manage to install Noosfero successfully, please feel free to contact the
37   -Noosfero development mailing with the instructions for doing so, and we'll
38   -include it here.
  38 +Note: the tango-icon-theme package is not available in Debian Lenny's main
  39 +repository, because back then it was not DFSG-free (Debian Squeeze will have it
  40 +in main). You can either add the non-free repository to your sources.list file,
  41 +or download the .deb directly and install it manually with `dpkg -i`. In this
  42 +case will need do install hicolor-icon-theme as well bacause tango-icon-theme
  43 +depends on it. After that you can try the command line above again, but
  44 +without "tango-icon-theme".
  45 +
  46 +If you manage to install Noosfero successfully on other systems than Debian,
  47 +please feel free to contact the Noosfero development mailing with the
  48 +instructions for doing so, and we'll include it here.
39 49  
40 50 === Setting up a production environment
41 51  
  52 +DISCLAIMER: this installation procedure is tested with Debian stable, which is
  53 +currently the only recommended operating system for production usage. It is
  54 +possible that you can install it on other systems, and if you do so, please
  55 +report it on one of the Noosfero mailing lists, and please send a patch
  56 +updating these instructions.
  57 +
  58 +As root user
  59 +============
  60 +
42 61 NOTE: these instructions are for seting up a *production* environment. If you
43   -are going to do Noosfero development, you don't need to do these steps. See the
44   -HACKING file instead.
  62 +are going to do Noosfero development, you don't need to do these steps. Stop
  63 +here and see the HACKING file instead.
  64 +
  65 +Install memcached. On Debian:
  66 +
  67 +# apt-get install memcached
  68 +
  69 +Study whether you need to raise the ammount of memory it uses for caching,
  70 +depending on the demand you expect for your site. If you are going to run a
  71 +high-traffic site, you will want to raise the ammount of memory reserved for
  72 +caching.
  73 +
  74 +It is recommended that you run noosfero with its own user account. To create
  75 +such an account, please do the following:
  76 +
  77 +# adduser --system --group noosfero --shell /bin/sh --home /var/lib/noosfero
  78 +
  79 +(note that you can change the $HOME directory of the user if you wish, here we
  80 +are using /var/lib/noosfero)
  81 +
  82 +The --system option will tell adduser to create a system user, i.e. this user
  83 +will not have a password and cannot login to the system directly. To become
  84 +this user, you have to use sudo:
  85 +
  86 +# sudo -u noosfero -i
  87 +
  88 +or
  89 +
  90 +# su - noosfero
  91 +
  92 +As noosfero user
  93 +================
  94 +
  95 +downloading from git
  96 +--------------------
  97 +
  98 +Here we are cloning the noosfero repository from git. Note: you will need to
  99 +install git before.
  100 +
  101 +$ git clone git://git.colivre.coop.br/noosfero.git current
  102 +$ cd current
  103 +$ git checkout -b stable origin/stable
  104 +
  105 +downloading tarball
  106 +-------------------
  107 +
  108 +Note: replace 0.27.1 below from the latest stable version.
  109 +
  110 +$ wget http://noosfero.org/pub/Development/NoosferoVersion00x27x01/noosfero-0.27.1.tar.gz
  111 +$ tar -zxvf noosfero-0.27.1.tar.gz
  112 +$ ln -s noosfero-0.27.1 current
  113 +$ cd current
  114 +
  115 +Copy config/ferret_server.yml.dist to config/ferret_server.yml. You will
  116 +probably not need to customize this configuration, but have a look at it.
  117 +
  118 +Create the mongrel configuration file:
  119 +
  120 +$ mongrel_rails cluster::configure
  121 +
  122 +Edit config/mongrel_cluster.yml to suit your needs. Make sure your apache
  123 +configuration matches the mongrel cluster configuration, specially in respect
  124 +to the ports and numbers of mongrel instances.
  125 +
  126 +Note: currently Noosfero only supports Rails 2.1.0, which is the version in
  127 +Debian Lenny. If you have a Rails version newer than that, Noosfero will
  128 +probably not work. You can install Rails 2.1.0 into your Noosfero installation
  129 +with the following procedure:
  130 +
  131 +$ cd /var/lib/noosfero/current/vendor
  132 +$ wget http://ftp.de.debian.org/debian/pool/main/r/rails/rails_2.1.0.orig.tar.gz
  133 +$ tar xzf rails_2.1.0.orig.tar.gz
  134 +$ ln -s rails-2.1.0 rails
  135 +
  136 +As root user
  137 +============
  138 +
  139 +Setup Noosfero log and tmp directories:
  140 +
  141 +# cd /var/lib/noosfero/current
  142 +# ./etc/init.d/noosfero setup
  143 +
  144 +Now it's time to setup the database. In this example we are using PostgreSQL,
  145 +so if you are planning to use a different database this steps won't apply.
  146 +
  147 +# apt-get install postgresql libpgsql-ruby
  148 +# su postgres -c 'createuser noosfero -S -d -R'
  149 +
  150 +By default Rails will try to connect on postgresql through 5432 port, but
  151 +Debian start postgresql on port 5433, then is needed to change postgresql to
  152 +start on port 5432 in /etc/postgresql/8.3/main/postgresql.conf file.
  153 +
  154 +Restart postgresql:
  155 +
  156 +# invoke-rc.d postgresql-8.3 restart
  157 +
  158 +Noosfero needs a functional e-mail setup to work: the local mail system should
  159 +be able to deliver e-mail to the internet, either directly or through an
  160 +external SMTP server.
  161 +
  162 +If you know mail systems well, you just need to make sure thet the local MTA,
  163 +listening on localhost:25, is able to deliver e-mails to the internet. Any mail
  164 +server will do it.
  165 +
  166 +If you are not a mail specialist, we suggest that you use the Postfix mail
  167 +server, since it is easy to configure and very reliable. Just follow the
  168 +instructions below.
  169 +
  170 +To install Postfix:
  171 +
  172 +# apt-get install postfix
  173 +
  174 +During the installation process, you will be asked a few questions. Your answer
  175 +to them will vary in 2 cases:
  176 +
  177 +Case 1: you can send e-mails directly to the internet. This will be the case
  178 +for most commercial private servers. Your answers should be:
  179 +
  180 + General type of mail configuration: Internet site
  181 + System mail name: the name of your domain, e.g. "mysocialnetwork.com"
  182 +
  183 +Case 2: you cannot, or don't want to, send e-mail directly to the internet.
  184 +This happens for example if your server is not allowed to make outbound
  185 +connections on port 25, or if you want to concentrate all your outbound mail
  186 +through a single SMTP server. Your answers in this case should be:
  187 +
  188 + General type of mail configuration: Internet with smarthost
  189 + System mail name: the name of your domain, e.g. "mysocialnetwork.com"
  190 + SMTP relay host: smtp.yourprovider.com
  191 +
  192 +Note that smtp.yourprovider.com must allow your server to deliver e-mails
  193 +through it. You should probably ask your servive provider about this.
  194 +
  195 +There is another possibility: if you are installing on a shared server, and
  196 +don't have permission to configure the local MTA, you can instruct Noosfero to
  197 +send e-mails directly through an external server. Please note that this should
  198 +be your last option, since contacting an external SMTP server directly may slow
  199 +down your Noosfero application server. To configure Noosfero to send e-mails
  200 +through an external SMTP server, follow the instructions on
  201 +http://noosfero.org/Development/SMTPMailSending
45 202  
46   -* install memcached. Study whether you need to raise the ammount of memory it uses for caching, depending on the demand you expect for your site.
47   -* enter the directory where you unpacked noosfero
48   -* copy config/ferret_server.yml.dist to config/ferret_server.yml
49   -* create the mongrel configuration file: `mongrel_rails cluster::configure`
50   -** then edit config/mongrel_cluster.yml to suit your environment. Make sure your apache configuration matches the mongrel cluster configuration, specially in respect to the ports and numbers of mongrel instances.
51   -* create needed temporary directories:
52   - mkdir tmp
53   - mkdir tmp/pids
54   - mkdir log
55   -* create database (example using PostgreSQL, YMMV)
  203 +As noosfero user
  204 +================
56 205  
57   - root user
58   - =========
59   - # sudo apt-get install postgresql libpgsql-ruby
60   - # su - postgres
  206 +Now create the databases:
61 207  
62   - postgres user
63   - =============
64   - postgres@HOST:~$ createuser noosfero
65   - Shall the new role be a superuser? (y/n) n
66   - Shall the new role be allowed to create databases? (y/n) y
67   - Shall the new role be allowed to create more new roles? (y/n) n
  208 +$ cd /var/lib/noosfero/current
  209 +$ createdb noosfero_production
  210 +$ createdb noosfero_development
  211 +$ createdb noosfero_test
68 212  
69   - noosfero_user
70   - =============
71   - createdb noosfero_production
72   - createdb noosfero_development
73   - createdb noosfero_test
  213 +The development and test databases are actually optional. If you are creating a
  214 +stricly production server, you will probably not need them.
74 215  
75   -* configure database access in config/database.yml
  216 +Now we want to configure Noosfero for accessing the database we just created.
  217 +To do that, you can 1) copy config/database.yml.pgsql to config/database.yml,
  218 +or create config/database.yml from scratch with the following content:
76 219  
77   -* test database access:
78   -** first create the development database
79   - rake db:schema:load
80   -** if everything goes right, then create the production database:
81   - RAILS_ENV=production rake db:schema:load
  220 + production:
  221 + adapter: postgresql
  222 + encoding: unicode
  223 + database: noosfero_production
  224 + username: noosfero
82 225  
83   -* create sample data:
84   - RAILS_ENV=production rake db:populate
  226 +Now, to test the database access, you can fire the Rails database console:
85 227  
86   -* Add the domain name you will be using for your noosfero site to the list of
87   - environment domain, like this:
  228 +$ ./script/dbconsole production
88 229  
89   - ./script/runner "Environment.default.domains << Domain.new(:name => 'your.domain.com')"
  230 +If it connects to your database, then everything is fine. If you got an error
  231 +message, then you have to check your database configuration.
90 232  
91   - (of course, replace your.domain.com with your real domain)
  233 +Create the database structure:
92 234  
93   -* compile the translations:
94   - rake makemo
  235 +$ RAILS_ENV=production rake db:schema:load
95 236  
96   -* start the server:
97   - ./script/production start
  237 +Now we have to create some initial data. To create your default environment
  238 +(the first one), run the command below:
98 239  
99   -* to stop the server:
  240 +$ RAILS_ENV=production ./script/runner 'Environment.create!(:name => "My environment", :is_default => true)'
100 241  
101   - ./script/production stop
  242 +(of course, replace "My environment" with your environment's name!)
102 243  
103   -* to restart the server:
  244 +And now you have to add the domain name you will be using for your noosfero
  245 +site to the list of domains of that default environment you just created:
104 246  
105   - ./script/production restart
  247 +$ RAILS_ENV=production ./script/runner "Environment.default.domains << Domain.new(:name => 'your.domain.com')"
106 248  
107   -* enable the following apache modules:
  249 +(replace "your.domain.com" with your actual domain name)
  250 +
  251 +Add at least one user as admin of environment:
  252 +
  253 +$ RAILS_ENV=production ./script/runner "User.create(:login => 'adminuser', :email => 'admin@example.com', :password => 'admin', :password_confirmation => 'admin', :environment => Environment.default)"
  254 +
  255 +(replace "adminuser", "admin@example.com", "admin" with the login, email
  256 +and password of your environment admin)
  257 +
  258 +Compile the translations:
  259 +
  260 +$ RAILS_ENV=production rake noosfero:translations:compile
  261 +
  262 +To start the Noosfero application servers:
  263 +
  264 +$ ./script/production start
  265 +
  266 +At this point you have a functional Noosfero installation running, the only
  267 +thing left is to configure your webserver as a reverse proxy to pass requests
  268 +to them.
  269 +
  270 +Enabling exception notifications
  271 +================================
  272 +
  273 +This is an optional step. You will need it only if you want to receive e-mail
  274 +notifications when some exception occurs on Noosfero.
  275 +
  276 +First, install this version of the gem.
  277 +Others versions may not be compatible with Noosfero:
  278 +
  279 +# gem install exception_notification -v 1.0.20090728
  280 +
  281 +You can configure the e-mails that will receive the notifications.
  282 +Change the file config/noosfero.yml as the following example, replacing the
  283 +e-mails by real ones:
  284 +
  285 + production:
  286 + exception_recipients: [admin@example.com, you@example.com]
  287 +
  288 +==================
  289 +Apache instalation
  290 +==================
  291 +
  292 +# apt-get install apache2
  293 +
  294 +Apache configuration
  295 +--------------------
  296 +
  297 +First you have to enable the following some apache modules:
108 298  
109 299 deflate
110 300 expires
... ... @@ -113,26 +303,32 @@ HACKING file instead.
113 303 proxy_http
114 304 rewrite
115 305  
116   - On Debian GNU/Linux system, these modules can be enabled with the following
117   - command line, as root:
  306 +On Debian GNU/Linux system, these modules can be enabled with the following
  307 +command line, as root:
118 308  
119   - a2enmod deflate expires proxy proxy_balancer proxy_http rewrite
  309 +# a2enmod deflate expires proxy proxy_balancer proxy_http rewrite
120 310  
121   - In other systems the way by which you enable apache modules may be different.
  311 +In other systems the way by which you enable apache modules may be different.
122 312  
123   -* Configure apache web server as a reverse proxy. You can use the template
124   - below, replacing /path/to/noosfero with the directory in which your noosfero
125   - installation is, your.domain.com with the domain name of your noosfero site.
126   - We are assuming that you are running two mongrel instances on ports 4000 and
127   - 4001. If your setup is different you'll need to adjust <Proxy> section. If
128   - you don't understand something in the configuration, please refer to the
129   - apache documentation.
  313 +Now with the Apache configuration. You can use the template below, replacing
  314 +/var/lib/noosfero/current with the directory in which your noosfero
  315 +installation is, your.domain.com with the domain name of your noosfero site.
  316 +We are assuming that you are running two mongrel instances on ports 3000 and
  317 +3001. If your setup is different you'll need to adjust <Proxy> section. If you
  318 +don't understand something in the configuration, please refer to the apache
  319 +documentation.
  320 +
  321 +Add a file called "mysite" (or whatever name you want to give to your noosfero
  322 +site) to /etc/apache2/sites-available with the following content, and customize
  323 +as needed (as usual, make sure you replace "your.domain.com" with you actual
  324 +domain name, and "/var/lib/noosfero/current" with the directory where Noosfero
  325 +is installed):
130 326  
131 327 <VirtualHost *:80>
132 328 ServerName your.domain.com
133 329  
134   - DocumentRoot "/path/to/noosfero/public"
135   - <Directory "/path/to/noosfero/public">
  330 + DocumentRoot "/var/lib/noosfero/current/public"
  331 + <Directory "/var/lib/noosfero/current/public">
136 332 Options FollowSymLinks
137 333 AllowOverride None
138 334 Order Allow,Deny
... ... @@ -156,18 +352,110 @@ HACKING file instead.
156 352 LogLevel warn
157 353 CustomLog /var/log/apache2/noosfero.access.log combined
158 354  
159   - Include /path/to/noosfero/etc/noosfero/apache/cache.conf
  355 + Include /var/lib/noosfero/current/etc/noosfero/apache/cache.conf
160 356  
161 357 </VirtualHost>
162 358  
163 359 <Proxy balancer://noosfero>
164   - BalancerMember http://127.0.0.1:4000
165   - BalancerMember http://127.0.0.1:4001
  360 + BalancerMember http://127.0.0.1:3000
  361 + BalancerMember http://127.0.0.1:3001
166 362 Order Allow,Deny
167 363 Allow from All
168 364 </Proxy>
169 365  
170   - The cache.conf file included in the end of the <VirtualHost> section is
171   - important, since it will tell apache to pass expiration and cache headers to
172   - clients so that the site feels faster for users. Do we need to say that using
173   - that configuration is strongly recommended?
  366 +The cache.conf file included in the end of the <VirtualHost> section is
  367 +important, since it will tell apache to pass expiration and cache headers to
  368 +clients so that the site feels faster for users. Do we need to say that using
  369 +that configuration is strongly recommended?
  370 +
  371 +Enable that site with (as root, replace "mysite" with the actual name you gave
  372 +to your site configuration):
  373 +
  374 +# a2ensite mysite
  375 +
  376 +Now restart your apache server (as root):
  377 +
  378 +# invoke-rc.d apache2 restart
  379 +
  380 +============
  381 +Maintainance
  382 +============
  383 +
  384 +To ease the maintainance, install a symbolic link for the Noosfero startup
  385 +script in your server and add it to the system initialization and shutdown
  386 +sequences (as root):
  387 +
  388 +# ln -s /var/lib/noosfero/current/etc/init.d/noosfero /etc/init.d/noosfero
  389 +# update-rc.d noosfero defaults
  390 + Adding system startup for /etc/init.d/noosfero ...
  391 + /etc/rc0.d/K20noosfero -> ../init.d/noosfero
  392 + /etc/rc1.d/K20noosfero -> ../init.d/noosfero
  393 + /etc/rc6.d/K20noosfero -> ../init.d/noosfero
  394 + /etc/rc2.d/S20noosfero -> ../init.d/noosfero
  395 + /etc/rc3.d/S20noosfero -> ../init.d/noosfero
  396 + /etc/rc4.d/S20noosfero -> ../init.d/noosfero
  397 + /etc/rc5.d/S20noosfero -> ../init.d/noosfero
  398 +
  399 +Now to start Noosfero, you do as root:
  400 +
  401 +# invoke-rc.d noosfero start
  402 +
  403 +To stop Noosfero:
  404 +
  405 +# invoke-rc.d noosfero start
  406 +
  407 +To restart Noosfero:
  408 +
  409 +# invoke-rc.d noosfero restart
  410 +
  411 +Noosfero will be automatically started during system boot, and automatically
  412 +stopped if the system shuts down for some reason (or during the shutdown part
  413 +of a reboot).
  414 +
  415 +=============
  416 +Rotating logs
  417 +=============
  418 +
  419 +Noosfero provides an example logrotate configuation to rotate its logs. To use
  420 +it, create a symbolic link in /etc/logrotate.d/:
  421 +
  422 +# cd /etc/logrotate.d/
  423 +# ln -s /var/lib/noosfero/current/etc/logrotate.d/noosfero
  424 +
  425 +Note that the provided file assumes Noosfero logging is being done in
  426 +/var/log/noosfero (which is the case if you followed the instructions above
  427 +correctly). If the logs are stored elsewhere, it's recommended that you copy
  428 +the file over to /etc/logrotate.d/ and modify it to point to your local log
  429 +directly.
  430 +
  431 +=========
  432 +Upgrading
  433 +=========
  434 +
  435 +If you followed the steps in this document and installed Noosfero from the git
  436 +repository, then upgrading is easy. First, you need to allow the noosfero user
  437 +to restart the memcached server with sudo, by adding the following line in
  438 +/etc/sudoers:
  439 +
  440 +noosfero ALL=NOPASSWD: /etc/init.d/memcached
  441 +
  442 +Then, to perform an upgrade, do the following as the noosfero user:
  443 +
  444 +$ cd /var/lib/noosfero/current
  445 +$ ./script/git-upgrade
  446 +
  447 +The git-upgrade script will take care of everything for you. It will first stop
  448 +the service, then fetch the current source code, upgrade database, compile
  449 +translations, and then start the service again.
  450 +
  451 +Note 1: make sure your local git repository is following the "stable" branch,
  452 +just like the instructions above. The "master" branch is not recommended for
  453 +use in production environments.
  454 +
  455 +Note 2: always read the release notes before upgrading. Sometimes there will be
  456 +steps that must be performed manually. If that is the case, you can invoke the
  457 +git-upgrade script with the special parameter "--shell" that will give you a
  458 +shell after the upgrade, which you can use to perform any manual steps
  459 +required:
  460 +
  461 +$ ./script/git-upgrade --shell
... ...
INSTALL.chat 0 → 100644
... ... @@ -0,0 +1,255 @@
  1 +== XMPP/Chat Client Setup
  2 +
  3 +To configure XMPP/BOSH in Noosfero you need:
  4 +
  5 +* REST Client - http://github.com/archiloque/rest-client
  6 +* SystemTimer - http://ph7spot.com/musings/system-timer
  7 +* Pidgin data files - http://www.pidgin.im/
  8 +
  9 +If you use Debian Lenny:
  10 +
  11 +# apt-get install librestclient-ruby (from backports)
  12 +# apt-get install pidgin-data
  13 +# apt-get install ruby1.8-dev
  14 +# gem install SystemTimer
  15 +
  16 +Take a look at util/chat directory to see samples of config file to configure a
  17 +XMPP/BOSH server with ejabberd, postgresql and apache2.
  18 +
  19 +== XMPP/Chat Server Setup
  20 +
  21 +This is a step-by-step guide to get a XMPP service working, in a Debian system.
  22 +
  23 +1. Install the required packages
  24 +
  25 +# apt-get -t lenny-backports install ejabberd
  26 +# apt-get install odbc-postgresql
  27 +
  28 +2. Ejabberd configuration
  29 +
  30 +All the following changes must be done in config file:
  31 +
  32 + /etc/ejabberd/ejabberd.cfg
  33 +
  34 + 2.1. Set the default admin user
  35 +
  36 +{ acl, admin, { user, "john", "www.example.com" } }.
  37 +{ acl, admin, { user, "bart", "www.example.com" } }.
  38 +
  39 + 2.2. Set the default host
  40 +
  41 +{ hosts, [ "www.example.com" ] }.
  42 +
  43 + 2.3. Http-Bind activation
  44 +
  45 +{ 5280, ejabberd_http, [
  46 + http_bind,
  47 + web_admin
  48 + ]
  49 +}
  50 +
  51 +(...)
  52 +
  53 +{ modules, [
  54 + {mod_http_bind, []},
  55 + ...
  56 +] }.
  57 +
  58 +Ejabberd creates semi-anonymous rooms by default, but Noosfero's Jabber client
  59 +needs non-anonymous room, then we need to change default params of creation
  60 +rooms in ejabberd to create non-anonymous rooms.
  61 +
  62 +In non-anonymous rooms the jabber service sends the new occupant's full JID to
  63 +all occupants in the room[1].
  64 +
  65 +Add option "{default_room_options, [{anonymous, false}]}" to
  66 +/etc/ejabberd/ejabberd.cfg in mod_muc session. See below:
  67 +
  68 +{ mod_muc, [
  69 + %%{host, "conference.@HOST@"},
  70 + {access, muc},
  71 + {access_create, muc},
  72 + {access_persistent, muc},
  73 + {access_admin, muc_admin},
  74 + {max_users, 500},
  75 + {default_room_options, [{anonymous, false}]}
  76 +]},
  77 +
  78 +[1] - http://xmpp.org/extensions/xep-0045.html#enter-nonanon
  79 +
  80 +
  81 + 2.4. Authentication method
  82 +
  83 +To use Postgresql through ODBC, the following modifications must be done:
  84 +
  85 + * Disable the default method:
  86 +
  87 +{auth_method, internal}.
  88 +
  89 + * Enable autheticantion through ODBC:
  90 +
  91 +{auth_method, odbc}.
  92 +
  93 + * Set database server name
  94 +
  95 +{odbc_server, "DSN=PostgreSQLEjabberdNoosfero"}.
  96 +
  97 +
  98 + 2.5. Increase the shaper traffic limit
  99 +
  100 +{ shaper, normal, { maxrate, 10000000 } }.
  101 +
  102 +
  103 + 2.6. Disable unused modules
  104 +
  105 +Unused modules can be disabled, for example:
  106 +
  107 + * s2s
  108 + * web_admin
  109 + * mod_pubsub
  110 + * mod_irc
  111 + * mod_offine
  112 + * mod_admin_extra
  113 + * mod_register
  114 +
  115 +
  116 + 2.7. Enable ODBC modules
  117 +
  118 + * mod_privacy -> mod_privacy_odbc
  119 + * mod_private -> mod_private_odbc
  120 + * mod_roster -> mod_roster_odbc
  121 +
  122 +3. Configuring Postgresql
  123 +
  124 +Login as noosfero user, and execute:
  125 +
  126 + $ psql noosfero < /path/to/noosfero/util/chat/postgresql/ejabberd.sql
  127 +
  128 +Where 'noosfero' may need to be replace by the name of the database used for
  129 +Noosfero.
  130 +
  131 +This will create a new schema inside the noosfero database, called 'ejabberd'.
  132 +
  133 +Note 'noosfero' user should have permission to create Postgresql schemas. Also,
  134 +there should be at least one domain with 'is_default = true' in 'domains'
  135 +table, otherwise people couldn't see your friends online.
  136 +
  137 +
  138 +4. ODBC configuration
  139 +
  140 +The following files must be created:
  141 +
  142 + * /etc/odbc.ini
  143 +
  144 +[PostgreSQLEjabberdNoosfero]
  145 +Description = PostgreSQL Noosfero ejabberd database
  146 +Driver = PostgreSQL Unicode
  147 +Trace = No
  148 +TraceFile = /tmp/psqlodbc.log
  149 +Database = noosfero
  150 +Servername = localhost
  151 +UserName = <DBUSER>
  152 +Password = <DBPASS>
  153 +Port =
  154 +ReadOnly = No
  155 +RowVersioning = No
  156 +ShowSystemTables = No
  157 +ShowOidColumn = No
  158 +FakeOidIndex = No
  159 +ConnSettings = SET search_path TO ejabberd
  160 +
  161 + * /etc/odbcinst.ini
  162 +
  163 +[PostgreSQL Unicode]
  164 +Description = PostgreSQL ODBC driver (Unicode version)
  165 +Driver = /usr/lib/odbc/psqlodbcw.so
  166 +Setup = /usr/lib/odbc/libodbcpsqlS.so
  167 +Debug = 0
  168 +CommLog = 1
  169 +UsageCount = 3
  170 +
  171 + * testing all:
  172 +
  173 +# isql 'PostgreSQLEjabberdNoosfero' DBUSER
  174 +
  175 +
  176 +5. Enabling kernel polling and SMP in /etc/default/ejabberd
  177 +
  178 +POLL=true
  179 +SMP=auto
  180 +
  181 +
  182 +6. Increase the file descriptors limit for user ejabberd
  183 +
  184 + 6.1. Uncomment this line in file /etc/pam.d/su:
  185 +
  186 +session required pam_limits.so
  187 +
  188 +
  189 + 6.2. Add this lines to file /etc/security/limits.conf:
  190 +
  191 +ejabberd hard nofile 65536
  192 +ejabberd soft nofile 65536
  193 +
  194 +Now, test the configuration:
  195 +
  196 +# cat /proc/<EJABBERD_BEAM_PROCESS_PID>/limits
  197 +
  198 +
  199 +7. Apache Configuration
  200 +
  201 +Apache server must be configurated as follow:
  202 +
  203 + * /etc/apache2/sites-enabled/noosfero
  204 +
  205 +RewriteEngine On
  206 +Include /usr/share/noosfero/util/chat/apache/xmpp.conf
  207 +
  208 + * /etc/apache2/apache2.conf:
  209 +
  210 +<IfModule mpm_worker_module>
  211 + StartServers 8
  212 + MinSpareThreads 25
  213 + MaxSpareThreads 75
  214 + ThreadLimit 128
  215 + ThreadsPerChild 128
  216 + MaxClients 2048
  217 + MaxRequestsPerChild 0
  218 +</IfModule>
  219 +
  220 +Note: module proxy_http must be enabled:
  221 +
  222 +# a2enmod proxy_http
  223 +
  224 +
  225 +8. DNS configuration
  226 +
  227 + * /etc/bind/db.colivre:
  228 +
  229 +_xmpp-client._tcp SRV 5 100 5222 master
  230 +(...)
  231 +conference CNAME master
  232 +_xmpp-client._tcp.conference SRV 5 100 5222 master
  233 +
  234 +
  235 +9. Testing this Setup
  236 +
  237 +Adjust shell limits to proceed with some benchmarks and load tests:
  238 +
  239 +# ulimit −s 256
  240 +# ulimit −n 8192
  241 +# echo 10 > /proc/sys/net/ipv4/tcp_syn_retries
  242 +
  243 +To measure the bandwidth between server and client:
  244 +
  245 + * at server side:
  246 +
  247 +# iperf −s
  248 +
  249 + * at client side:
  250 +
  251 +# iperf −c server_ip
  252 +
  253 +For heavy load tests, clone and use this software:
  254 +
  255 +git clone http://git.holoscopio.com/git/metal/tester.git
... ...
INSTALL.varnish 0 → 100644
... ... @@ -0,0 +1,67 @@
  1 += Setting up Varnish for your Noosfero site
  2 +
  3 +Varnish is a HTTP caching server, and using it together with Noosfero is highly
  4 +recommended. See http://www.varnish-cache.org/ for more information on Varnish.
  5 +
  6 +Varnish can be set up to use with Noosfero with the following steps:
  7 +
  8 +1) setup Noosfero with apache according to the INSTALL file.
  9 +
  10 +2) install Varnish
  11 +
  12 + # apt-get install varnish
  13 +
  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 +3) Enable varnish logging:
  19 +
  20 +3a) Edit /etc/default/varnishncsa and uncomment the line that contains:
  21 +
  22 +VARNISHNCSA_ENABLED=1
  23 +
  24 +The varnish log will be written to /var/log/varnish/varnishncsa.log in an
  25 +apache-compatible format. You should change your statistics generation software
  26 +(e.g. awstats) to use that instead of apache logs.
  27 +
  28 +3b) Restart Varnish Logging service
  29 +
  30 + # invoke-rc.d varnishncsa start
  31 +
  32 +4) Change Apache to listen on port 8080 instead of 80
  33 +
  34 +4a) Edit /etc/apache2/ports.conf, and:
  35 +
  36 + * change 'Listen 80' to 'Listen 127.0.0.1:8080'
  37 + * change 'NameVirtualHost *:80' to 'NameVirtualHost *:8080'
  38 +
  39 +4b) Edit /etc/apache2/sites-enabled/*, and change '<VirtualHost *:80>' to '<VirtualHost *:8080>'
  40 +
  41 +4c) Restart apache
  42 +
  43 + # invoke-rc.d apache2 restart
  44 +
  45 +5) Change Varnish to listen on port 80
  46 +
  47 +5a) Edit /etc/default/varnish and change '-a :6081' to '-a :80'
  48 +
  49 +5b) Restart Varnish
  50 +
  51 + # invoke-rc.d varnish restart
  52 +
  53 +6) Configure varnish to store separate caches for each language
  54 +
  55 +6a) Add the following line to your /etc/varnish/default.vcl file (assuming
  56 +Noosfero is installed in /var/lib/noosfero):
  57 +
  58 + include "/var/lib/noosfero/etc/noosfero/varnish-accept-language.vcl";
  59 +
  60 +6b) Restart Varnish
  61 +
  62 + # invoke-rc.d varnish restart
  63 +
  64 +Thanks to Cosimo Streppone for varnish-accept-language. See
  65 +http://github.com/cosimo/varnish-accept-language for more information.
  66 +
  67 + -- Antonio Terceiro <terceiro@colivre.coop.br> Sat, 04 Sep 2010 17:29:27 -0300
... ...
Rakefile.pkg
... ... @@ -1,101 +0,0 @@
1   -require(File.join(File.dirname(__FILE__), 'config', 'boot'))
2   -
3   -require 'rake'
4   -require 'rake/testtask'
5   -require 'rake/rdoctask'
6   -require 'rake/packagetask'
7   -require 'noosfero'
8   -
9   -Rake::PackageTask.new(Noosfero::PROJECT, Noosfero::VERSION) do |p|
10   - p.need_tar_gz = true
11   -
12   - # cleaning temporary files
13   - FileUtils.rm_rf('tmp')
14   - FileUtils.mkdir_p('tmp/cache')
15   - FileUtils.mkdir_p('tmp/sessions')
16   - FileUtils.mkdir_p('tmp/sockets')
17   -
18   - # application files
19   - p.package_files.include('app/**/*.{rb,rhtml,rjs,rxml,erb}')
20   - p.package_files.include('config/**/*.{rb,sqlite3}')
21   - p.package_files.include('config/ferret_server.yml.dist')
22   - p.package_files.include('db/migrate/*.rb')
23   - p.package_files.include('db/schema.rb')
24   - p.package_files.include('doc/README_FOR_APP')
25   - p.package_files.include('lib/**/*.{rake,rb}')
26   - p.package_files.include('Rakefile')
27   - p.package_files.include('Rakefile.pkg')
28   -
29   - # translation files
30   - p.package_files.include('po/*/*.po')
31   - p.package_files.include('po/noosfero.pot')
32   -
33   - # templates
34   - p.package_files.include('public/designs/templates/**/*')
35   -
36   - # icon sets
37   - p.package_files.include('public/designs/icons/tango/**/*')
38   - p.package_files.exclude('public/designs/icons/tango/Tango')
39   - p.package_files.exclude('public/designs/icons/default')
40   -
41   - # themes
42   - p.package_files.include('public/designs/themes/noosfero/**/*')
43   - p.package_files.include('public/designs/themes/base/**/*')
44   - p.package_files.exclude('public/designs/themes/default')
45   -
46   - # static files
47   - p.package_files.include('public/dispatch.*')
48   - p.package_files.include('public/favicon.ico')
49   - p.package_files.include('public/*.html')
50   - p.package_files.include('public/*.html.erb')
51   - p.package_files.include('public/images/**/*')
52   - p.package_files.include('public/javascripts/**/*')
53   - p.package_files.include('public/robots.txt')
54   - p.package_files.include('public/stylesheets/**/*')
55   -
56   - # top-level docs
57   - p.package_files.include('README')
58   - p.package_files.include('COPYING')
59   - p.package_files.include('COPYRIGHT')
60   - p.package_files.include('INSTALL')
61   - p.package_files.include('HACKING')
62   -
63   - # scripts
64   - p.package_files.include('script/**/*')
65   -
66   - # test files
67   - p.package_files.include('test/**/*.{rb,yml}')
68   - p.package_files.include('test/fixtures/files/*')
69   - p.package_files.include('features/**/*')
70   - p.package_files.include('config/cucumber.yml')
71   -
72   - # empty directories that must exist
73   - p.package_files.include('tmp/cache')
74   - p.package_files.include('tmp/sessions')
75   - p.package_files.include('tmp/sockets')
76   - p.package_files.include('log')
77   -
78   - # symbolic links
79   - p.package_files.include('app/views/profile_design/*')
80   - p.package_files.include('app/views/environment_design/*')
81   -
82   - # util
83   - p.package_files.include('util/**/*')
84   -
85   - # external resources
86   - p.package_files.include('vendor/**/*')
87   - p.package_files.exclude('vendor/rails')
88   -
89   - # online documentation
90   - p.package_files.include('doc/noosfero/**/*')
91   -
92   - # do not install locally generated files
93   - p.package_files.exclude('coverage/**/*')
94   - p.package_files.exclude('public/images/[0-9][0-9][0-9][0-9]/**/*')
95   -
96   -end
97   -
98   -task :default => :package
99   -task :clean => :clobber_package
100   -
101   -# vim: ft=ruby
... ... @@ -1,49 +0,0 @@
1   -0 - All modified code have the tag UPGRADE on it.
2   -
3   -1 - I Changed acts_as_versioned plugin code to the new one:
4   -
5   - http://github.com/technoweenie/acts_as_versioned/tree/master
6   -
7   -2 - I removed the 'build_person' calls after create the user on UserTest
8   -
9   -3 - The settings_items method generated a lot of bugs. Theses bugs are explained here:
10   - Solution 1: http://casperfabricius.com/site/2008/06/12/serialize-doesnt-play-nice-with-rails-21-partial-updates/
11   - Solution 2: http://www.kalzumeus.com/2008/06/28/rails-fails-to-update-serialized-columns-on-save/
12   -
13   -The problem is how rails is working with ActiveRecord. The serialization data was not save on database.
14   -
15   -I add this code
16   -
17   - send(self.class.settings_field.to_s + '_will_change!')
18   -
19   -on lib/acts_as_having_settings
20   -
21   -I used the solution 1.
22   -
23   -I choose solution number 2 for Environment model. I think it is more clear in this situation.
24   -I put the code:
25   -
26   - self.partial_updates = false
27   -
28   -on Environment model.
29   -
30   -
31   -4 - acts_as_searchable -> find_by_contents
32   -
33   -I solved an incompatibility between paginate and geokit with an work around like this the one explained on the link:
34   -
35   - http://groups.google.com/group/will_paginate/browse_thread/thread/1bbb1f0c5810f1c1
36   -
37   -5 - The test of this method 'theme_include' of application helper was not passing.
38   -
39   -I changed the tests
40   - - "should 'render theme footer' do"
41   - - " should 'ignore unexisting theme footer' do"
42   -
43   - to make the test usefull
44   -
45   -
46   -6 - I changed the way as the product is created on ManageProducts controller. I removed the line:
47   - @product.build_image unless @product.image
48   -
49   -because the image was never saved. It don't have valid values to be saved.
app/controllers/admin/admin_panel_controller.rb
... ... @@ -40,7 +40,7 @@ class AdminPanelController &lt; AdminController
40 40 end
41 41 redirect_to :action => 'set_portal_folders'
42 42 else
43   - flash[:notice] = __('Community not found. You must insert the identifier of a community from this environment')
  43 + session[:notice] = __('Community not found. You must insert the identifier of a community from this environment')
44 44 end
45 45 end
46 46 end
... ... @@ -54,7 +54,7 @@ class AdminPanelController &lt; AdminController
54 54 folders = params[:folders].map{|fid| Folder.find(:first, :conditions => {:profile_id => env.portal_community, :id => fid})} if params[:folders]
55 55 env.portal_folders = folders
56 56 if env.save
57   - flash[:notice] = _('Saved the portal folders')
  57 + session[:notice] = _('Saved the portal folders')
58 58 redirect_to :action => 'set_portal_news_amount'
59 59 end
60 60 end
... ... @@ -63,7 +63,7 @@ class AdminPanelController &lt; AdminController
63 63 def set_portal_news_amount
64 64 if request.post?
65 65 if @environment.update_attributes(params[:environment])
66   - flash[:notice] = _('Saved the number of news on folders')
  66 + session[:notice] = _('Saved the number of news on folders')
67 67 redirect_to :action => 'index'
68 68 end
69 69 end
... ...
app/controllers/admin/categories_controller.rb
... ... @@ -5,9 +5,9 @@ class CategoriesController &lt; AdminController
5 5 helper :categories
6 6  
7 7 def index
8   - # WORKAROUND: restricting the category trees to display. Region and
9   - # ProductCategory have VERY LARGE trees.
10 8 @categories = environment.categories.find(:all, :conditions => "parent_id is null AND type is null")
  9 + @regions = environment.regions.find(:all, :conditions => {:parent_id => nil})
  10 + @product_categories = environment.product_categories.find(:all, :conditions => {:parent_id => nil})
11 11 end
12 12  
13 13 ALLOWED_TYPES = CategoriesHelper::TYPES.map {|item| item[1] }
... ...
app/controllers/admin/environment_design_controller.rb
... ... @@ -3,7 +3,7 @@ class EnvironmentDesignController &lt; BoxOrganizerController
3 3 protect 'edit_environment_design', :environment
4 4  
5 5 def available_blocks
6   - @available_blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock ]
  6 + @available_blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock ]
7 7 end
8 8  
9 9 end
... ...
app/controllers/admin/environment_role_manager_controller.rb
... ... @@ -14,9 +14,9 @@ class EnvironmentRoleManagerController &lt; AdminController
14 14 @roles = params[:roles] ? Role.find(params[:roles]) : []
15 15 @person = Person.find(params[:person])
16 16 if @person.define_roles(@roles, environment)
17   - flash[:notice] = _('Roles successfuly updated')
  17 + session[:notice] = _('Roles successfuly updated')
18 18 else
19   - flash[:notice] = _('Couldnt change the roles')
  19 + session[:notice] = _('Couldnt change the roles')
20 20 end
21 21 redirect_to :action => :index
22 22 end
... ... @@ -42,9 +42,9 @@ class EnvironmentRoleManagerController &lt; AdminController
42 42 def remove_role
43 43 @association = RoleAssignment.find(params[:id])
44 44 if @association.destroy
45   - flash[:notice] = _('Member succefully unassociated')
  45 + session[:notice] = _('Member succefully unassociated')
46 46 else
47   - flash[:notice] = _('Failed to unassociate member')
  47 + session[:notice] = _('Failed to unassociate member')
48 48 end
49 49 redirect_to :aciton => 'index'
50 50 end
... ... @@ -52,9 +52,9 @@ class EnvironmentRoleManagerController &lt; AdminController
52 52 def unassociate
53 53 @association = RoleAssignment.find(params[:id])
54 54 if @association.destroy
55   - flash[:notice] = _('Member succefully unassociated')
  55 + session[:notice] = _('Member succefully unassociated')
56 56 else
57   - flash[:notice] = _('Failed to unassociate member')
  57 + session[:notice] = _('Failed to unassociate member')
58 58 end
59 59 redirect_to :aciton => 'index'
60 60 end
... ...
app/controllers/admin/features_controller.rb
... ... @@ -8,7 +8,7 @@ class FeaturesController &lt; AdminController
8 8 post_only :update
9 9 def update
10 10 if @environment.update_attributes(params[:environment])
11   - flash[:notice] = _('Features updated successfully.')
  11 + session[:notice] = _('Features updated successfully.')
12 12 redirect_to :action => 'index'
13 13 else
14 14 render :action => 'index'
... ... @@ -24,7 +24,7 @@ class FeaturesController &lt; AdminController
24 24 def manage_person_fields
25 25 environment.custom_person_fields = params[:person_fields]
26 26 if environment.save!
27   - flash[:notice] = _('Person fields updated successfully.')
  27 + session[:notice] = _('Person fields updated successfully.')
28 28 else
29 29 flash[:error] = _('Person fields not updated successfully.')
30 30 end
... ... @@ -34,9 +34,9 @@ class FeaturesController &lt; AdminController
34 34 def manage_enterprise_fields
35 35 environment.custom_enterprise_fields = params[:enterprise_fields]
36 36 if environment.save!
37   - flash[:notice] = _('Enterprise fields updated successfully.')
  37 + session[:notice] = __('Enterprise fields updated successfully.')
38 38 else
39   - flash[:error] = _('Enterprise fields not updated successfully.')
  39 + flash[:error] = __('Enterprise fields not updated successfully.')
40 40 end
41 41 redirect_to :action => 'manage_fields'
42 42 end
... ... @@ -44,7 +44,7 @@ class FeaturesController &lt; AdminController
44 44 def manage_community_fields
45 45 environment.custom_community_fields = params[:community_fields]
46 46 if environment.save!
47   - flash[:notice] = _('Community fields updated successfully.')
  47 + session[:notice] = _('Community fields updated successfully.')
48 48 else
49 49 flash[:error] = _('Community fields not updated successfully.')
50 50 end
... ...
app/controllers/admin/plugins_controller.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +class PluginsController < AdminController
  2 +
  3 + def index
  4 + @active_plugins = Noosfero::Plugin.all.map {|plugin_name| plugin_name.constantize }.compact
  5 + end
  6 +
  7 + post_only :update
  8 + def update
  9 + params[:environment][:enabled_plugins].delete('')
  10 + if @environment.update_attributes(params[:environment])
  11 + session[:notice] = _('Plugins updated successfully.')
  12 + else
  13 + session[:error] = _('Plugins were not updated successfully.')
  14 + end
  15 + redirect_to :action => 'index'
  16 + end
  17 +
  18 +end
... ...
app/controllers/admin/role_controller.rb
... ... @@ -19,7 +19,7 @@ class RoleController &lt; AdminController
19 19 if @role.save
20 20 redirect_to :action => 'show', :id => @role
21 21 else
22   - flash[:notice] = _('Failed to create role')
  22 + session[:notice] = _('Failed to create role')
23 23 render :action => 'new'
24 24 end
25 25 end
... ... @@ -33,7 +33,7 @@ class RoleController &lt; AdminController
33 33 if @role.update_attributes(params[:role])
34 34 redirect_to :action => 'show', :id => @role
35 35 else
36   - flash[:notice] = _('Failed to edit role')
  36 + session[:notice] = _('Failed to edit role')
37 37 render :action => 'edit'
38 38 end
39 39 end
... ... @@ -43,7 +43,7 @@ class RoleController &lt; AdminController
43 43 if @role.destroy
44 44 redirect_to :action => 'index'
45 45 else
46   - flash[:notice] = _('Failed to edit role')
  46 + session[:notice] = _('Failed to edit role')
47 47 redirect_to :action => 'index'
48 48 end
49 49 end
... ...
app/controllers/admin/users_controller.rb 0 → 100644
... ... @@ -0,0 +1,36 @@
  1 +class UsersController < AdminController
  2 +
  3 + protect 'manage_environment_users', :environment
  4 +
  5 + def index
  6 + @users = environment.users
  7 + respond_to do |format|
  8 + format.html
  9 + format.xml do
  10 + render :xml => @users.to_xml(
  11 + :skip_types => true,
  12 + :only => %w[email login created_at updated_at],
  13 + :include => { :person => {:only => %w[name updated_at created_at address birth_date contact_phone identifier lat lng] } }
  14 + )
  15 + end
  16 + format.csv do
  17 + render :template => "users/index_csv.rhtml", :content_type => 'text/csv', :layout => false
  18 + end
  19 + end
  20 + end
  21 +
  22 + def send_mail
  23 + @mailing = environment.mailings.build(params[:mailing])
  24 + if request.post?
  25 + @mailing.locale = locale
  26 + @mailing.person = user
  27 + if @mailing.save
  28 + session[:notice] = _('The e-mails are being sent')
  29 + redirect_to :action => 'index'
  30 + else
  31 + session[:notice] = _('Could not create the e-mail')
  32 + end
  33 + end
  34 + end
  35 +
  36 +end
... ...
app/controllers/box_organizer_controller.rb
... ... @@ -96,7 +96,7 @@ class BoxOrganizerController &lt; ApplicationController
96 96 expire_timeout_fragment(@block.cache_keys)
97 97 redirect_to :action => 'index'
98 98 else
99   - flash[:notice] = _('Failed to remove block')
  99 + session[:notice] = _('Failed to remove block')
100 100 end
101 101 end
102 102  
... ...
app/controllers/my_profile/cms_controller.rb
... ... @@ -14,7 +14,8 @@ class CmsController &lt; MyProfileController
14 14 end
15 15 end
16 16  
17   - protect_if :except => [:set_home_page, :edit, :destroy, :publish] do |c, user, profile|
  17 + before_filter :login_required, :except => [:suggest_an_article]
  18 + protect_if :except => [:suggest_an_article, :set_home_page, :edit, :destroy, :publish] do |c, user, profile|
18 19 user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))
19 20 end
20 21  
... ... @@ -40,15 +41,11 @@ class CmsController &lt; MyProfileController
40 41 def available_article_types
41 42 articles = [
42 43 TinyMceArticle,
43   - TextileArticle
  44 + TextileArticle,
  45 + Event
44 46 ]
45   - articles << Event unless profile.environment.enabled?(:disable_asset_events)
  47 + articles += special_article_types if params && params[:cms]
46 48 parent_id = params ? params[:parent_id] : nil
47   - if !parent_id or !Article.find(parent_id).blog?
48   - articles += [
49   - RssFeed
50   - ]
51   - end
52 49 if profile.enterprise?
53 50 articles << EnterpriseHomepage
54 51 end
... ... @@ -56,22 +53,31 @@ class CmsController &lt; MyProfileController
56 53 end
57 54  
58 55 def special_article_types
59   - [Folder, Blog, UploadedFile]
  56 + [Folder, Blog, UploadedFile, Forum, Gallery, RssFeed]
60 57 end
61 58  
62 59 def view
63 60 @article = profile.articles.find(params[:id])
64   - @subitems = @article.children.reject {|item| item.folder? }
65   - if @article.blog?
66   - @subitems.reject! {|item| item.class == RssFeed }
  61 + conditions = []
  62 + if @article.has_posts?
  63 + conditions = ['type != ?', 'RssFeed']
67 64 end
68   - @folders = @article.children.select {|item| item.folder? }
  65 +
  66 + @articles = @article.children.paginate(
  67 + :order => "case when type = 'Folder' then 0 when type ='Blog' then 1 else 2 end, updated_at DESC",
  68 + :conditions => conditions,
  69 + :per_page => per_page,
  70 + :page => params[:npage]
  71 + )
69 72 end
70 73  
71 74 def index
72 75 @article = nil
73   - @subitems = profile.top_level_articles.reject {|item| item.folder? }
74   - @folders = profile.top_level_articles.select {|item| item.folder?}
  76 + @articles = profile.top_level_articles.paginate(
  77 + :order => "case when type = 'Folder' then 0 when type ='Blog' then 1 else 2 end, updated_at DESC",
  78 + :per_page => per_page,
  79 + :page => params[:npage]
  80 + )
75 81 render :action => 'view'
76 82 end
77 83  
... ... @@ -79,17 +85,21 @@ class CmsController &lt; MyProfileController
79 85 @article = profile.articles.find(params[:id])
80 86 @parent_id = params[:parent_id]
81 87 @type = params[:type] || @article.class.to_s
  88 + translations if @article.translatable?
  89 + continue = params[:continue]
82 90  
83 91 refuse_blocks
84   - if !@article.nil? && @article.blog? || !@type.nil? && @type == 'Blog'
85   - @back_url = url_for(:controller => 'profile_editor', :profile => profile.identifier)
86   - end
87   - record_coming_from_public_view
  92 + record_coming
88 93 if request.post?
89 94 @article.last_changed_by = user
90 95 if @article.update_attributes(params[:article])
91   - redirect_back
92   - return
  96 + if !continue
  97 + if @article.content_type.nil? || @article.image?
  98 + redirect_to @article.view_url
  99 + else
  100 + redirect_to :action => (@article.parent ? 'view' : 'index'), :id => @article.parent
  101 + end
  102 + end
93 103 end
94 104 end
95 105 end
... ... @@ -99,6 +109,7 @@ class CmsController &lt; MyProfileController
99 109  
100 110 # user must choose an article type first
101 111  
  112 + record_coming
102 113 @type = params[:type]
103 114 if @type.blank?
104 115 @article_types = []
... ... @@ -110,12 +121,9 @@ class CmsController &lt; MyProfileController
110 121 })
111 122 end
112 123 @parent_id = params[:parent_id]
113   - render :action => 'select_article_type', :layout => false
  124 + render :action => 'select_article_type', :layout => false, :back_to => @back_to
114 125 return
115 126 else
116   - if @type == 'Blog'
117   - @back_url = url_for(:controller => 'profile_editor', :profile => profile.identifier)
118   - end
119 127 refuse_blocks
120 128 end
121 129  
... ... @@ -131,13 +139,19 @@ class CmsController &lt; MyProfileController
131 139 @parent_id = parent.id
132 140 end
133 141  
134   - record_creating_from_public_view
  142 + translations if @article.translatable?
135 143  
136 144 @article.profile = profile
137 145 @article.last_changed_by = user
  146 +
  147 + continue = params[:continue]
138 148 if request.post?
139 149 if @article.save
140   - redirect_back
  150 + if continue
  151 + redirect_to :action => 'edit', :id => @article
  152 + else
  153 + redirect_to @article.view_url
  154 + end
141 155 return
142 156 end
143 157 end
... ... @@ -150,7 +164,7 @@ class CmsController &lt; MyProfileController
150 164 @article = profile.articles.find(params[:id])
151 165 profile.home_page = @article
152 166 profile.save(false)
153   - flash[:notice] = _('"%s" configured as home page.') % @article.name
  167 + session[:notice] = _('"%s" configured as home page.') % @article.name
154 168 redirect_to :action => 'view', :id => @article.id
155 169 end
156 170  
... ... @@ -159,29 +173,36 @@ class CmsController &lt; MyProfileController
159 173 @article = @parent = check_parent(params[:parent_id])
160 174 @target = @parent ? ('/%s/%s' % [profile.identifier, @parent.full_name]) : '/%s' % profile.identifier
161 175 @folders = Folder.find(:all, :conditions => { :profile_id => profile })
162   - record_coming_from_public_view if @article
  176 + @media_listing = params[:media_listing]
  177 + if @article && !@media_listing
  178 + record_coming
  179 + end
163 180 if request.post? && params[:uploaded_files]
164 181 params[:uploaded_files].each do |file|
165 182 @uploaded_files << UploadedFile.create(:uploaded_data => file, :profile => profile, :parent => @parent) unless file == ''
166 183 end
167 184 @errors = @uploaded_files.select { |f| f.errors.any? }
168   - @back_to = params[:back_to]
169 185 if @errors.any?
170   - if @back_to && @back_to == 'media_listing'
  186 + if @media_listing
171 187 flash[:notice] = _('Could not upload all files')
172   - redirect_back
  188 + redirect_to :action => 'media_listing'
173 189 else
174 190 render :action => 'upload_files', :parent_id => @parent_id
175 191 end
176 192 else
177   - if params[:back_to]
178   - redirect_back
  193 + if @media_listing
  194 + flash[:notice] = _('All files were uploaded successfully')
  195 + redirect_to :action => 'media_listing'
179 196 else
180   - redirect_to( if @parent
181   - {:action => 'view', :id => @parent.id}
  197 + if @back_to
  198 + redirect_to @back_to
182 199 else
183   - {:action => 'index'}
184   - end)
  200 + redirect_to( if @parent
  201 + {:action => 'view', :id => @parent.id}
  202 + else
  203 + {:action => 'index'}
  204 + end)
  205 + end
185 206 end
186 207 end
187 208 end
... ... @@ -212,11 +233,11 @@ class CmsController &lt; MyProfileController
212 233  
213 234 def publish
214 235 @article = profile.articles.find(params[:id])
215   - record_coming_from_public_view
  236 + record_coming
216 237 @groups = profile.memberships - [profile]
217 238 @marked_groups = []
218 239 groups_ids = profile.memberships.map{|m|m.id.to_s}
219   - @marked_groups = params[:marked_groups].map do |item|
  240 + @marked_groups = params[:marked_groups].map do |key, item|
220 241 if groups_ids.include?(item[:group_id])
221 242 item.merge :group => Profile.find(item.delete(:group_id))
222 243 end
... ... @@ -232,8 +253,47 @@ class CmsController &lt; MyProfileController
232 253 end
233 254 end
234 255 if @failed.blank?
235   - flash[:notice] = _("Your publish request was sent successfully")
236   - redirect_back
  256 + session[:notice] = _("Your publish request was sent successfully")
  257 + if @back_to
  258 + redirect_to @back_to
  259 + else
  260 + redirect_to @article.view_url
  261 + end
  262 + end
  263 + end
  264 + end
  265 +
  266 + def publish_on_portal_community
  267 + @article = profile.articles.find(params[:id])
  268 + if request.post?
  269 + if environment.portal_community
  270 + task = ApproveArticle.create!(:article => @article, :name => params[:name], :target => environment.portal_community, :requestor => user)
  271 + begin
  272 + task.finish unless environment.portal_community.moderated_articles?
  273 + flash[:notice] = _("Your publish request was sent successfully")
  274 + rescue
  275 + flash[:error] = _("Your publish request couldn't be sent.")
  276 + end
  277 + else
  278 + flash[:notice] = _("There is no portal community to publish your article.")
  279 + end
  280 +
  281 + if @back_to
  282 + redirect_to @back_to
  283 + else
  284 + redirect_to @article.view_url
  285 + end
  286 + end
  287 + end
  288 +
  289 + def suggest_an_article
  290 + @back_to = params[:back_to] || request.referer || url_for(profile.public_profile_url)
  291 + @task = SuggestArticle.new(params[:task])
  292 + if request.post?
  293 + @task.target = profile
  294 + if @task.save
  295 + session[:notice] = _('Thanks for your suggestion. The community administrators were notified.')
  296 + redirect_to @back_to
237 297 end
238 298 end
239 299 end
... ... @@ -241,24 +301,24 @@ class CmsController &lt; MyProfileController
241 301 def media_listing
242 302 if params[:image_folder_id]
243 303 folder = profile.articles.find(params[:image_folder_id]) if !params[:image_folder_id].blank?
244   - @images = (folder ? folder.children : UploadedFile.find(:all, :order => 'created_at desc', :conditions => ["profile_id = ? AND parent_id is NULL", profile ])).select { |c| c.image? }
  304 + @images = (folder ? folder.children : profile.top_level_articles).images
245 305 elsif params[:document_folder_id]
246 306 folder = profile.articles.find(params[:document_folder_id]) if !params[:document_folder_id].blank?
247   - @documents = (folder ? folder.children : UploadedFile.find(:all, :order => 'created_at desc', :conditions => ["profile_id = ? AND parent_id is NULL", profile ])).select { |c| c.kind_of?(UploadedFile) && !c.image? }
  307 + @documents = (folder ? folder.children : profile.top_level_articles)
248 308 else
249   - @documents = UploadedFile.find(:all, :order => 'created_at desc', :conditions => ["profile_id = ? AND parent_id is NULL", profile ])
250   - @images = @documents.select(&:image?)
  309 + @documents = profile.articles
  310 + @images = @documents.images
251 311 @documents -= @images
252 312 end
253 313  
254   - @images = @images.paginate(:per_page => per_page, :page => params[:ipage]) if @images
255   - @documents = @documents.paginate(:per_page => per_page, :page => params[:dpage]) if @documents
  314 + @images = @images.paginate(:per_page => per_page, :page => params[:ipage], :order => "updated_at desc") if @images
  315 + @documents = @documents.paginate(:per_page => per_page, :page => params[:dpage], :order => "updated_at desc", :conditions => {:is_image => false}) if @documents
256 316  
257   - @folders = Folder.find(:all, :conditions => { :profile_id => profile })
  317 + @folders = profile.folders
258 318 @image_folders = @folders.select {|f| f.children.any? {|c| c.image?} }
259 319 @document_folders = @folders.select {|f| f.children.any? {|c| !c.image? && c.kind_of?(UploadedFile) } }
260 320  
261   - @back_to = 'media_listing'
  321 + @media_listing = true
262 322  
263 323 respond_to do |format|
264 324 format.html { render :layout => false}
... ... @@ -273,42 +333,11 @@ class CmsController &lt; MyProfileController
273 333  
274 334 protected
275 335  
276   - def redirect_back
277   - if params[:back_to] == 'control_panel'
278   - redirect_to :controller => 'profile_editor', :profile => @profile.identifier
279   - elsif params[:back_to] == 'public_view'
280   - redirect_to @article.view_url.merge(Noosfero.url_options)
281   - elsif params[:back_to] == 'media_listing'
282   - redirect_to :action => 'media_listing'
283   - elsif @article.parent
284   - redirect_to :action => 'view', :id => @article.parent
285   - elsif @article.folder? && !@article.blog? && @article.parent
286   - redirect_to :action => 'index'
  336 + def record_coming
  337 + if request.post?
  338 + @back_to = params[:back_to]
287 339 else
288   - redirect_back_or_default :action => 'index'
289   - end
290   - end
291   -
292   - def record_coming_from_public_view
293   - referer = request.referer
294   - referer.gsub!(/\?.*/, '') unless referer.nil?
295   - if (maybe_ssl(url_for(@article.url)).include?(referer)) || (@article == profile.home_page && maybe_ssl(url_for(profile.url)).include?(referer))
296   - @back_to = 'public_view'
297   - @back_url = @article.view_url
298   - end
299   - if !request.post? and @article.blog?
300   - store_location(request.referer)
301   - end
302   - end
303   -
304   - def record_creating_from_public_view
305   - referer = request.referer
306   - if (referer =~ Regexp.new("^#{(url_for(profile.url).sub('https:', 'https?:'))}")) || params[:back_to] == 'public_view'
307   - @back_to = 'public_view'
308   - @back_url = referer
309   - end
310   - if !request.post? and @article.blog?
311   - store_location(request.referer)
  340 + @back_to = params[:back_to] || request.referer
312 341 end
313 342 end
314 343  
... ... @@ -341,5 +370,11 @@ class CmsController &lt; MyProfileController
341 370 def per_page
342 371 10
343 372 end
  373 +
  374 + def translations
  375 + @locales = Noosfero.locales.invert.reject { |name, lang| !@article.possible_translations.include?(lang) }
  376 + @selected_locale = @article.language || FastGettext.locale
  377 + end
  378 +
344 379 end
345 380  
... ...
app/controllers/my_profile/friends_controller.rb
... ... @@ -8,18 +8,6 @@ class FriendsController &lt; MyProfileController
8 8 end
9 9 end
10 10  
11   - def add
12   - @friend = Person.find(params[:id])
13   - if request.post? && params[:confirmation]
14   - # FIXME this shouldn't be in Person model?
15   - AddFriend.create!(:person => profile, :friend => @friend, :group_for_person => params[:group])
16   -
17   - flash[:notice] = _('%s still needs to accept being your friend.') % @friend.name
18   - # FIXME shouldn't redirect to the friend's page?
19   - redirect_to :action => 'index'
20   - end
21   - end
22   -
23 11 def remove
24 12 @friend = profile.friends.find(params[:id])
25 13 if request.post? && params[:confirmation]
... ...
app/controllers/my_profile/mailconf_controller.rb
... ... @@ -20,20 +20,20 @@ class MailconfController &lt; MyProfileController
20 20 @task = EmailActivation.new(:target => environment, :requestor => profile)
21 21 begin
22 22 @task.save!
23   - flash[:notice] = _('Please fill your personal information below in order to get your mailbox approved by one of the administrators')
  23 + session[:notice] = _('Please fill your personal information below in order to get your mailbox approved by one of the administrators')
24 24 redirect_to :controller => 'profile_editor', :action => 'edit'
25 25 rescue Exception => ex
26   - flash[:notice] = _('e-Mail was not enabled successfully.')
  26 + session[:notice] = _('e-Mail was not enabled successfully.')
27 27 render :action => 'index'
28 28 end
29 29 end
30 30 post_only :disable
31 31 def disable
32 32 if profile.user.disable_email!
33   - flash[:notice] = _('e-Mail disabled successfully.')
  33 + session[:notice] = _('e-Mail disabled successfully.')
34 34 redirect_to :controller => 'profile_editor'
35 35 else
36   - flash[:notice] = _('e-Mail was not disabled successfully.')
  36 + session[:notice] = _('e-Mail was not disabled successfully.')
37 37 redirect_to :action => 'index'
38 38 end
39 39 end
... ...
app/controllers/my_profile/manage_products_controller.rb
1 1 class ManageProductsController < ApplicationController
2 2 needs_profile
3 3  
4   - protect 'manage_products', :profile
  4 + protect 'manage_products', :profile, :except => [:show]
5 5 before_filter :check_environment_feature
  6 + before_filter :login_required, :except => [:show]
6 7  
7 8 protected
  9 +
8 10 def check_environment_feature
9 11 if profile.environment.enabled?('disable_products_for_enterprises')
10 12 render_not_found
... ... @@ -13,104 +15,147 @@ class ManageProductsController &lt; ApplicationController
13 15 end
14 16  
15 17 public
  18 +
16 19 def index
17   - @products = @profile.products
18   - @consumptions = @profile.consumptions
  20 + @products = @profile.products.paginate(:per_page => 10, :page => params[:page])
19 21 end
20 22  
21 23 def show
22 24 @product = @profile.products.find(params[:id])
  25 + @inputs = @product.inputs
  26 + @allowed_user = user && user.has_permission?('manage_products', profile)
  27 + end
  28 +
  29 + def categories_for_selection
  30 + @category = Category.find(params[:category_id]) if params[:category_id]
  31 + @object_name = params[:object_name]
  32 + if @category
  33 + @categories = @category.children
  34 + @level = @category.leaf? ? @category.level : @categories.first.level
  35 + else
  36 + @categories = ProductCategory.top_level_for(environment)
  37 + @level = 0
  38 + end
  39 + render :partial => 'categories_for_selection', :locals => { :categories => @categories, :level => @level }
23 40 end
24 41  
25 42 def new
26   - @object = Product.new
27   - @categories = @current_category.nil? ? ProductCategory.top_level_for(environment) : @current_category.children
28   - @product = @profile.products.build(params[:product])
  43 + @product = @profile.products.build(:product_category_id => params[:selected_category_id])
  44 + @category = @product.product_category
  45 + @categories = ProductCategory.top_level_for(environment)
  46 + @level = 0
29 47 if request.post?
30 48 if @product.save
31   - flash[:notice] = _('Product succesfully created')
32   - redirect_to :action => 'show', :id => @product
  49 + session[:notice] = _('Product succesfully created')
  50 + render :partial => 'shared/redirect_via_javascript',
  51 + :locals => { :url => url_for(:controller => 'manage_products', :action => 'show', :id => @product) }
33 52 else
34   - flash[:notice] = _('Could not create the product')
  53 + render :partial => 'shared/dialog_error_messages', :locals => { :object_name => 'product' }
35 54 end
36 55 end
37 56 end
38 57  
39 58 def edit
40 59 @product = @profile.products.find(params[:id])
41   - @object = @profile.products.find(params[:id])
42   - @current_category = @product.product_category
43   - @categories = @current_category.nil? ? [] : @current_category.children
  60 + field = params[:field]
  61 + if request.post?
  62 + begin
  63 + @product.update_attributes!(params[:product])
  64 + render :partial => "display_#{field}", :locals => {:product => @product}
  65 + rescue Exception => e
  66 + render :partial => "edit_#{field}", :locals => {:product => @product, :errors => true}
  67 + end
  68 + else
  69 + render :partial => "edit_#{field}", :locals => {:product => @product, :errors => false}
  70 + end
  71 + end
  72 +
  73 + def edit_category
  74 + @product = @profile.products.find(params[:id])
  75 + @category = @product.product_category
  76 + @categories = ProductCategory.top_level_for(environment)
  77 + @edit = true
  78 + @level = @category.level
  79 + if request.post?
  80 + if @product.update_attributes(:product_category_id => params[:selected_category_id])
  81 + render :partial => 'shared/redirect_via_javascript',
  82 + :locals => { :url => url_for(:controller => 'manage_products', :action => 'show', :id => @product) }
  83 + else
  84 + render :partial => 'shared/dialog_error_messages', :locals => { :object_name => 'product' }
  85 + end
  86 + end
  87 + end
  88 +
  89 + def add_input
  90 + @product = @profile.products.find(params[:id])
  91 + @input = @product.inputs.build
  92 + @categories = ProductCategory.top_level_for(environment)
  93 + @level = 0
44 94 if request.post?
45   - if @product.update_attributes(params[:product])
46   - flash[:notice] = _('Product succesfully updated')
47   - redirect_back_or_default :action => 'show', :id => @product
  95 + if @input.update_attributes(:product_category_id => params[:selected_category_id])
  96 + @inputs = @product.inputs
  97 + render :partial => 'display_inputs'
48 98 else
49   - flash[:notice] = _('Could not update the product')
  99 + render :partial => 'shared/dialog_error_messages', :locals => { :object_name => 'product' }
50 100 end
  101 + else
  102 + render :partial => 'add_input'
51 103 end
52 104 end
53 105  
54 106 def destroy
55 107 @product = @profile.products.find(params[:id])
56 108 if @product.destroy
57   - flash[:notice] = _('Product succesfully removed')
  109 + session[:notice] = _('Product succesfully removed')
58 110 redirect_back_or_default :action => 'index'
59 111 else
60   - flash[:notice] = _('Could not remove the product')
  112 + session[:notice] = _('Could not remove the product')
61 113 redirect_back_or_default :action => 'show', :id => @product
62 114 end
63 115 end
64 116  
65   - def update_categories
66   - @object = params[:id] ? @profile.products.find(params[:id]) : Product.new
67   - if params[:category_id]
68   - @current_category = Category.find(params[:category_id])
69   - @categories = @current_category.children
70   - else
71   - @current_category = ProductCategory.top_level_for(environment).first
72   - @categories = @current_category.nil? ? [] : @current_category.children
  117 + def edit_input
  118 + if request.xhr?
  119 + @input = @profile.inputs.find_by_id(params[:id])
  120 + if @input
  121 + if request.post?
  122 + if @input.update_attributes(params[:input])
  123 + render :partial => 'display_input', :locals => {:input => @input}
  124 + else
  125 + render :partial => 'edit_input'
  126 + end
  127 + else
  128 + render :partial => 'edit_input'
  129 + end
  130 + else
  131 + render :text => _('The input was not found')
  132 + end
73 133 end
74   - render :partial => 'shared/select_categories', :locals => {:object_name => 'product', :multiple => false}, :layout => false
75 134 end
76 135  
77   - def update_subcategories
78   - @current_category = ProductCategory.find(params[:id]) if params[:id]
79   - @categories = @current_category ? @current_category.children : ProductCategory.top_level_for(environment)
80   - render :partial => 'subcategories'
  136 + def order_inputs
  137 + @product = @profile.products.find(params[:id])
  138 + @product.order_inputs!(params[:input]) if params[:input]
  139 + render :nothing => true
81 140 end
82   -
83   - def new_consumption
84   - @consumption = @profile.consumptions.build(params[:consumption])
  141 +
  142 + def remove_input
  143 + @input = @profile.inputs.find(params[:id])
  144 + @product = @input.product
85 145 if request.post?
86   - if @consumption.save
87   - flash[:notice] = _('Raw material succesfully created')
88   - redirect_to :action => 'index'
  146 + if @input.destroy
  147 + @inputs = @product.inputs
  148 + render :partial => 'display_inputs'
89 149 else
90   - flash[:notice] = _('Could not create the raw material')
  150 + render :partial => 'shared/dialog_error_messages', :locals => { :object_name => 'input' }
91 151 end
92 152 end
93 153 end
94 154  
95   - def destroy_consumption
96   - @consumption = @profile.consumptions.find(params[:id])
97   - if @consumption.destroy
98   - flash[:notice] = _('Raw material succesfully removed')
99   - else
100   - flash[:notice] = _('Could not remove the raw material')
101   - end
102   - redirect_back_or_default :action => 'index'
103   - end
104   -
105   - def edit_consumption
106   - @consumption = @profile.consumptions.find(params[:id])
107   - if request.post?
108   - if @consumption.update_attributes(params[:consumption])
109   - flash[:notice] = _('Raw material succesfully updated')
110   - redirect_back_or_default :action => 'index'
111   - else
112   - flash[:notice] = _('Could not update the raw material')
113   - end
  155 + def certifiers_for_selection
  156 + @qualifier = Qualifier.exists?(params[:id]) ? Qualifier.find(params[:id]) : nil
  157 + render :update do |page|
  158 + page.replace_html params[:certifier_area], :partial => 'certifiers_for_selection'
114 159 end
115 160 end
116 161  
... ...
app/controllers/my_profile/maps_controller.rb
... ... @@ -8,7 +8,7 @@ class MapsController &lt; MyProfileController
8 8 begin
9 9 Profile.transaction do
10 10 if profile.update_attributes!(params[:profile_data])
11   - flash[:notice] = _('Address was updated successfully!')
  11 + session[:notice] = _('Address was updated successfully!')
12 12 redirect_to :action => 'edit_location'
13 13 end
14 14 end
... ...
app/controllers/my_profile/memberships_controller.rb
... ... @@ -7,32 +7,12 @@ class MembershipsController &lt; MyProfileController
7 7 end
8 8  
9 9 def new_community
10   - @wizard = params[:wizard].blank? ? false : params[:wizard]
11 10 @community = Community.new(params[:community])
12 11 @community.environment = environment
13 12 if request.post? && @community.valid?
14 13 @community = Community.create_after_moderation(user, {:environment => environment}.merge(params[:community]))
15   - if @wizard
16   - redirect_to :controller => 'search', :action => 'assets', :asset => 'communities', :wizard => true
17   - return
18   - else
19   - redirect_to :action => 'index'
20   - return
21   - end
22   - end
23   - if @wizard
24   - render :layout => 'wizard'
  14 + redirect_to :action => 'index'
  15 + return
25 16 end
26 17 end
27   -
28   - def destroy_community
29   - @community = Community.find(params[:id])
30   - if request.post?
31   - if @community.destroy
32   - flash[:notice] = _('%s was removed.') % @community.short_name
33   - redirect_to :action => 'index'
34   - end
35   - end
36   - end
37   -
38 18 end
... ...
app/controllers/my_profile/profile_design_controller.rb
... ... @@ -5,7 +5,7 @@ class ProfileDesignController &lt; BoxOrganizerController
5 5 protect 'edit_profile_design', :profile
6 6  
7 7 def available_blocks
8   - blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock ]
  8 + blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock ]
9 9  
10 10 # blocks exclusive for organizations
11 11 if profile.has_members?
... ... @@ -24,6 +24,7 @@ class ProfileDesignController &lt; BoxOrganizerController
24 24 if profile.enterprise?
25 25 blocks << DisabledEnterpriseMessageBlock
26 26 blocks << HighlightsBlock
  27 + blocks << FeaturedProductsBlock
27 28 end
28 29  
29 30 # product block exclusive for enterprises in environments that permits it
... ...
app/controllers/my_profile/profile_editor_controller.rb
1 1 class ProfileEditorController < MyProfileController
2 2  
3   - protect 'edit_profile', :profile
  3 + protect 'edit_profile', :profile, :except => [:destroy_profile]
  4 + protect 'destroy_profile', :profile, :only => [:destroy_profile]
4 5  
5 6 def index
6 7 @pending_tasks = profile.all_pending_tasks.select{|i| user.has_permission?(i.permission, profile)}
... ... @@ -25,7 +26,7 @@ class ProfileEditorController &lt; MyProfileController
25 26 if profile.identifier.blank?
26 27 profile.identifier = params[:profile]
27 28 end
28   - flash[:notice] = _('Cannot update profile')
  29 + session[:notice] = _('Cannot update profile')
29 30 end
30 31 end
31 32 end
... ... @@ -34,7 +35,7 @@ class ProfileEditorController &lt; MyProfileController
34 35 @to_enable = profile
35 36 if request.post? && params[:confirmation]
36 37 unless @to_enable.update_attribute('enabled', true)
37   - flash[:notice] = _('%s was not enabled.') % @to_enable.name
  38 + session[:notice] = _('%s was not enabled.') % @to_enable.name
38 39 end
39 40 redirect_to :action => 'index'
40 41 end
... ... @@ -44,7 +45,7 @@ class ProfileEditorController &lt; MyProfileController
44 45 @to_disable = profile
45 46 if request.post? && params[:confirmation]
46 47 unless @to_disable.update_attribute('enabled', false)
47   - flash[:notice] = _('%s was not disabled.') % @to_disable.name
  48 + session[:notice] = _('%s was not disabled.') % @to_disable.name
48 49 end
49 50 redirect_to :action => 'index'
50 51 end
... ... @@ -72,4 +73,14 @@ class ProfileEditorController &lt; MyProfileController
72 73 end
73 74 end
74 75  
  76 + def destroy_profile
  77 + if request.post?
  78 + if @profile.destroy
  79 + session[:notice] = _('The profile was deleted.')
  80 + redirect_to :controller => 'home'
  81 + else
  82 + session[:notice] = _('Could not delete profile')
  83 + end
  84 + end
  85 + end
75 86 end
... ...
app/controllers/my_profile/profile_members_controller.rb
... ... @@ -10,23 +10,34 @@ class ProfileMembersController &lt; MyProfileController
10 10 def update_roles
11 11 @roles = params[:roles] ? environment.roles.find(params[:roles].select{|r|!r.to_i.zero?}) : []
12 12 @roles = @roles.select{|r| r.has_kind?('Profile') }
13   - @person = profile.members.find { |m| m.id == params[:person].to_i }
14   - if @person && @person.define_roles(@roles, profile)
15   - flash[:notice] = _('Roles successfuly updated')
  13 + begin
  14 + @person = profile.members.find(params[:person])
  15 + rescue ActiveRecord::RecordNotFound
  16 + @person = nil
  17 + end
  18 + if !params[:confirmation] && @person && @person.is_last_admin_leaving?(profile, @roles)
  19 + redirect_to :action => :last_admin, :roles => params[:roles], :person => @person
16 20 else
17   - flash[:notice] = _('Couldn\'t change the roles')
  21 + if @person && @person.define_roles(@roles, profile)
  22 + session[:notice] = _('Roles successfuly updated')
  23 + else
  24 + session[:notice] = _('Couldn\'t change the roles')
  25 + end
  26 + if params[:confirmation]
  27 + redirect_to profile.url
  28 + else
  29 + redirect_to :action => :index
  30 + end
18 31 end
19   - redirect_to :action => :index
20 32 end
21 33  
22   - def change_role
23   - @roles = Profile::Roles.organization_member_roles(environment.id)
24   - @member = profile.members.find { |m| m.id == params[:id].to_i }
25   - if @member
26   - @associations = @member.find_roles(@profile)
27   - else
28   - redirect_to :action => :index
29   - end
  34 + def last_admin
  35 + @person = params[:person]
  36 + @roles = params[:roles] || []
  37 + @members = profile.members.select {|member| !profile.admins.include?(member)}
  38 + @title = _('Current admins')
  39 + @collection = :profile_admins
  40 + @remove_action = {:action => 'remove_admin'}
30 41 end
31 42  
32 43 def add_role
... ... @@ -44,21 +55,35 @@ class ProfileMembersController &lt; MyProfileController
44 55 def remove_role
45 56 @association = RoleAssignment.find(:all, :conditions => {:id => params[:id], :target_id => profile.id})
46 57 if @association.destroy
47   - flash[:notice] = 'Member succefully unassociated'
  58 + session[:notice] = 'Member succefully unassociated'
48 59 else
49   - flash[:notice] = 'Failed to unassociate member'
  60 + session[:notice] = 'Failed to unassociate member'
50 61 end
51 62 render :layout => false
52 63 end
53 64  
  65 + def change_role
  66 + @roles = Profile::Roles.organization_member_roles(environment.id)
  67 + begin
  68 + @member = profile.members.find(params[:id])
  69 + rescue ActiveRecord::RecordNotFound
  70 + @member = nil
  71 + end
  72 + if @member
  73 + @associations = @member.find_roles(@profile)
  74 + else
  75 + redirect_to :action => :index
  76 + end
  77 + end
  78 +
54 79 def unassociate
55 80 member = Person.find(params[:id])
56 81 associations = member.find_roles(profile)
57 82 RoleAssignment.transaction do
58 83 if associations.map(&:destroy)
59   - flash[:notice] = 'Member succefully unassociated'
  84 + session[:notice] = _('Member succesfully unassociated')
60 85 else
61   - flash[:notice] = 'Failed to unassociate member'
  86 + session[:notice] = _('Failed to unassociate member')
62 87 end
63 88 end
64 89 render :layout => false
... ... @@ -69,19 +94,61 @@ class ProfileMembersController &lt; MyProfileController
69 94  
70 95 def add_member
71 96 if profile.enterprise?
72   - member = Person.find_by_identifier(params[:id])
  97 + member = Person.find(params[:id])
73 98 member.define_roles(Profile::Roles.all_roles(environment), profile)
74 99 end
75 100 render :layout => false
76 101 end
77 102  
  103 + def add_admin
  104 + @title = _('Current admins')
  105 + @collection = :profile_admins
  106 +
  107 + if profile.community?
  108 + member = profile.members.find_by_identifier(params[:id])
  109 + profile.add_admin(member)
  110 + end
  111 + render :layout => false
  112 + end
  113 +
  114 + def remove_admin
  115 + @title = _('Current admins')
  116 + @collection = :profile_admins
  117 +
  118 + if profile.community?
  119 + member = profile.members.find_by_identifier(params[:id])
  120 + profile.remove_admin(member)
  121 + end
  122 + render :layout => false
  123 + end
  124 +
78 125 def find_users
79 126 if !params[:query] || params[:query].length <= 2
80 127 @users_found = []
81   - else
82   - @users_found = Person.find_by_contents(params[:query] + '*')
  128 + elsif params[:scope] == 'all_users'
  129 + @users_found = Person.find_by_contents(params[:query] + '*').select {|user| !profile.members.include?(user)}
  130 + @button_alt = _('Add member')
  131 + @add_action = {:action => 'add_member'}
  132 + elsif params[:scope] == 'new_admins'
  133 + @users_found = Person.find_by_contents(params[:query] + '*').select {|user| profile.members.include?(user) && !profile.admins.include?(user)}
  134 + @button_alt = _('Add member')
  135 + @add_action = {:action => 'add_admin'}
83 136 end
84 137 render :layout => false
85 138 end
86 139  
  140 + def send_mail
  141 + @mailing = profile.mailings.build(params[:mailing])
  142 + if request.post?
  143 + @mailing.locale = locale
  144 + @mailing.person = user
  145 + if @mailing.save
  146 + session[:notice] = _('The e-mails are being sent')
  147 + redirect_to :action => 'index'
  148 + else
  149 + session[:notice] = _('Could not create the e-mail')
  150 + end
  151 + end
  152 + end
  153 +
87 154 end
... ...
app/controllers/my_profile/tasks_controller.rb
... ... @@ -4,26 +4,38 @@ class TasksController &lt; MyProfileController
4 4  
5 5 def index
6 6 @tasks = profile.all_pending_tasks.sort_by(&:created_at)
  7 + @failed = params ? params[:failed] : {}
7 8 end
8 9  
9 10 def processed
10 11 @tasks = profile.all_finished_tasks.sort_by(&:created_at)
11 12 end
12 13  
13   - VALID_DECISIONS = [ 'finish', 'cancel' ]
  14 + VALID_DECISIONS = [ 'finish', 'cancel', 'skip' ]
14 15  
15 16 def close
16   - decision = params[:decision]
17   - if request.post? && VALID_DECISIONS.include?(decision) && params[:id]
18   - task = profile.find_in_all_tasks(params[:id])
19   - task.update_attributes!(params[:task])
20   - begin
21   - task.send(decision)
22   - rescue Exception => ex
23   - flash[:notice] = ex.clean_message
  17 + failed = {}
  18 +
  19 + params[:tasks].each do |id, value|
  20 + decision = value[:decision]
  21 + if request.post? && VALID_DECISIONS.include?(decision) && id && decision != 'skip'
  22 + task = profile.find_in_all_tasks(id)
  23 + task.update_attributes!(value[:task])
  24 + begin
  25 + task.send(decision)
  26 + rescue Exception => ex
  27 + message = "#{task.title} (#{task.requestor ? task.requestor.name : task.author_name})"
  28 + failed[ex.clean_message] ? failed[ex.clean_message] << message : failed[ex.clean_message] = [message]
  29 + end
24 30 end
25 31 end
26   - redirect_to :action => 'index'
  32 +
  33 + if failed.blank?
  34 + session[:notice] = _("All decisions were applied successfully.")
  35 + else
  36 + session[:notice] = _("Some decisions couldn't be applied.")
  37 + end
  38 + redirect_to params.merge!(:action => 'index', :failed => failed)
27 39 end
28 40  
29 41 def new
... ...
app/controllers/my_profile/themes_controller.rb
... ... @@ -8,6 +8,11 @@ class ThemesController &lt; MyProfileController
8 8 redirect_to :action => 'index'
9 9 end
10 10  
  11 + def unset
  12 + profile.update_theme(nil)
  13 + redirect_to :action => 'index'
  14 + end
  15 +
11 16 def index
12 17 @themes = profile.environment.themes + Theme.approved_themes(profile)
13 18 @current_theme = profile.theme
... ... @@ -18,7 +23,7 @@ class ThemesController &lt; MyProfileController
18 23  
19 24 def new
20 25 if !request.xhr?
21   - id = params[:name].to_slug
  26 + id = params[:name] ? params[:name].to_slug : 'my-theme'
22 27 t = Theme.new(id, :name => params[:name], :owner => profile, :public => false)
23 28 t.save
24 29 redirect_to :action => 'index'
... ...
app/controllers/public/account_controller.rb
1 1 class AccountController < ApplicationController
2 2  
  3 + no_design_blocks
  4 +
3 5 inverse_captcha :field => 'e_mail'
4 6  
5   - require_ssl :except => [ :login_popup, :logout_popup, :wizard, :profile_details ]
  7 + require_ssl :except => [ :login_popup, :logout_popup, :profile_details ]
6 8  
7 9 before_filter :login_required, :only => [:activation_question, :accept_terms, :activate_enterprise]
8 10 before_filter :redirect_if_logged_in, :only => [:login, :signup]
... ... @@ -28,10 +30,10 @@ class AccountController &lt; ApplicationController
28 30 end
29 31 if redirect?
30 32 go_to_initial_page
31   - flash[:notice] = _("Logged in successfully")
  33 + session[:notice] = _("Logged in successfully")
32 34 end
33 35 else
34   - flash[:notice] = _('Incorrect username or password') if redirect?
  36 + session[:notice] = _('Incorrect username or password') if redirect?
35 37 redirect_to :back if redirect?
36 38 end
37 39 end
... ... @@ -48,8 +50,6 @@ class AccountController &lt; ApplicationController
48 50 # action to register an user to the application
49 51 def signup
50 52 @invitation_code = params[:invitation_code]
51   - @wizard = params[:wizard].blank? ? false : params[:wizard]
52   - @step = 1
53 53 begin
54 54 @user = User.new(params[:user])
55 55 @user.terms_of_use = environment.terms_of_use
... ... @@ -68,38 +68,17 @@ class AccountController &lt; ApplicationController
68 68 invitation.update_attributes!({:friend => @user.person})
69 69 invitation.finish
70 70 end
71   - flash[:notice] = _("Thanks for signing up!")
72   - if @wizard
73   - redirect_to :controller => 'search', :action => 'assets', :asset => 'communities', :wizard => true
74   - return
75   - else
76   - go_to_initial_page if redirect?
77   - end
  71 + session[:notice] = _("Thanks for signing up!")
  72 + go_to_initial_page if redirect?
78 73 end
79   - if @wizard
80   - render :layout => 'wizard'
81   - end
82 74 rescue ActiveRecord::RecordInvalid
83 75 @person.valid?
84 76 @person.errors.delete(:identifier)
85 77 @person.errors.delete(:user_id)
86   - if @wizard
87   - render :action => 'signup', :layout => 'wizard'
88   - else
89   - render :action => 'signup'
90   - end
  78 + render :action => 'signup'
91 79 end
92 80 end
93 81  
94   - def wizard
95   - render :layout => false
96   - end
97   -
98   - def profile_details
99   - @profile = Profile.find_by_identifier(params[:profile])
100   - render :partial => 'profile_details', :layout => 'wizard'
101   - end
102   -
103 82 # action to perform logout from the application
104 83 def logout
105 84 if logged_in?
... ... @@ -107,7 +86,7 @@ class AccountController &lt; ApplicationController
107 86 end
108 87 cookies.delete :auth_token
109 88 reset_session
110   - flash[:notice] = _("You have been logged out.")
  89 + session[:notice] = _("You have been logged out.")
111 90 redirect_to :controller => 'home', :action => 'index'
112 91 end
113 92  
... ... @@ -118,10 +97,10 @@ class AccountController &lt; ApplicationController
118 97 @user.change_password!(params[:current_password],
119 98 params[:new_password],
120 99 params[:new_password_confirmation])
121   - flash[:notice] = _('Your password has been changed successfully!')
  100 + session[:notice] = _('Your password has been changed successfully!')
122 101 redirect_to :action => 'index'
123 102 rescue User::IncorrectPassword => e
124   - flash[:notice] = _('The supplied current password is incorrect.')
  103 + session[:notice] = _('The supplied current password is incorrect.')
125 104 render :action => 'change_password'
126 105 end
127 106 else
... ... @@ -233,6 +212,21 @@ class AccountController &lt; ApplicationController
233 212 render :partial => 'identifier_status'
234 213 end
235 214  
  215 + def user_data
  216 + user_data =
  217 + if logged_in?
  218 + current_user.data_hash
  219 + else
  220 + { }
  221 + end
  222 + if session[:notice]
  223 + user_data['notice'] = session[:notice]
  224 + session[:notice] = nil # consume the notice
  225 + end
  226 +
  227 + render :text => user_data.to_json, :layout => false, :content_type => "application/javascript"
  228 + end
  229 +
236 230 protected
237 231  
238 232 def redirect?
... ...
app/controllers/public/browse_controller.rb 0 → 100644
... ... @@ -0,0 +1,58 @@
  1 +class BrowseController < PublicController
  2 +
  3 + no_design_blocks
  4 +
  5 + FILTERS = %w(
  6 + more_recent
  7 + more_active
  8 + more_popular
  9 + )
  10 +
  11 + def people
  12 + @filter = filter
  13 + @title = self.filter_description(params[:action] + '_' + @filter )
  14 +
  15 + @results = @environment.people.visible.send(@filter)
  16 +
  17 + if params[:query].blank?
  18 + @results = @results.paginate(:per_page => 27, :page => params[:page])
  19 + else
  20 + @results = @results.find_by_contents(params[:query]).paginate(:per_page => 27, :page => params[:page])
  21 + end
  22 + end
  23 +
  24 + def communities
  25 + @filter = filter
  26 + @title = self.filter_description(params[:action] + '_' + @filter )
  27 +
  28 + @results = @environment.communities.visible.send(@filter)
  29 +
  30 + if params[:query].blank?
  31 + @results = @results.paginate(:per_page => 27, :page => params[:page])
  32 + else
  33 + @results = @results.find_by_contents(params[:query]).paginate(:per_page => 27, :page => params[:page])
  34 + end
  35 + end
  36 +
  37 + protected
  38 +
  39 + def filter
  40 + if FILTERS.include?(params[:filter])
  41 + params[:filter]
  42 + else
  43 + 'more_recent'
  44 + end
  45 + end
  46 +
  47 + def filter_description(str)
  48 + {
  49 + 'people_more_recent' => _('More recent people'),
  50 + 'people_more_active' => _('More active people'),
  51 + 'people_more_popular' => _('More popular people'),
  52 + 'communities_more_recent' => _('More recent communities'),
  53 + 'communities_more_active' => _('More active communities'),
  54 + 'communities_more_popular' => _('More popular communities'),
  55 + }[str] || str
  56 + end
  57 +
  58 +end
... ...
app/controllers/public/catalog_controller.rb
1 1 class CatalogController < PublicController
2 2 needs_profile
  3 +
3 4 before_filter :check_enterprise_and_environment
4 5  
5 6 def index
6   - @products = @profile.products
  7 + @products = @profile.products.paginate(:per_page => 10, :page => params[:page])
7 8 end
8 9  
9   - def show
10   - @product = @profile.products.find(params[:id])
11   - end
12   -
13 10 protected
14 11 def check_enterprise_and_environment
15 12 unless @profile.kind_of?(Enterprise) && !@profile.environment.enabled?('disable_products_for_enterprises')
... ...
app/controllers/public/chat_controller.rb 0 → 100644
... ... @@ -0,0 +1,52 @@
  1 +class ChatController < PublicController
  2 +
  3 + before_filter :login_required
  4 + before_filter :check_environment_feature
  5 +
  6 + def start_session
  7 + login = user.jid
  8 + password = current_user.crypted_password
  9 + begin
  10 + jid, sid, rid = RubyBOSH.initialize_session(login, password, "http://#{environment.default_hostname}/http-bind",
  11 + :wait => 30, :hold => 1, :window => 5)
  12 + session_data = { :jid => jid, :sid => sid, :rid => rid }
  13 + render :text => session_data.to_json, :layout => false, :content_type => 'application/javascript'
  14 + rescue
  15 + render :action => 'start_session_error', :layout => false, :status => 500
  16 + end
  17 + end
  18 +
  19 + def avatar
  20 + profile = environment.profiles.find_by_identifier(params[:id])
  21 + filename, mimetype = profile_icon(profile, :minor, true)
  22 + data = File.read(File.join(RAILS_ROOT, 'public', filename))
  23 + render :text => data, :layout => false, :content_type => mimetype
  24 + expires_in 24.hours
  25 + end
  26 +
  27 + def index
  28 + presence = current_user.last_chat_status
  29 + if presence.blank? or presence == 'chat'
  30 + render :action => 'auto_connect_online'
  31 + else
  32 + render :action => 'auto_connect_busy'
  33 + end
  34 + end
  35 +
  36 + def update_presence_status
  37 + if request.xhr?
  38 + current_user.update_attributes({:chat_status_at => DateTime.now}.merge(params[:status] || {}))
  39 + end
  40 + render :nothing => true
  41 + end
  42 +
  43 + protected
  44 +
  45 + def check_environment_feature
  46 + unless environment.enabled?('xmpp_chat')
  47 + render_not_found
  48 + return
  49 + end
  50 + end
  51 +
  52 +end
... ...
app/controllers/public/contact_controller.rb
... ... @@ -12,10 +12,10 @@ class ContactController &lt; PublicController
12 12 @contact.city = (!params[:city].blank? && City.exists?(params[:city])) ? City.find(params[:city]).name : nil
13 13 @contact.state = (!params[:state].blank? && State.exists?(params[:state])) ? State.find(params[:state]).name : nil
14 14 if @contact.deliver
15   - flash[:notice] = _('Contact successfully sent')
  15 + session[:notice] = _('Contact successfully sent')
16 16 redirect_to :action => 'new'
17 17 else
18   - flash[:notice] = _('Contact not sent')
  18 + session[:notice] = _('Contact not sent')
19 19 end
20 20 else
21 21 @contact = user.build_contact(profile)
... ...
app/controllers/public/content_viewer_controller.rb
... ... @@ -51,6 +51,8 @@ class ContentViewerController &lt; ApplicationController
51 51 return
52 52 end
53 53  
  54 + redirect_to_translation
  55 +
54 56 # At this point the page will be showed
55 57 @page.hit
56 58  
... ... @@ -67,9 +69,6 @@ class ContentViewerController &lt; ApplicationController
67 69 return
68 70 end
69 71  
70   - # store location if the page is not a download
71   - store_location
72   -
73 72 @form_div = params[:form]
74 73  
75 74 if request.post? && params[:comment] && params[self.icaptcha_field].blank? && params[:confirm] == 'true' && @page.accept_comments?
... ... @@ -80,22 +79,28 @@ class ContentViewerController &lt; ApplicationController
80 79 remove_comment
81 80 end
82 81  
83   - if @page.blog?
  82 + if @page.has_posts?
84 83 posts = if params[:year] and params[:month]
85 84 filter_date = DateTime.parse("#{params[:year]}-#{params[:month]}-01")
86   - @page.posts.by_range(filter_date..Article.last_day_of_month(filter_date))
  85 + @page.posts.by_range(filter_date..filter_date.at_end_of_month)
87 86 else
88 87 @page.posts
89 88 end
90   - @posts = available_articles(posts, user).paginate :page => params[:npage], :per_page => @page.posts_per_page
  89 +
  90 + posts = posts.native_translations if @page.blog? && @page.display_posts_in_current_language?
  91 +
  92 + @posts = posts.paginate({ :page => params[:npage], :per_page => @page.posts_per_page }.merge(Article.display_filter(user, profile)))
  93 +
  94 + @posts.map!{ |p| p.get_translation_to(FastGettext.locale) } if @page.blog? && @page.display_posts_in_current_language?
91 95 end
92 96  
93   - if @page.folder? && @page.view_as == 'image_gallery'
  97 + if @page.folder? && @page.gallery?
94 98 @images = @page.images
95 99 @images = @images.paginate(:per_page => per_page, :page => params[:npage]) unless params[:slideshow]
96 100 end
97 101  
98   - @comments = @page.comments(true)
  102 + @comments = @page.comments(true).as_thread
  103 + @comments_count = @page.comments.count
99 104 if params[:slideshow]
100 105 render :action => 'slideshow', :layout => 'slideshow'
101 106 end
... ... @@ -110,8 +115,9 @@ class ContentViewerController &lt; ApplicationController
110 115 if @comment.save
111 116 @page.touch
112 117 @comment = nil # clear the comment form
  118 + redirect_to :action => 'view_page', :profile => params[:profile], :page => @page.explode_path, :view => params[:view]
113 119 else
114   - @form_div = 'opened'
  120 + @form_div = 'opened' if params[:comment][:reply_of_id].blank?
115 121 end
116 122 end
117 123  
... ... @@ -119,7 +125,7 @@ class ContentViewerController &lt; ApplicationController
119 125 @comment = @page.comments.find(params[:remove_comment])
120 126 if (user == @comment.author || user == @page.profile || user.has_permission?(:moderate_comments, @page.profile))
121 127 @comment.destroy
122   - flash[:notice] = _('Comment succesfully deleted')
  128 + session[:notice] = _('Comment succesfully deleted')
123 129 end
124 130 redirect_to :action => 'view_page', :profile => params[:profile], :page => @page.explode_path, :view => params[:view]
125 131 end
... ... @@ -127,4 +133,24 @@ class ContentViewerController &lt; ApplicationController
127 133 def per_page
128 134 12
129 135 end
  136 +
  137 + def redirect_to_translation
  138 + locale = FastGettext.locale
  139 + if !@page.language.nil? && @page.language != locale
  140 + translations = [@page.native_translation] + @page.native_translation.translations
  141 + urls = translations.map{ |t| URI.parse(url_for(t.url)).path }
  142 + urls << URI.parse(url_for(profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }))).path
  143 + urls << URI.parse(url_for(profile.admin_url.merge(:controller => 'cms', :action => 'new'))).path
  144 + referer = URI.parse(url_for(request.referer)).path unless request.referer.blank?
  145 + unless urls.include?(referer)
  146 + translations.each do |translation|
  147 + if translation.language == locale
  148 + @page = translation
  149 + redirect_to :profile => @page.profile.identifier, :page => @page.explode_path
  150 + end
  151 + end
  152 + end
  153 + end
  154 + end
  155 +
130 156 end
... ...
app/controllers/public/enterprise_registration_controller.rb
... ... @@ -16,9 +16,9 @@ class EnterpriseRegistrationController &lt; ApplicationController
16 16 @create_enterprise.target = Profile.find(params[:create_enterprise][:target_id])
17 17 end
18 18 elsif @validation == :admin || @validation == :none
19   - @create_enterprise.target = @create_enterprise.environment
  19 + @create_enterprise.target = environment
20 20 end
21   - @create_enterprise.requestor = current_user.person
  21 + @create_enterprise.requestor = user
22 22 the_action =
23 23 if request.post?
24 24 if @create_enterprise.valid_before_selecting_target?
... ...
app/controllers/public/events_controller.rb
... ... @@ -13,7 +13,7 @@ class EventsController &lt; PublicController
13 13 @events_of_the_day = profile.events.by_day(@selected_day)
14 14 end
15 15  
16   - events = profile.events.by_range(Event.first_day_of_month(date - 1.month)..Event.last_day_of_month(date + 1.month))
  16 + events = profile.events.by_range((date - 1.month).at_beginning_of_month..(date + 1.month).at_end_of_month)
17 17  
18 18 @calendar = populate_calendar(date, events)
19 19 @previous_calendar = populate_calendar(date - 1.month, events)
... ...
app/controllers/public/home_controller.rb
... ... @@ -7,8 +7,8 @@ class HomeController &lt; PublicController
7 7 @news_cache_key = environment.portal_news_cache_key
8 8 if !read_fragment(@news_cache_key)
9 9 portal_community = environment.portal_community
10   - @portal_news = portal_community.news(5)
11 10 @highlighted_news = portal_community.news(2, true)
  11 + @portal_news = portal_community.news(7, true) - @highlighted_news
12 12 @area_news = environment.portal_folders
13 13 end
14 14 end
... ...
app/controllers/public/invite_controller.rb
... ... @@ -2,40 +2,57 @@ class InviteController &lt; PublicController
2 2  
3 3 needs_profile
4 4 before_filter :login_required
5   - before_filter :check_permissions_to_invite, :only => 'friends'
  5 + before_filter :check_permissions_to_invite
6 6  
7   - def friends
8   - step = params[:step]
  7 + def select_address_book
  8 + @import_from = params[:import_from] || "manual"
9 9 if request.post?
10   - if step == '1'
11   - begin
12   - @contacts = Invitation.get_contacts(params[:import_from], params[:login], params[:password])
13   - rescue
14   - @login = params[:login]
15   - flash.now[:notice] = _('There was an error while looking for your contact list. Did you enter correct login and password?')
16   - end
17   - elsif step == '2'
18   - manual_import_addresses = params[:manual_import_addresses]
19   - webmail_import_addresses = params[:webmail_import_addresses]
20   - contacts_to_invite = Invitation.join_contacts(manual_import_addresses, webmail_import_addresses)
21   - if !params[:mail_template].match(/<url>/)
22   - flash.now[:notice] = _('&lt;url&gt; is needed in invitation mail.')
23   - elsif !contacts_to_invite.empty?
24   - Invitation.invite(current_user.person, contacts_to_invite, params[:mail_template], profile)
25   - flash[:notice] = _('Your invitations have been sent.')
26   - redirect_back_or_default :controller => 'profile'
  10 + contact_list = ContactList.create
  11 + Delayed::Job.enqueue GetEmailContactsJob.new(@import_from, params[:login], params[:password], contact_list.id) if @import_from != 'manual'
  12 + redirect_to :action => 'select_friends', :contact_list => contact_list.id, :import_from => @import_from
  13 + end
  14 + end
  15 +
  16 + def select_friends
  17 + @contact_list = ContactList.find(params[:contact_list])
  18 + @mail_template = params[:mail_template] || environment.invitation_mail_template(profile)
  19 + @import_from = params[:import_from] || "manual"
  20 + if request.post?
  21 + manual_import_addresses = params[:manual_import_addresses]
  22 + webmail_import_addresses = params[:webmail_import_addresses]
  23 + contacts_to_invite = Invitation.join_contacts(manual_import_addresses, webmail_import_addresses)
  24 + if !contacts_to_invite.empty?
  25 + Delayed::Job.enqueue InvitationJob.new(current_user.person.id, contacts_to_invite, params[:mail_template], profile.id, @contact_list.id, locale)
  26 + session[:notice] = _('Your invitations are being sent.')
  27 + if profile.person?
  28 + redirect_to :controller => 'profile', :action => 'friends'
27 29 else
28   - flash.now[:notice] = _('Please enter a valid email address.')
  30 + redirect_to :controller => 'profile', :action => 'members'
29 31 end
30   - @contacts = params[:webmail_friends] ? params[:webmail_friends].map {|e| YAML.load(e)} : []
31   - @manual_import_addresses = manual_import_addresses || ""
32   - @webmail_import_addresses = webmail_import_addresses || []
  32 + return
  33 + else
  34 + session[:notice] = _('Please enter a valid email address.')
33 35 end
34   - else
35   - store_location(request.referer)
  36 + @manual_import_addresses = manual_import_addresses || ""
  37 + @webmail_import_addresses = webmail_import_addresses || []
36 38 end
37   - @import_from = params[:import_from] || "manual"
38   - @mail_template = params[:mail_template] || environment.invitation_mail_template(profile)
  39 + end
  40 +
  41 + def invitation_data
  42 + contact_list = ContactList.find(params[:contact_list])
  43 + render :text => contact_list.data.to_json, :layout => false, :content_type => "application/javascript"
  44 + end
  45 +
  46 + def add_contact_list
  47 + contact_list = ContactList.find(params[:contact_list])
  48 + contacts = contact_list.list
  49 + render :partial => 'invite/contact_list', :locals => {:contacts => contacts}
  50 + end
  51 +
  52 + def cancel_fetching_emails
  53 + contact_list = ContactList.find(params[:contact_list])
  54 + contact_list.destroy
  55 + redirect_to :action => 'select_address_book'
39 56 end
40 57  
41 58 protected
... ...
app/controllers/public/profile_controller.rb
1 1 class ProfileController < PublicController
2 2  
3 3 needs_profile
4   - before_filter :check_access_to_profile, :except => [:join, :refuse_join, :refuse_for_now, :index]
5   - before_filter :store_before_join, :only => [:join]
6   - before_filter :login_required, :only => [:join, :refuse_join, :leave, :unblock]
  4 + before_filter :check_access_to_profile, :except => [:join, :join_not_logged, :index, :add]
  5 + before_filter :store_before_join, :only => [:join, :join_not_logged]
  6 + before_filter :login_required, :only => [:add, :join, :join_not_logged, :leave, :unblock, :leave_scrap, :remove_scrap, :remove_activity, :view_more_scraps, :view_more_activities, :view_more_network_activities]
7 7  
8 8 helper TagsHelper
9 9  
10 10 def index
  11 + @activities = @profile.tracked_actions.paginate(:per_page => 30, :page => params[:page])
  12 + @wall_items = []
  13 + @network_activities = !@profile.is_a?(Person) ? @profile.tracked_notifications.paginate(:per_page => 30, :page => params[:page]) : []
  14 + if logged_in? && current_person.follows?(@profile)
  15 + @network_activities = @profile.tracked_notifications.paginate(:per_page => 30, :page => params[:page]) if @network_activities.empty?
  16 + @wall_items = @profile.scraps_received.not_replies.paginate(:per_page => 30, :page => params[:page])
  17 + end
11 18 @tags = profile.article_tags
12 19 unless profile.display_info_to?(user)
13 20 profile.visible? ? private_profile : invisible_profile
... ... @@ -73,20 +80,27 @@ class ProfileController &lt; PublicController
73 80 end
74 81  
75 82 def join
76   - @wizard = params[:wizard]
77   - if request.post? && params[:confirmation]
78   - profile.add_member(current_user.person)
79   - flash[:notice] = _('%s administrator still needs to accept you as member.') % profile.name if profile.closed?
80   - if @wizard
81   - redirect_to :controller => 'search', :action => 'assets', :asset => 'communities', :wizard => true
  83 + if !user.memberships.include?(profile)
  84 + profile.add_member(user)
  85 + if !profile.members.include?(user)
  86 + render :text => {:message => _('%s administrator still needs to accept you as member.') % profile.name}.to_json
82 87 else
83   - redirect_to_before_join
  88 + render :text => {:message => _('You just became a member of %s.') % profile.name}.to_json
84 89 end
85 90 else
86   - store_location(request.referer)
87   - if current_user.person.memberships.include?(profile)
88   - flash[:notice] = _('You are already a member of "%s"') % profile.name
89   - redirect_back_or_default profile.url
  91 + render :text => {:message => _('You are already a member of %s.') % profile.name}.to_json
  92 + end
  93 + end
  94 +
  95 + def join_not_logged
  96 + if request.post?
  97 + profile.add_member(user)
  98 + session[:notice] = _('%s administrator still needs to accept you as member.') % profile.name if profile.closed?
  99 + redirect_to_before_join
  100 + else
  101 + if user.memberships.include?(profile)
  102 + session[:notice] = _('You are already a member of %s.') % profile.name
  103 + redirect_to profile.url
90 104 return
91 105 end
92 106 if request.xhr?
... ... @@ -96,47 +110,109 @@ class ProfileController &lt; PublicController
96 110 end
97 111  
98 112 def leave
99   - @wizard = params[:wizard]
100   - if request.post? && params[:confirmation]
101   - profile.remove_member(current_user.person)
102   - if @wizard
103   - redirect_to :controller => 'search', :action => 'assets', :asset => 'communities', :wizard => true
  113 + if current_person.memberships.include?(profile)
  114 + if current_person.is_last_admin?(profile)
  115 + render :text => {:redirect_to => url_for({:controller => 'profile_members', :action => 'last_admin', :person => current_person.id})}.to_json
104 116 else
105   - redirect_back_or_default profile.url
  117 + render :text => current_person.leave(profile, params[:reload])
106 118 end
107 119 else
108   - store_location(request.referer)
109   - if request.xhr?
110   - render :layout => false
111   - end
  120 + render :text => {:message => _('You are not a member of %s.') % profile.name}.to_json
112 121 end
113 122 end
114 123  
115   - def refuse_join
116   - p = current_user.person
117   - p.refused_communities << profile
118   - p.save
119   - redirect_to profile.url
  124 + def check_membership
  125 + unless logged_in?
  126 + render :text => ''
  127 + return
  128 + end
  129 + if user.memberships.include?(profile)
  130 + render :text => 'true'
  131 + else
  132 + render :text => 'false'
  133 + end
120 134 end
121 135  
122   - def refuse_for_now
123   - session[:no_asking] ||= []
124   - session[:no_asking].shift if session[:no_asking].size >= 10
125   - session[:no_asking] << profile.id
126   - render :text => '', :layout => false
  136 + def add
  137 + # FIXME this shouldn't be in Person model?
  138 + if !user.memberships.include?(profile)
  139 + AddFriend.create!(:person => user, :friend => profile)
  140 + render :text => _('%s still needs to accept being your friend.') % profile.name
  141 + else
  142 + render :text => _('You are already a friend of %s.') % profile.name
  143 + end
  144 + end
  145 +
  146 + def check_friendship
  147 + unless logged_in?
  148 + render :text => ''
  149 + return
  150 + end
  151 + if user == profile || user.already_request_friendship?(profile) || user.is_a_friend?(profile)
  152 + render :text => 'true'
  153 + else
  154 + render :text => 'false'
  155 + end
127 156 end
128 157  
129 158 def unblock
130 159 if current_user.person.is_admin?(profile.environment)
131 160 profile.unblock
132   - flash[:notice] = _("You have unblocked %s successfully. ") % profile.name
  161 + session[:notice] = _("You have unblocked %s successfully. ") % profile.name
133 162 redirect_to :controller => 'profile', :action => 'index'
134 163 else
135   - message = _('You are not allowed to unblock enterprises in this environment.')
  164 + message = __('You are not allowed to unblock enterprises in this environment.')
136 165 render_access_denied(message)
137 166 end
138 167 end
139 168  
  169 + def leave_scrap
  170 + sender = params[:sender_id].nil? ? current_user.person : Person.find(params[:sender_id])
  171 + receiver = params[:receiver_id].nil? ? @profile : Person.find(params[:receiver_id])
  172 + @scrap = Scrap.new(params[:scrap])
  173 + @scrap.sender= sender
  174 + @scrap.receiver= receiver
  175 + @tab_action = params[:tab_action]
  176 + @message = @scrap.save ? _("Message successfully sent.") : _("You can't leave an empty message.")
  177 + @scraps = @profile.scraps_received.not_replies.paginate(:per_page => 30, :page => params[:page]) if params[:not_load_scraps].nil?
  178 + render :partial => 'leave_scrap'
  179 + end
  180 +
  181 + def view_more_scraps
  182 + @scraps = @profile.scraps_received.not_replies.paginate(:per_page => 30, :page => params[:page])
  183 + render :partial => 'profile_scraps', :locals => {:scraps => @scraps}
  184 + end
  185 +
  186 + def view_more_activities
  187 + @activities = @profile.tracked_actions.paginate(:per_page => 30, :page => params[:page])
  188 + render :partial => 'profile_activities', :locals => {:activities => @activities}
  189 + end
  190 +
  191 + def view_more_network_activities
  192 + @activities = @profile.tracked_notifications.paginate(:per_page => 30, :page => params[:page])
  193 + render :partial => 'profile_network_activities', :locals => {:network_activities => @activities}
  194 + end
  195 +
  196 + def remove_scrap
  197 + begin
  198 + scrap = current_user.person.scraps(params[:scrap_id])
  199 + scrap.destroy
  200 + render :text => _('Scrap successfully removed.')
  201 + rescue
  202 + render :text => _('You could not remove this scrap')
  203 + end
  204 + end
  205 +
  206 + def remove_activity
  207 + begin
  208 + activity = current_person.tracked_actions.find(params[:activity_id])
  209 + activity.destroy
  210 + render :text => _('Activity successfully removed.')
  211 + rescue
  212 + render :text => _('You could not remove this activity')
  213 + end
  214 + end
  215 +
140 216 protected
141 217  
142 218 def check_access_to_profile
... ... @@ -146,7 +222,9 @@ class ProfileController &lt; PublicController
146 222 end
147 223  
148 224 def store_before_join
149   - session[:before_join] = request.referer unless logged_in?
  225 + if session[:before_join].nil?
  226 + session[:before_join] = request.referer
  227 + end
150 228 end
151 229  
152 230 def redirect_to_before_join
... ... @@ -155,14 +233,14 @@ class ProfileController &lt; PublicController
155 233 session[:before_join] = nil
156 234 redirect_to back
157 235 else
158   - redirect_back_or_default profile.url
  236 + redirect_to profile.url
159 237 end
160 238 end
161 239  
162 240 def private_profile
163 241 if profile.person?
164 242 @action = :add_friend
165   - @message = _("The content here is available to %s's friends only." % profile.short_name)
  243 + @message = _("The content here is available to %s's friends only.") % profile.short_name
166 244 else
167 245 @action = :join
168 246 @message = _('The contents in this community is available to members only.')
... ...
app/controllers/public/profile_search_controller.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +class ProfileSearchController < PublicController
  2 +
  3 + include SearchHelper
  4 +
  5 + needs_profile
  6 + before_filter :check_access_to_profile
  7 +
  8 + def index
  9 + @q = params[:q]
  10 + unless @q.blank?
  11 + @filtered_query = remove_stop_words(@q)
  12 + if params[:where] == 'environment'
  13 + redirect_to :controller => 'search', :query => @q
  14 + else
  15 + @results = profile.articles.published.find_by_contents(@filtered_query).paginate(:per_page => 10, :page => params[:page])
  16 + end
  17 + end
  18 + end
  19 +
  20 + protected
  21 +
  22 + def check_access_to_profile
  23 + unless profile.display_info_to?(user)
  24 + redirect_to :controller => 'profile', :action => 'index'
  25 + end
  26 + end
  27 +
  28 +end
... ...
app/controllers/public/search_controller.rb
... ... @@ -102,7 +102,7 @@ class SearchController &lt; PublicController
102 102  
103 103 if month || year
104 104 date = Date.new(year.to_i, month.to_i, 1)
105   - result[:date_range] = (date - 1.month)..Event.last_day_of_month(date + 1.month)
  105 + result[:date_range] = (date - 1.month)..(date + 1.month).at_end_of_month
106 106 end
107 107  
108 108 result
... ... @@ -148,8 +148,6 @@ class SearchController &lt; PublicController
148 148 end
149 149  
150 150 def index
151   - @wizard = params[:wizard].blank? ? false : params[:wizard]
152   - @step = 2
153 151 @query = params[:query] || ''
154 152 @filtered_query = remove_stop_words(@query)
155 153 @product_category = ProductCategory.find(params[:product_category]) if params[:product_category]
... ... @@ -174,20 +172,12 @@ class SearchController &lt; PublicController
174 172 if respond_to?(specific_action)
175 173 @asset_name = getterm(@names[@results.keys.first])
176 174 send(specific_action)
177   - if @wizard
178   - render :action => specific_action, :layout => 'wizard'
179   - else
180   - render :action => specific_action
181   - end
  175 + render :action => specific_action
182 176 return
183 177 end
184 178 end
185 179  
186   - if @wizard
187   - render :action => 'index', :layout => 'wizard'
188   - else
189   - render :action => 'index'
190   - end
  180 + render :action => 'index'
191 181 end
192 182  
193 183 alias :assets :index
... ... @@ -223,10 +213,10 @@ class SearchController &lt; PublicController
223 213 end
224 214  
225 215 def tag
226   - @tag = environment.tags.find_by_name(params[:tag])
  216 + @tag = params[:tag]
227 217 @tag_cache_key = "tag_#{CGI.escape(@tag.to_s)}_env_#{environment.id.to_s}_page_#{params[:npage]}"
228 218 if is_cache_expired?(@tag_cache_key, true)
229   - @tagged = environment.articles.find_tagged_with(@tag.name).paginate(:per_page => 10, :page => params[:npage])
  219 + @tagged = environment.articles.find_tagged_with(@tag).paginate(:per_page => 10, :page => params[:npage])
230 220 end
231 221 end
232 222  
... ...
app/helpers/account_helper.rb
1 1 module AccountHelper
2 2  
3   -
4   - def button_to_step(type, step, current_step, html_options = {})
5   - if current_step == step
6   - the_class = 'active'
7   - if html_options.has_key?(:class)
8   - html_options[:class] << " #{the_class}"
9   - else
10   - html_options[:class] = the_class
11   - end
12   - end
13   - if step == 1
14   - url = '#'
15   - else
16   - url = send('url_step_' + step.to_s)
17   - end
18   - button(type, step.to_s, url, html_options)
19   - end
20   -
21   - def button_to_step_without_text(type, step, html_options = {})
22   - url = 'url_step_' + step
23   - button_without_text(type, step, send(url), html_options)
24   - end
25   -
26   - def button_to_previous_step(step, html_options = {})
27   - step = step - 1
28   - if step > 1
29   - button_to_step_without_text(:left, step.to_s, html_options)
30   - end
31   - end
32   -
33   - def button_to_next_step(step, html_options = {})
34   - step = step + 1
35   - if step < 4
36   - button_to_step_without_text(:forward, step.to_s, html_options)
37   - end
38   - end
39   -
40   - def url_step_1
41   - options = {:controller => 'account', :action => 'signup', :wizard => true}
42   - Noosfero.url_options.merge(options)
43   - end
44   -
45   - def url_step_2
46   - options = {:controller => 'search', :action => 'assets', :asset => 'communities', :wizard => true}
47   - Noosfero.url_options.merge(options)
48   - end
49   -
50   - def url_step_3
51   - options = {:controller => 'friends', :action => 'invite', :profile => user.identifier, :wizard => true}
52   - Noosfero.url_options.merge(options)
53   - end
54   -
55 3 end
... ...
app/helpers/application_helper.rb
... ... @@ -27,18 +27,7 @@ module ApplicationHelper
27 27 include AccountHelper
28 28  
29 29 def locale
30   - FastGettext.locale
31   - end
32   -
33   - def load_web2_conf
34   - if File.exists?( RAILS_ROOT + '/config/web2.0.yml')
35   - YAML.load_file( RAILS_ROOT + '/config/web2.0.yml' )
36   - else
37   - {}
38   - end
39   - end
40   - def web2_conf
41   - @web_conf ||= load_web2_conf
  30 + (@page && !@page.language.blank?) ? @page.language : FastGettext.locale
42 31 end
43 32  
44 33 # Displays context help. You can pass the content of the help message as the
... ... @@ -217,7 +206,8 @@ module ApplicationHelper
217 206 if html_options.has_key?(:class)
218 207 the_class << ' ' << html_options[:class]
219 208 end
220   - link_to('&nbsp;'+content_tag('span', label), url, html_options.merge(:class => the_class, :title => label))
  209 + the_title = html_options[:title] || label
  210 + link_to('&nbsp;'+content_tag('span', label), url, html_options.merge(:class => the_class, :title => the_title))
221 211 end
222 212  
223 213 def button_to_function(type, label, js_code, html_options = {}, &block)
... ... @@ -278,6 +268,19 @@ module ApplicationHelper
278 268 end
279 269 end
280 270  
  271 + def partial_for_task_class(klass, action)
  272 + if klass.nil?
  273 + raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?'
  274 + end
  275 +
  276 + name = "#{klass.name.underscore}_#{action.to_s}"
  277 + if File.exists?(File.join(RAILS_ROOT, 'app', 'views', params[:controller], "_#{name}.rhtml"))
  278 + name
  279 + else
  280 + partial_for_task_class(klass.superclass, action)
  281 + end
  282 + end
  283 +
281 284 def user
282 285 @controller.send(:user)
283 286 end
... ... @@ -339,7 +342,7 @@ module ApplicationHelper
339 342 if ENV['RAILS_ENV'] == 'development' && environment.theme == 'random'
340 343 @random_theme ||= Dir.glob('public/designs/themes/*').map { |f| File.basename(f) }.rand
341 344 @random_theme
342   - elsif ENV['RAILS_ENV'] == 'development' && params[:theme]
  345 + elsif ENV['RAILS_ENV'] == 'development' && params[:theme] && File.exists?(File.join(Rails.root, 'public/designs/themes', params[:theme]))
343 346 params[:theme]
344 347 else
345 348 if profile && !profile.theme.nil?
... ... @@ -371,6 +374,24 @@ module ApplicationHelper
371 374 nil
372 375 end
373 376  
  377 + def theme_favicon
  378 + return '/designs/themes/' + current_theme + '/favicon.ico' if profile.nil? || profile.theme.nil?
  379 + if File.exists?(File.join(RAILS_ROOT, 'public', theme_path, 'favicon.ico'))
  380 + '/designs/themes/' + profile.theme + '/favicon.ico'
  381 + else
  382 + favicon = profile.articles.find_by_path('favicon.ico')
  383 + if favicon
  384 + favicon.public_filename
  385 + else
  386 + '/designs/themes/' + environment.theme + '/favicon.ico'
  387 + end
  388 + end
  389 + end
  390 +
  391 + def theme_site_title
  392 + theme_include('site_title')
  393 + end
  394 +
374 395 def theme_header
375 396 theme_include('header')
376 397 end
... ... @@ -395,6 +416,7 @@ module ApplicationHelper
395 416 #
396 417 # If the profile has no image set yet, then a default image is used.
397 418 def profile_image(profile, size=:portrait, opt={})
  419 + return '' if profile.nil?
398 420 opt[:alt] ||= profile.name()
399 421 opt[:title] ||= ''
400 422 opt[:class] ||= ''
... ... @@ -402,21 +424,33 @@ module ApplicationHelper
402 424 image_tag(profile_icon(profile, size), opt )
403 425 end
404 426  
405   - def profile_icon( profile, size=:portrait )
  427 + def profile_icon( profile, size=:portrait, return_mimetype=false )
  428 + filename, mimetype = '', 'image/png'
406 429 if profile.image
407   - profile.image.public_filename( size )
  430 + filename = profile.image.public_filename( size )
  431 + mimetype = profile.image.content_type
408 432 else
409   - if profile.organization?
410   - if profile.kind_of?(Community)
411   - '/images/icons-app/users_size-'+ size.to_s() +'.png'
  433 + icon =
  434 + if profile.organization?
  435 + if profile.kind_of?(Community)
  436 + '/images/icons-app/community-'+ size.to_s() +'.png'
  437 + else
  438 + '/images/icons-app/enterprise-'+ size.to_s() +'.png'
  439 + end
412 440 else
413   - '/images/icons-app/enterprise-default-pic-'+ size.to_s() +'.png'
  441 + '/images/icons-app/person-'+ size.to_s() +'.png'
414 442 end
415   - else
416   - '/images/icons-app/user_icon_size-'+ size.to_s() +'.png'
417   - end
  443 + filename = default_or_themed_icon(icon)
418 444 end
  445 + return_mimetype ? [filename, mimetype] : filename
  446 + end
419 447  
  448 + def default_or_themed_icon(icon)
  449 + if File.exists?(File.join(Rails.root, 'public', theme_path, icon))
  450 + theme_path + icon
  451 + else
  452 + icon
  453 + end
420 454 end
421 455  
422 456 def profile_sex_icon( profile )
... ... @@ -455,71 +489,69 @@ module ApplicationHelper
455 489 end
456 490 end
457 491  
458   - # displays a link to add the profile with its image (as generated by
459   - # #profile_image) or only its name below.
460   - def profile_add_link( profile, image=false, size=:portrait, tag='li')
461   - the_class = profile.members.include?(user) ? 'profile_member' : ''
462   - name = h(profile.short_name)
463   - if image
464   - display = content_tag( 'span', profile_image( profile, size ), :class => 'profile-image' ) +
465   - content_tag( 'span', name, :class => 'org' ) +
466   - profile_cat_icons( profile )
467   - the_class << ' vcard'
468   - else
469   - display = content_tag( 'span', name, :class => 'org' )
  492 + def links_for_balloon(profile)
  493 + if environment.enabled?(:show_balloon_with_profile_links_when_clicked)
  494 + if profile.kind_of?(Person)
  495 + [
  496 + {_('Wall') => {:href => url_for(profile.public_profile_url)}},
  497 + {_('Friends') => {:href => url_for(:controller => :profile, :action => :friends, :profile => profile.identifier)}},
  498 + {_('Communities') => {:href => url_for(:controller => :profile, :action => :communities, :profile => profile.identifier)}},
  499 + {_('Send an e-mail') => {:href => url_for(:profile => profile.identifier, :controller => 'contact', :action => 'new'), :class => 'send-an-email', :style => 'display: none'}},
  500 + {_('Add') => {:href => url_for(profile.add_url), :class => 'add-friend', :style => 'display: none'}}
  501 + ]
  502 + elsif profile.kind_of?(Community)
  503 + [
  504 + {_('Wall') => {:href => url_for(profile.public_profile_url)}},
  505 + {_('Members') => {:href => url_for(:controller => :profile, :action => :members, :profile => profile.identifier)}},
  506 + {_('Agenda') => {:href => url_for(:controller => :profile, :action => :events, :profile => profile.identifier)}},
  507 + {_('Join') => {:href => url_for(profile.join_url), :class => 'join-community', :style => 'display: none'}},
  508 + {_('Leave') => {:href => url_for(profile.leave_url), :class => 'leave-community', :style => 'display: none'}},
  509 + {_('Send an e-mail') => {:href => url_for(:profile => profile.identifier, :controller => 'contact', :action => 'new'), :class => 'send-an-email', :style => 'display: none'}}
  510 + ]
  511 + elsif profile.kind_of?(Enterprise)
  512 + [
  513 + {_('Products') => {:href => catalog_path(profile.identifier)}},
  514 + {_('Members') => {:href => url_for(:controller => :profile, :action => :members, :profile => profile.identifier)}},
  515 + {_('Agenda') => {:href => url_for(:controller => :profile, :action => :events, :profile => profile.identifier)}},
  516 + {_('Send an e-mail') => {:href => url_for(:profile => profile.identifier, :controller => 'contact', :action => 'new'), :class => 'send-an-email', :style => 'display: none'}},
  517 + ]
  518 + else
  519 + []
  520 + end
470 521 end
471   - content_tag tag,
472   - link_to_remote( display,
473   - :update => 'search-results-and-pages',
474   - :url => {:controller => 'account', :action => 'profile_details', :profile => profile.identifier},
475   - :onclick => 'document.location.href = this.href', # work-arround for ie.
476   - :class => 'profile_link url',
477   - :help => _('Click on this icon to add <b>%s</b> to your network') % profile.name,
478   - :title => profile.name ),
479   - :class => the_class
480 522 end
481 523  
482 524 # displays a link to the profile homepage with its image (as generated by
483 525 # #profile_image) and its name below it.
484   - def profile_image_link( profile, size=:portrait, tag='li' )
485   - if profile.class == Person
486   - name = profile.short_name
487   - city = content_tag 'span', content_tag( 'span', profile.city, :class => 'locality' ), :class => 'adr'
  526 + def profile_image_link( profile, size=:portrait, tag='li', extra_info = nil )
  527 + name = profile.short_name
  528 + if profile.person?
  529 + url = url_for(profile.check_friendship_url)
  530 + trigger_class = 'person-trigger'
488 531 else
489   - name = profile.short_name
490 532 city = ''
  533 + url = url_for(profile.check_membership_url)
  534 + if profile.community?
  535 + trigger_class = 'community-trigger'
  536 + elsif profile.enterprise?
  537 + trigger_class = 'enterprise-trigger'
  538 + end
491 539 end
  540 + extra_info = extra_info.nil? ? '' : content_tag( 'span', extra_info, :class => 'extra_info' )
  541 + links = links_for_balloon(profile)
492 542 content_tag tag,
  543 + (environment.enabled?(:show_balloon_with_profile_links_when_clicked) ? link_to( content_tag( 'span', _('Profile links')), '#', :onclick => "toggleSubmenu(this, '#{profile.short_name}', #{links.to_json}); return false", :class => "menu-submenu-trigger #{trigger_class}", :url => url) : "") +
493 544 link_to(
494 545 content_tag( 'span', profile_image( profile, size ), :class => 'profile-image' ) +
495 546 content_tag( 'span', h(name), :class => ( profile.class == Person ? 'fn' : 'org' ) ) +
496   - city + profile_sex_icon( profile ) + profile_cat_icons( profile ),
  547 + extra_info + profile_sex_icon( profile ) + profile_cat_icons( profile ),
497 548 profile.url,
498   - :onclick => 'document.location.href = this.href', # work-arround for ie.
499 549 :class => 'profile_link url',
500 550 :help => _('Click on this icon to go to the <b>%s</b>\'s home page') % profile.name,
501 551 :title => profile.name ),
502 552 :class => 'vcard'
503 553 end
504 554  
505   - # displays a link to the community homepage with its image (as generated by
506   - # #profile_image) and its name and number of members beside it.
507   - def community_image_link( profile, size=:portrait, tag='li' )
508   - name = h(profile.name)
509   - content_tag tag,
510   - link_to(
511   - content_tag( 'span', profile_image( profile, size ), :class => 'profile-image' ) +
512   - content_tag( 'span', name, :class => 'org' ) +
513   - content_tag( 'span', n_('1 member', '%s members', profile.members.count) % profile.members.count, :class => 'community-member-count' ),
514   - profile.url,
515   - :onclick => 'document.location.href = this.href', # work-arround for ie.
516   - :class => 'profile_link url',
517   - :help => _('Click on this icon to go to the <b>%s</b>\'s home page') % profile.name,
518   - :title => profile.name ) +
519   - '<br class="may-clear"/>',
520   - :class => 'vcard'
521   - end
522   -
523 555 def gravatar_url_for(email, options = {})
524 556 # Ta dando erro de roteamento
525 557 url_for( { :gravatar_id => Digest::MD5.hexdigest(email),
... ... @@ -527,12 +559,12 @@ module ApplicationHelper
527 559 :protocol => 'http://',
528 560 :only_path => false,
529 561 :controller => 'avatar.php',
530   - :d => web2_conf['gravatar'] ? web2_conf['gravatar']['default'] : nil
  562 + :d => NOOSFERO_CONF['gravatar'] ? NOOSFERO_CONF['gravatar'] : nil
531 563 }.merge(options) )
532 564 end
533 565  
534 566 def str_gravatar_url_for(email, options = {})
535   - default = web2_conf['gravatar'] ? web2_conf['gravatar']['default'] : nil
  567 + default = NOOSFERO_CONF['gravatar'] ? NOOSFERO_CONF['gravatar'] : nil
536 568 url = 'http://www.gravatar.com/avatar.php?gravatar_id=' +
537 569 Digest::MD5.hexdigest(email)
538 570 {
... ... @@ -591,7 +623,16 @@ module ApplicationHelper
591 623 end
592 624  
593 625 def select_folder(label, object, method, collection, html_options = {}, js_options = {})
594   - labelled_form_field(label, select(object, method, collection.map {|f| [ profile.identifier + '/' + f.full_name, f.id ] }, html_options.merge({:include_blank => "#{profile.identifier}"}), js_options))
  626 + root = profile ? profile.identifier : _("root")
  627 + labelled_form_field(label, select(object, method,
  628 + collection.map {|f| [ root + '/' + f.full_name, f.id ]},
  629 + {:include_blank => root}, html_options.merge(js_options)))
  630 + end
  631 +
  632 + def select_profile_folder(label, object, method, profile, html_options = {}, js_options = {})
  633 + labelled_form_field(label, select(object, method,
  634 + profile.folders.map {|f| [ profile.identifier + '/' + f.full_name, f.id ]},
  635 + {:include_blank => profile.identifier}, html_options.merge(js_options)))
595 636 end
596 637  
597 638 def theme_option(opt = nil)
... ... @@ -851,6 +892,7 @@ module ApplicationHelper
851 892  
852 893 def helper_for_article(article)
853 894 article_helper = ActionView::Base.new
  895 + article_helper.controller = controller
854 896 article_helper.extend ArticleHelper
855 897 begin
856 898 class_name = article.class.name + 'Helper'
... ... @@ -877,37 +919,27 @@ module ApplicationHelper
877 919 end
878 920 end
879 921  
880   - def ask_to_join?
881   - return if !environment.enabled?(:join_community_popup)
882   - return if params[:action] == 'join'
883   - return unless profile && profile.kind_of?(Community)
884   - if (session[:no_asking] && session[:no_asking].include?(profile.id))
885   - return false
886   - end
887   - if logged_in?
888   - user.ask_to_join?(profile)
889   - else
890   - true
891   - end
892   - end
893   -
894 922 def icon_theme_stylesheet_path
895   - theme_path = "/designs/icons/#{environment.icon_theme}/style.css"
896   - if File.exists?(File.join(RAILS_ROOT, 'public', theme_path))
897   - theme_path
898   - else
899   - '/designs/icons/default/style.css'
  923 + icon_themes = []
  924 + theme_icon_themes = theme_option(:icon_theme) || []
  925 + for icon_theme in theme_icon_themes do
  926 + theme_path = "/designs/icons/#{icon_theme}/style.css"
  927 + if File.exists?(File.join(RAILS_ROOT, 'public', theme_path))
  928 + icon_themes << theme_path
  929 + end
900 930 end
  931 + icon_themes
901 932 end
902 933  
903 934 def page_title
904   - (@page ? @page.name + ' - ' : '') +
  935 + (@page ? @page.title + ' - ' : '') +
905 936 (profile ? profile.short_name + ' - ' : '') +
906 937 (@topic ? @topic.title + ' - ' : '') +
907 938 (@section ? @section.title + ' - ' : '') +
908 939 (@toc ? _('Online Manual') + ' - ' : '') +
  940 + (@controller.controller_name == 'chat' ? _('Chat') + ' - ' : '') +
909 941 environment.name +
910   - (@category ? "&rarr; #{@category.full_name}" : '')
  942 + (@category ? " - #{@category.full_name}" : '')
911 943 end
912 944  
913 945 def noosfero_javascript
... ... @@ -946,7 +978,7 @@ module ApplicationHelper
946 978 end
947 979  
948 980 def article_to_html(article, options = {})
949   - options.merge(:page => params[:npage])
  981 + options.merge!(:page => params[:npage])
950 982 content = article.to_html(options)
951 983 return self.instance_eval(&content) if content.kind_of?(Proc)
952 984 content
... ... @@ -960,4 +992,221 @@ module ApplicationHelper
960 992 text_field_tag(name, value, options.merge(:class => 'colorpicker_field'))
961 993 end
962 994  
  995 + def ui_icon(icon_class, extra_class = '')
  996 + "<span class='ui-icon #{icon_class} #{extra_class}' style='float:left; margin-right:7px;'></span>"
  997 + end
  998 +
  999 + def ui_button(label, url, html_options = {})
  1000 + link_to(label, url, html_options.merge(:class => 'ui_button fg-button'))
  1001 + end
  1002 +
  1003 + def ui_button_to_remote(label, options, html_options = {})
  1004 + link_to_remote(label, options, html_options.merge(:class => 'ui_button fg-button'))
  1005 + end
  1006 +
  1007 + def jquery_theme
  1008 + theme_option(:jquery_theme) || 'smoothness_mod'
  1009 + end
  1010 +
  1011 + def jquery_ui_theme_stylesheet_path
  1012 + 'jquery.ui/' + jquery_theme + '/jquery-ui-1.8.2.custom'
  1013 + end
  1014 +
  1015 + def ui_error(message)
  1016 + content_tag('div', ui_icon('ui-icon-alert') + message, :class => 'alert fg-state-error ui-state-error')
  1017 + end
  1018 +
  1019 + def ui_highlight(message)
  1020 + content_tag('div', ui_icon('ui-icon-info') + message, :class => 'alert fg-state-highlight ui-state-highlight')
  1021 + end
  1022 +
  1023 + def float_to_currency(value)
  1024 + number_to_currency(value, :unit => environment.currency_unit, :separator => environment.currency_separator, :delimiter => environment.currency_delimiter, :format => "%u %n")
  1025 + end
  1026 +
  1027 + def collapsed_item_icon
  1028 + "<span class='ui-icon ui-icon-circlesmall-plus' style='float:left;'></span>"
  1029 + end
  1030 + def expanded_item_icon
  1031 + "<span class='ui-icon ui-icon-circlesmall-minus' style='float:left;'></span>"
  1032 + end
  1033 + def leaf_item_icon
  1034 + "<span class='ui-icon ui-icon-arrow-1-e' style='float:left;'></span>"
  1035 + end
  1036 +
  1037 + def display_category_menu(block, categories, root = true)
  1038 + categories = categories.sort{|x,y| x.name <=> y.name}
  1039 + return "" if categories.blank?
  1040 + content_tag(:ul,
  1041 + categories.map do |category|
  1042 + category_path = category.kind_of?(ProductCategory) ? {:controller => 'search', :action => 'assets', :asset => 'products', :product_category => category.id} : { :controller => 'search', :action => 'category_index', :category_path => category.explode_path }
  1043 + category.display_in_menu? ?
  1044 + content_tag(:li,
  1045 + ( !category.is_leaf_displayable_in_menu? ? content_tag(:a, collapsed_item_icon, :href => "#", :id => "block_#{block.id}_category_#{category.id}", :class => 'category-link-expand ' + (root ? 'category-root' : 'category-no-root'), :onclick => "expandCategory(#{block.id}, #{category.id}); return false", :style => 'display: none') : leaf_item_icon) +
  1046 + link_to(content_tag(:span, category.name, :class => 'category-name'), category_path, :class => ("category-leaf" if category.is_leaf_displayable_in_menu?)) +
  1047 + content_tag(:div, display_category_menu(block, category.children, false), :id => "block_#{block.id}_category_content_#{category.id}", :class => 'child-category')
  1048 + ) : ''
  1049 + end
  1050 + ) +
  1051 + content_tag(:p) +
  1052 + (root ? javascript_tag("
  1053 + jQuery('.child-category').hide();
  1054 + jQuery('.category-link-expand').show();
  1055 + var expanded_icon = \"#{ expanded_item_icon }\";
  1056 + var collapsed_icon = \"#{ collapsed_item_icon }\";
  1057 + var category_expanded = { 'block' : 0, 'category' : 0 };
  1058 + ") : '')
  1059 + end
  1060 +
  1061 + def browse_people_menu
  1062 + links = [
  1063 + {s_('people|More Recent') => {:href => url_for({:controller => 'browse', :action => 'people', :filter => 'more_recent'})}},
  1064 + {s_('people|More Active') => {:href => url_for({:controller => 'browse', :action => 'people', :filter => 'more_active'})}},
  1065 + {s_('people|More Popular') => {:href => url_for({:controller => 'browse', :action => 'people', :filter => 'more_popular'})}}
  1066 + ]
  1067 + if logged_in?
  1068 + links.push(_('My friends') => {:href => url_for({:profile => current_user.login, :controller => 'friends'})})
  1069 + links.push(_('Invite friends') => {:href => url_for({:profile => current_user.login, :controller => 'invite', :action => 'friends'})})
  1070 + end
  1071 +
  1072 + link_to(content_tag(:span, _('People'), :class => 'icon-menu-people'), {:controller => "browse", :action => 'people'}, :id => 'submenu-people') +
  1073 + link_to(content_tag(:span, _('People Menu')), '#', :onclick => "toggleSubmenu(this,'',#{links.to_json}); return false", :class => 'menu-submenu-trigger up', :id => 'submenu-people-trigger')
  1074 + end
  1075 +
  1076 + def browse_communities_menu
  1077 + links = [
  1078 + {s_('communities|More Recent') => {:href => url_for({:controller => 'browse', :action => 'communities', :filter => 'more_recent'})}},
  1079 + {s_('communities|More Active') => {:href => url_for({:controller => 'browse', :action => 'communities', :filter => 'more_active'})}},
  1080 + {s_('communities|More Popular') => {:href => url_for({:controller => 'browse', :action => 'communities', :filter => 'more_popular'})}}
  1081 + ]
  1082 + if logged_in?
  1083 + links.push(_('My communities') => {:href => url_for({:profile => current_user.login, :controller => 'memberships'})})
  1084 + links.push(_('New community') => {:href => url_for({:profile => current_user.login, :controller => 'memberships', :action => 'new_community'})})
  1085 + end
  1086 +
  1087 + link_to(content_tag(:span, _('Communities'), :class => 'icon-menu-community'), {:controller => "browse", :action => 'communities'}, :id => 'submenu-communities') +
  1088 + link_to(content_tag(:span, _('Communities Menu')), '#', :onclick => "toggleSubmenu(this,'',#{links.to_json}); return false", :class => 'menu-submenu-trigger up', :id => 'submenu-communities-trigger')
  1089 + end
  1090 +
  1091 + def pagination_links(collection, options={})
  1092 + options = {:prev_label => '&laquo; ' + _('Previous'), :next_label => _('Next') + ' &raquo;'}.merge(options)
  1093 + will_paginate(collection, options)
  1094 + end
  1095 +
  1096 + def render_environment_features(folder)
  1097 + result = ''
  1098 + environment.enabled_features.keys.each do |feature|
  1099 + file = File.join(@controller.view_paths, 'shared', folder.to_s, "#{feature}.rhtml")
  1100 + if File.exists?(file)
  1101 + result << render(:file => file, :use_full_path => false)
  1102 + end
  1103 + end
  1104 + result
  1105 + end
  1106 +
  1107 + def usermenu_logged_in
  1108 + pending_tasks_count = ''
  1109 + if user && user.all_pending_tasks.count > 0
  1110 + pending_tasks_count = link_to(user.all_pending_tasks.count.to_s, '/myprofile/{login}/tasks', :id => 'pending-tasks-count', :title => _("Manage your pending tasks"))
  1111 + end
  1112 +
  1113 + (_('Welcome, %s') % link_to('<i></i><strong>{login}</strong>', '/{login}', :id => "homepage-link", :title => _('Go to your homepage'))) +
  1114 + render_environment_features(:usermenu) +
  1115 + link_to('<i class="icon-menu-admin"></i><strong>' + _('Administration') + '</strong>', { :controller => 'admin_panel', :action => 'index' }, :id => "controlpanel", :title => _("Configure the environment"), :class => 'admin-link', :style => 'display: none') +
  1116 + link_to('<i class="icon-menu-ctrl-panel"></i><strong>' + _('Control panel') + '</strong>', '/myprofile/{login}', :id => "controlpanel", :title => _("Configure your personal account and content")) +
  1117 + pending_tasks_count +
  1118 + link_to('<i class="icon-menu-logout"></i><strong>' + _('Logout') + '</strong>', { :controller => 'account', :action => 'logout'} , :id => "logout", :title => _("Leave the system"))
  1119 + end
  1120 +
  1121 + def limited_text_area(object_name, method, limit, text_area_id, options = {})
  1122 + content_tag(:div, [
  1123 + text_area(object_name, method, { :id => text_area_id, :onkeyup => "limited_text_area('#{text_area_id}', #{limit})" }.merge(options)),
  1124 + content_tag(:p, content_tag(:span, limit) + ' ' + _(' characters left'), :id => text_area_id + '_left'),
  1125 + content_tag(:p, _('Limit of characters reached'), :id => text_area_id + '_limit', :style => 'display: none')
  1126 + ], :class => 'limited-text-area')
  1127 + end
  1128 +
  1129 + def pluralize_without_count(count, singular, plural = nil)
  1130 + count == 1 ? singular : (plural || singular.pluralize)
  1131 + end
  1132 +
  1133 + def unique_with_count(list, connector = 'for')
  1134 + list.sort.inject(Hash.new(0)){|h,i| h[i] += 1; h }.collect{ |x, n| [n, connector, x].join(" ") }.sort
  1135 + end
  1136 +
  1137 + #FIXME Use time_ago_in_words instead of this method if you're using Rails 2.2+
  1138 + def time_ago_as_sentence(from_time, include_seconds = false)
  1139 + to_time = Time.now
  1140 + from_time = from_time.to_time if from_time.respond_to?(:to_time)
  1141 + to_time = to_time.to_time if to_time.respond_to?(:to_time)
  1142 + distance_in_minutes = (((to_time - from_time).abs)/60).round
  1143 + distance_in_seconds = ((to_time - from_time).abs).round
  1144 + case distance_in_minutes
  1145 + when 0..1
  1146 + return (distance_in_minutes == 0) ? _('less than a minute') : _('1 minute') unless include_seconds
  1147 + case distance_in_seconds
  1148 + when 0..4 then _('less than 5 seconds')
  1149 + when 5..9 then _('less than 10 seconds')
  1150 + when 10..19 then _('less than 20 seconds')
  1151 + when 20..39 then _('half a minute')
  1152 + when 40..59 then _('less than a minute')
  1153 + else _('1 minute')
  1154 + end
  1155 +
  1156 + when 2..44 then _('%{distance} minutes') % { :distance => distance_in_minutes }
  1157 + when 45..89 then _('about 1 hour')
  1158 + when 90..1439 then _('about %{distance} hours') % { :distance => (distance_in_minutes.to_f / 60.0).round }
  1159 + when 1440..2879 then _('1 day')
  1160 + when 2880..43199 then _('%{distance} days') % { :distance => (distance_in_minutes / 1440).round }
  1161 + when 43200..86399 then _('about 1 month')
  1162 + when 86400..525599 then _('%{distance} months') % { :distance => (distance_in_minutes / 43200).round }
  1163 + when 525600..1051199 then _('about 1 year')
  1164 + else _('over %{distance} years') % { :distance => (distance_in_minutes / 525600).round }
  1165 + end
  1166 + end
  1167 +
  1168 + def comment_balloon(options = {}, &block)
  1169 + wrapper = content_tag(:div, capture(&block), :class => 'comment-balloon-content')
  1170 + (1..8).to_a.reverse.each { |i| wrapper = content_tag(:div, wrapper, :class => "comment-wrapper-#{i}") }
  1171 + classes = options.delete(:class) || options.delete("class") || ''
  1172 + concat(content_tag('div', wrapper + tag('br', :style => 'clear: both;'), { :class => 'comment-balloon ' + classes.to_s }.merge(options)), block.binding)
  1173 + end
  1174 +
  1175 + def display_source_info(page)
  1176 + if !page.source.blank?
  1177 + source_url = link_to(page.source_name.blank? ? page.source : page.source_name, page.source)
  1178 + elsif page.reference_article
  1179 + source_url = link_to(page.reference_article.profile.name, page.reference_article.url)
  1180 + end
  1181 + content_tag(:div, _('Source: %s') % source_url, :id => 'article-source') unless source_url.nil?
  1182 + end
  1183 +
  1184 + def task_information(task)
  1185 + values = {}
  1186 + values.merge!({:requestor => link_to(task.requestor.name, task.requestor.public_profile_url)}) if task.requestor
  1187 + values.merge!({:subject => content_tag('span', task.subject, :class=>'task_target')}) if task.subject
  1188 + values.merge!({:linked_subject => link_to(content_tag('span', task.linked_subject[:text], :class => 'task_target'), task.linked_subject[:url])}) if task.linked_subject
  1189 + values.merge!(task.information[:variables]) if task.information[:variables]
  1190 + task.information[:message] % values
  1191 + end
  1192 +
  1193 + def add_zoom_to_images
  1194 + if environment.enabled?(:show_zoom_button_on_article_images)
  1195 + stylesheet_link_tag('fancybox') +
  1196 + javascript_include_tag('jquery.fancybox-1.3.4.pack') +
  1197 + javascript_tag("jQuery(function($) {
  1198 + $('#article .article-body img').each( function(index) {
  1199 + var original = original_image_dimensions($(this).attr('src'));
  1200 + if ($(this).width() < original['width'] || $(this).height() < original['height']) {
  1201 + $(this).wrap('<div class=\"zoomable-image\" />');
  1202 + $(this).parent('.zoomable-image').attr('style', $(this).attr('style'));
  1203 + $(this).attr('style', '');
  1204 + $(this).after(\'<a href=\"' + $(this).attr('src') + '\" class=\"zoomify-image\"><span class=\"zoomify-text\">%s</span></a>');
  1205 + }
  1206 + });
  1207 + $('.zoomify-image').fancybox();
  1208 + });" % _('Zoom in'))
  1209 + end
  1210 + end
  1211 +
963 1212 end
... ...
app/helpers/article_helper.rb
... ... @@ -8,12 +8,13 @@ module ArticleHelper
8 8 'div',
9 9 check_box(:article, :published) +
10 10 content_tag('label', _('This article must be published (visible to other people)'), :for => 'article_published')
11   - ) +
  11 + ) + (article.parent && article.parent.forum? && controller.action_name == 'new' ?
  12 + hidden_field_tag('article[accept_comments]', 1) :
12 13 content_tag(
13 14 'div',
14 15 check_box(:article, :accept_comments) +
15   - content_tag('label', _('I want to receive comments about this article'), :for => 'article_accept_comments')
16   - ) +
  16 + content_tag('label', (article.parent && article.parent.forum? ? _('This topic is opened for replies') : _('I want to receive comments about this article')), :for => 'article_accept_comments')
  17 + )) +
17 18 content_tag(
18 19 'div',
19 20 check_box(:article, :notify_comments) +
... ...
app/helpers/assets_helper.rb
... ... @@ -9,7 +9,7 @@ module AssetsHelper
9 9 [ options.merge(:asset => 'products'), "icon-menu-product", _('Products') ],
10 10 [ options.merge(:asset => 'enterprises'), "icon-menu-enterprise", __('Enterprises') ],
11 11 [ options.merge(:asset => 'communities'), "icon-menu-community", __('Communities') ],
12   - [ options.merge(:asset => 'events'), "icon-menu-events", __('Events') ],
  12 + [ options.merge(:asset => 'events'), "icon-event", __('Events') ],
13 13  
14 14 ].select do |target, css_class, name|
15 15 !environment.enabled?('disable_asset_' + target[:asset])
... ...
app/helpers/block_helper.rb
... ... @@ -3,7 +3,7 @@ module BlockHelper
3 3 def block_title(title)
4 4 tag_class = 'block-title'
5 5 tag_class += ' empty' if title.empty?
6   - content_tag 'h3', title, :class => tag_class
  6 + content_tag 'h3', content_tag('span', title), :class => tag_class
7 7 end
8 8  
9 9 end
... ...
app/helpers/blog_helper.rb
... ... @@ -11,7 +11,7 @@ module BlogHelper
11 11 end
12 12  
13 13 def cms_label_for_edit
14   - _('Edit blog')
  14 + _('Configure blog')
15 15 end
16 16  
17 17 def list_posts(articles, format = 'full')
... ... @@ -48,7 +48,7 @@ module BlogHelper
48 48  
49 49 def display_short_format(article)
50 50 html = content_tag('div',
51   - article.first_paragraph +
  51 + article.lead +
52 52 content_tag('div',
53 53 link_to_comments(article) +
54 54 link_to( _('Read more'), article.url),
... ...
app/helpers/boxes_helper.rb
... ... @@ -65,7 +65,7 @@ module BoxesHelper
65 65 end
66 66  
67 67 def display_box_content(box, main_content)
68   - context = { :article => @page }
  68 + context = { :article => @page, :request_path => request.path, :locale => locale }
69 69 box_decorator.select_blocks(box.blocks, context).map { |item| display_block(item, main_content) }.join("\n") + box_decorator.block_target(box)
70 70 end
71 71  
... ...
app/helpers/catalog_helper.rb
1 1 module CatalogHelper
2 2  
3 3 include DisplayHelper
  4 +include ManageProductsHelper
4 5  
5 6 def display_products_list(profile, products)
6 7 data = ''
... ... @@ -19,7 +20,7 @@ include DisplayHelper
19 20 content_tag('h1', _('Products/Services')) + content_tag('ul', data, :id => 'product_list')
20 21 end
21 22  
22   -private
  23 + private
23 24  
24 25 def product_category_name(profile, product_category)
25 26 if profile.enabled?
... ...
app/helpers/categories_helper.rb
... ... @@ -34,8 +34,7 @@ module CategoriesHelper
34 34  
35 35 def select_category_type(field)
36 36 value = params[field]
37   - types = TYPES.select { |title,typename| environment.category_types.include?(typename) }
38   - labelled_form_field(_('Type of category'), select_tag('type', options_for_select(types, value)))
  37 + labelled_form_field(_('Type of category'), select_tag('type', options_for_select(TYPES, value)))
39 38 end
40 39  
41 40 end
... ...
app/helpers/chat_helper.rb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +module ChatHelper
  2 +
  3 + def user_status_menu(icon_class, status)
  4 + links = [
  5 + ['icon-menu-online', _('Online'), 'chat-connect'],
  6 + ['icon-menu-busy', _('Busy'), 'chat-busy'],
  7 + ['icon-menu-offline', _('Sign out of chat'), 'chat-disconnect'],
  8 + ]
  9 + content_tag('span',
  10 + link_to(content_tag('span', status) + ui_icon('ui-icon-triangle-1-s'),
  11 + '#',
  12 + :onclick => 'toggleMenu(this); return false',
  13 + :class => icon_class + ' simplemenu-trigger'
  14 + ) +
  15 + content_tag('ul',
  16 + links.map{|link| content_tag('li', link_to(link[1], '#', :class => link[0], :id => link[2], 'data-jid' => user.jid), :class => 'simplemenu-item') }.join("\n"),
  17 + :style => 'display: none; z-index: 100',
  18 + :class => 'simplemenu-submenu'
  19 + ),
  20 + :class => 'user-status'
  21 + )
  22 + end
  23 +
  24 +end
... ...
app/helpers/cms_helper.rb
... ... @@ -27,4 +27,34 @@ module CmsHelper
27 27 article_helper.custom_options_for_article(article)
28 28 end
29 29  
  30 + def link_to_article(article)
  31 + article_name = short_filename(article.title, 30)
  32 + if article.folder?
  33 + link_to article_name, {:action => 'view', :id => article.id}, :class => icon_for_article(article)
  34 + else
  35 + if article.image?
  36 + image_tag(icon_for_article(article)) + link_to(article_name, article.url)
  37 + else
  38 + link_to article_name, article.url, :class => icon_for_article(article)
  39 + end
  40 + end
  41 + end
  42 +
  43 + def display_spread_button(profile, article)
  44 + if profile.person?
  45 + button_without_text :spread, _('Spread this'), :action => 'publish', :id => article.id
  46 + elsif profile.community? && environment.portal_community
  47 + button_without_text :spread, _('Spread this'), :action => 'publish_on_portal_community', :id => article.id
  48 + end
  49 + end
  50 +
  51 + def display_delete_button(article)
  52 + confirm_message = if article.folder?
  53 + _('Are you sure that you want to remove this folder? Note that all the items inside it will also be removed!')
  54 + else
  55 + _('Are you sure that you want to remove this item?')
  56 + end
  57 +
  58 + button_without_text :delete, _('Delete'), { :action => 'destroy', :id => article.id }, :method => :post, :confirm => confirm_message
  59 + end
30 60 end
... ...
app/helpers/content_viewer_helper.rb
1 1 module ContentViewerHelper
2 2  
3 3 include BlogHelper
  4 + include ForumHelper
4 5  
5 6 def number_of_comments(article)
6 7 n = article.comments.size
... ... @@ -20,7 +21,7 @@ module ContentViewerHelper
20 21 title = content_tag('h1', link_to(article.name, article.url), :class => 'title')
21 22 end
22 23 comments = args[:no_comments] ? '' : (("- %s") % link_to_comments(article))
23   - title << content_tag('span', _("%s, by %s %s") % [show_date(article.published_at), link_to(article.author.name, article.author.url), comments], :class => 'created-at')
  24 + title << content_tag('span', _("%s, by %s %s") % [show_date(article.published_at), link_to(article.author_name, article.author.url), comments], :class => 'created-at')
24 25 end
25 26 title
26 27 end
... ... @@ -30,8 +31,20 @@ module ContentViewerHelper
30 31 end
31 32  
32 33 def image_label(image)
33   - text = image.title || image.abstract
  34 + text = image.abstract || image.title
34 35 text && (text.first(40) + (text.size > 40 ? '…' : ''))
35 36 end
36 37  
  38 + def article_translations(article)
  39 + unless article.native_translation.translations.empty?
  40 + links = (article.native_translation.translations + [article.native_translation]).map do |translation|
  41 + { Noosfero.locales[translation.language] => { :href => url_for(translation.url) } }
  42 + end
  43 + content_tag(:div, link_to(_('Translations'), '#',
  44 + :onclick => "toggleSubmenu(this, '#{_('Translations')}', #{links.to_json}); return false",
  45 + :class => 'article-translations-menu simplemenu-trigger up'),
  46 + :class => 'article-translations')
  47 + end
  48 + end
  49 +
37 50 end
... ...
app/helpers/dates_helper.rb
... ... @@ -36,7 +36,7 @@ module DatesHelper
36 36 # formats a datetime for displaying.
37 37 def show_time(time)
38 38 if time
39   - _('%{day} %{month} %{year}, %{hour}:%{minutes}') % { :year => time.year, :month => month_name(time.month), :day => time.day, :hour => time.hour, :minutes => time.min }
  39 + _('%{day} %{month} %{year}, %{hour}:%{minutes}') % { :year => time.year, :month => month_name(time.month), :day => time.day, :hour => time.hour, :minutes => time.strftime("%M") }
40 40 else
41 41 ''
42 42 end
... ... @@ -46,7 +46,7 @@ module DatesHelper
46 46 if (date1 == date2) || (date2.nil?)
47 47 show_date(date1)
48 48 else
49   - _('from %s to %s') % [show_date(date1), show_date(date2)]
  49 + _('from %{date1} to %{date2}') % {:date1 => show_date(date1), :date2 => show_date(date2)}
50 50 end
51 51 end
52 52  
... ...
app/helpers/display_helper.rb
... ... @@ -2,12 +2,16 @@ module DisplayHelper
2 2  
3 3 def link_to_product(product, opts={})
4 4 return _('No product') unless product
5   - target = product.enterprise.enabled? ? product.enterprise.public_profile_url.merge(:controller => 'catalog', :action => 'show', :id => product) : product.enterprise.url
  5 + target = product_path(product)
6 6 link_to content_tag( 'span', product.name ),
7 7 target,
8 8 opts
9 9 end
10 10  
  11 + def product_path(product)
  12 + product.enterprise.enabled? ? product.enterprise.public_profile_url.merge(:controller => 'manage_products', :action => 'show', :id => product) : product.enterprise.url
  13 + end
  14 +
11 15 def link_to_category(category, full = true)
12 16 return _('Uncategorized product') unless category
13 17 name = full ? category.full_name(' &rarr; ') : category.name
... ... @@ -27,9 +31,10 @@ module DisplayHelper
27 31 gsub( /\n\s*\n/, ' <p/> ' ).
28 32 gsub( /\n/, ' <br/> ' ).
29 33 gsub( /(^|\s)(www\.[^\s])/, '\1http://\2' ).
30   - gsub( /(https?:\/\/([^\s]+))/,
31   - '<a href="\1" target="_blank" rel="nofolow" onclick="return confirm(\'' +
32   - escape_javascript( _('Are you sure you want to visit this web site?') ) +
33   - '\n\n\'+this.href)">\2</a>' )
  34 + gsub( /(https?:\/\/([^\s]+))/ ) do
  35 + href, content = $1, $2.scan(/.{4}/).join('&#x200B;')
  36 + content_tag(:a, content, :href => href, :target => '_blank', :rel => 'nofolow',
  37 + :onclick => "return confirm('%s')" % escape_javascript(_('Are you sure you want to visit this web site?')))
  38 + end
34 39 end
35 40 end
... ...
app/helpers/events_helper.rb
... ... @@ -6,7 +6,7 @@ module EventsHelper
6 6 content_tag('h2', title) +
7 7 content_tag('div',
8 8 (events.any? ?
9   - content_tag('table', events.select { |item| item.public? }.map {|item| display_event_in_listing(item)}.join('')) :
  9 + content_tag('table', events.select { |item| item.display_to?(user) }.map {|item| display_event_in_listing(item)}.join('')) :
10 10 content_tag('em', _('No events for this date'), :class => 'no-events')
11 11 ), :id => 'agenda-items'
12 12 )
... ... @@ -15,7 +15,7 @@ module EventsHelper
15 15 def display_event_in_listing(article)
16 16 content_tag(
17 17 'tr',
18   - content_tag('td', link_to(image_tag(icon_for_article(article)) + article.name, article.url)),
  18 + content_tag('td', link_to(article.name, article.url, :class => icon_for_article(article))),
19 19 :class => 'agenda-item'
20 20 )
21 21 end
... ... @@ -26,7 +26,7 @@ module EventsHelper
26 26 # the day itself
27 27 date,
28 28 # is there any events in this date?
29   - events.any? do |event|
  29 + events.select {|event| event.display_to?(user)}.any? do |event|
30 30 event.date_range.include?(date)
31 31 end,
32 32 # is this date in the current month?
... ...
app/helpers/float_helper.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +module FloatHelper
  2 +
  3 + def decimal_to_float( num )
  4 + if num.count('.') == 1 && num.count(',') == 0
  5 + # number like "12.34"
  6 + return num.to_f
  7 + end
  8 +
  9 + if num.count('.') == 0 && num.count(',') == 1
  10 + # number like "12,34"
  11 + return num.tr(',','.').to_f
  12 + end
  13 +
  14 + if num.count('.') > 0 && num.count(',') > 0
  15 + # number like "12.345.678,90" or "12,345,678.90"
  16 + dec_sep = num.tr('0-9','')[-1].chr
  17 + return num.tr('^0-9'+dec_sep,'').tr(dec_sep,'.').to_f
  18 + end
  19 +
  20 + # if you are here is because there is only one
  21 + # separator and this appears 2 times or more.
  22 + # number like "12.345.678" or "12,345,678"
  23 +
  24 + return num.tr(',.','').to_f
  25 + end
  26 +
  27 +end
... ...
app/helpers/folder_helper.rb
1 1 module FolderHelper
2 2  
  3 + include ShortFilename
  4 +
3 5 def list_articles(articles, recursive = false)
4 6 if !articles.blank?
5   - content_tag(
6   - 'table',
7   - content_tag('tr', content_tag('th', _('Title')) + content_tag('th', _('Last update'))) +
8   - articles.map {|item| display_article_in_listing(item, recursive, 0)}.join('')
  7 + articles = articles.paginate(
  8 + :order => "updated_at DESC",
  9 + :per_page => 10,
  10 + :page => params[:npage]
9 11 )
  12 +
  13 + render :file => 'shared/articles_list', :locals => {:articles => articles, :recursive => recursive}
10 14 else
11 15 content_tag('em', _('(empty folder)'))
12 16 end
... ... @@ -17,9 +21,14 @@ module FolderHelper
17 21 end
18 22  
19 23 def display_article_in_listing(article, recursive = false, level = 0)
  24 + article_link = if article.image?
  25 + link_to('&nbsp;' * (level * 4) + image_tag(icon_for_article(article)) + short_filename(article.name), article.url.merge(:view => true))
  26 + else
  27 + link_to('&nbsp;' * (level * 4) + short_filename(article.name), article.url.merge(:view => true), :class => icon_for_article(article))
  28 + end
20 29 result = content_tag(
21 30 'tr',
22   - content_tag('td', link_to(('&nbsp;' * (level * 4) ) + image_tag(icon_for_article(article)) + short_filename(article.name), article.url.merge(:view => true)))+
  31 + content_tag('td', article_link )+
23 32 content_tag('td', show_date(article.updated_at), :class => 'last-update'),
24 33 :class => 'sitemap-item'
25 34 )
... ... @@ -31,18 +40,22 @@ module FolderHelper
31 40 end
32 41  
33 42 def icon_for_article(article)
34   - icon = article.icon_name
  43 + icon = article.class.icon_name(article)
35 44 if (icon =~ /\//)
36 45 icon
37 46 else
38   - if File.exists?(File.join(RAILS_ROOT, 'public', 'images', 'icons-mime', "#{icon}.png"))
39   - "icons-mime/#{icon}.png"
40   - else
41   - "icons-mime/unknown.png"
  47 + klasses = 'icon icon-' + icon
  48 + if article.kind_of?(UploadedFile)
  49 + klasses += ' icon-upload-file'
42 50 end
  51 + klasses
43 52 end
44 53 end
45 54  
  55 + def icon_for_new_article(type)
  56 + "icon-new icon-new%s" % type.constantize.icon_name
  57 + end
  58 +
46 59 def custom_options_for_article(article)
47 60 @article = article
48 61 content_tag('h4', _('Options')) +
... ... @@ -69,11 +82,4 @@ module FolderHelper
69 82 _('Edit folder')
70 83 end
71 84  
72   - def short_filename(filename, limit_chars = 43)
73   - return filename if filename.size <= limit_chars
74   - extname = File.extname(filename)
75   - basename = File.basename(filename,extname)
76   - str_complement = '(...)'
77   - return basename[0..(limit_chars - extname.size - str_complement.size - 1)] + str_complement + extname
78   - end
79 85 end
... ...
app/helpers/forms_helper.rb
... ... @@ -41,8 +41,7 @@ module FormsHelper
41 41 the_class << ' ' << html_options[:class]
42 42 end
43 43  
44   - # FIXME: should be in stylesheet
45   - bt_submit = submit_tag(label, html_options.merge(:style => 'height:28px; cursor:pointer', :class => the_class))
  44 + bt_submit = submit_tag(label, html_options.merge(:class => the_class))
46 45  
47 46 bt_submit + bt_cancel
48 47 end
... ... @@ -60,6 +59,10 @@ module FormsHelper
60 59  
61 60 state_id = 'state-' + FormsHelper.next_id_number
62 61 city_id = 'city-' + FormsHelper.next_id_number
  62 +
  63 + if states.length < 1
  64 + return
  65 + end
63 66  
64 67 if simple
65 68 states = [State.new(:name => _('Select the State'))] + states
... ... @@ -108,6 +111,18 @@ module FormsHelper
108 111 ))
109 112 end
110 113  
  114 + def options_for_select_with_title(container, selected = nil)
  115 + container = container.to_a if Hash === container
  116 +
  117 + options_for_select = container.inject([]) do |options, element|
  118 + text, value = option_text_and_value(element)
  119 + selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
  120 + options << %(<option title="#{html_escape(text.to_s)}" value="#{html_escape(value.to_s)}"#{selected_attribute}>#{html_escape(text.to_s)}</option>)
  121 + end
  122 +
  123 + options_for_select.join("\n")
  124 + end
  125 +
111 126 protected
112 127 def self.next_id_number
113 128 if defined? @@id_num
... ...
app/helpers/forum_helper.rb 0 → 100644
... ... @@ -0,0 +1,51 @@
  1 +module ForumHelper
  2 +
  3 + def cms_label_for_new_children
  4 + _('New discussion topic')
  5 + end
  6 +
  7 + def cms_label_for_edit
  8 + _('Configure forum')
  9 + end
  10 +
  11 + def list_forum_posts(articles)
  12 + pagination = will_paginate(articles, {
  13 + :param_name => 'npage',
  14 + :prev_label => _('&laquo; Newer posts'),
  15 + :next_label => _('Older posts &raquo;')
  16 + })
  17 + content = [content_tag('tr',
  18 + content_tag('th', _('Discussion topic')) +
  19 + content_tag('th', _('Posts')) +
  20 + content_tag('th', _('Last post'))
  21 + )
  22 + ]
  23 + artic_len = articles.length
  24 + articles.each_with_index{ |art,i|
  25 + css_add = [ 'position-'+(i+1).to_s() ]
  26 + position = (i%2 == 0) ? 'odd-post' : 'even-post'
  27 + css_add << 'first' if i == 0
  28 + css_add << 'last' if i == (artic_len-1)
  29 + css_add << 'not-published' if !art.published?
  30 + css_add << position
  31 + content << content_tag('tr',
  32 + content_tag('td', link_to(art.title, art.url)) +
  33 + content_tag('td', link_to(art.comments.count, art.url.merge(:anchor => 'comments_list'))) +
  34 + content_tag('td', last_topic_update(art)),
  35 + :class => 'forum-post ' + css_add.join(' '),
  36 + :id => "post-#{art.id}"
  37 + )
  38 + }
  39 + content_tag('table', content) + (pagination or '')
  40 + end
  41 +
  42 + def last_topic_update(article)
  43 + info = article.info_from_last_update
  44 + if info[:author_url]
  45 + time_ago_as_sentence(info[:date]) + ' ' + _('ago') + ' ' + _('by') + ' ' + link_to(info[:author_name], info[:author_url])
  46 + else
  47 + time_ago_as_sentence(info[:date]) + ' ' + _('ago') + ' ' + _('by') + ' ' + info[:author_name]
  48 + end
  49 + end
  50 +
  51 +end
... ...
app/helpers/friends_helper.rb
1 1 module FriendsHelper
2 2  
3   - def link_to_import(text, options = {})
4   - options.merge!({:action => 'invite', :import => 1, :wizard => true})
5   - link_to text, options
6   - end
7   -
8   - def pagination_links(collection, options={})
9   - options = {:prev_label => '&laquo; ' + _('Previous'), :next_label => _('Next') + ' &raquo;'}.merge(options)
10   - will_paginate(collection, options)
11   - end
12   -
13 3 end
... ...
app/helpers/manage_products_helper.rb
1 1 module ManageProductsHelper
  2 +
  3 + def remote_function_to_update_categories_selection(container_id, options = {})
  4 + remote_function({
  5 + :update => container_id,
  6 + :url => { :action => "categories_for_selection" },
  7 + :loading => "loading('hierarchy_navigation', '#{ _('loading…') }'); loading('#{container_id}', '&nbsp;')",
  8 + :complete => "loading_done('hierarchy_navigation'); loading_done('#{container_id}')"
  9 + }.merge(options))
  10 + end
  11 +
  12 + def hierarchy_category_item(category, make_links, title = nil)
  13 + title ||= category.name
  14 + if make_links
  15 + link_to(title, '#',
  16 + :title => title,
  17 + :onclick => remote_function_to_update_categories_selection("categories_container_level#{ category.level + 1 }",
  18 + :with => "'category_id=#{ category.id }'"
  19 + )
  20 + )
  21 + else
  22 + title
  23 + end
  24 + end
  25 +
  26 + def hierarchy_category_navigation(current_category, options = {})
  27 + hierarchy = []
  28 + if current_category
  29 + count_chars = 0
  30 + unless options[:hide_current_category]
  31 + hierarchy << current_category.name
  32 + count_chars += current_category.name.length
  33 + end
  34 + ancestors = current_category.ancestors
  35 + toplevel = ancestors.pop
  36 + if toplevel
  37 + count_chars += toplevel.name.length
  38 + end
  39 + ancestors.each do |category|
  40 + if count_chars > 55
  41 + hierarchy << hierarchy_category_item(category, options[:make_links], '( … )')
  42 + break
  43 + else
  44 + hierarchy << hierarchy_category_item(category, options[:make_links])
  45 + end
  46 + count_chars += category.name.length
  47 + end
  48 + if toplevel
  49 + hierarchy << hierarchy_category_item(toplevel, options[:make_links])
  50 + end
  51 + end
  52 + hierarchy.reverse.join(options[:separator] || ' &rarr; ')
  53 + end
  54 +
  55 + def options_for_select_categories(categories, selected = nil)
  56 + categories.sort_by{|cat| cat.name.transliterate}.map do |category|
  57 + selected_attribute = selected.nil? ? '' : (category == selected ? "selected='selected'" : '')
  58 + "<option value='#{category.id}' title='#{category.name}' #{selected_attribute}>#{truncate(category.name, 33) + (category.leaf? ? '': ' &raquo;')}</option>"
  59 + end.join("\n")
  60 + end
  61 +
  62 + def build_selects_for_ancestors(ancestors, current_category)
  63 + current_ancestor = ancestors.shift
  64 + if current_ancestor.nil?
  65 + select_for_new_category(current_category.children, current_category.level + 1)
  66 + else
  67 + content_tag('div',
  68 + select_tag('category_id',
  69 + options_for_select_categories(current_ancestor.siblings + [current_ancestor], current_ancestor),
  70 + :size => 10,
  71 + :onchange => remote_function_to_update_categories_selection("categories_container_level#{ current_ancestor.level + 1 }", :with => "'category_id=' + this.value")
  72 + ) +
  73 + build_selects_for_ancestors(ancestors, current_category),
  74 + :class => 'categories_container',
  75 + :id => "categories_container_level#{ current_ancestor.level }"
  76 + )
  77 + end
  78 + end
  79 +
  80 + def selects_for_all_ancestors(current_category)
  81 + build_selects_for_ancestors(current_category.ancestors.reverse + [current_category], current_category)
  82 + end
  83 +
  84 + def select_for_new_category(categories, level)
  85 + content_tag('div',
  86 + render(:partial => 'categories_for_selection', :locals => { :categories => categories, :level => level }),
  87 + :class => 'categories_container',
  88 + :id => "categories_container_level#{ level }"
  89 + )
  90 + end
  91 +
  92 + def categories_container(categories_selection_html, hierarchy_html = '')
  93 + hidden_field_tag('selected_category_id') +
  94 + content_tag('div', hierarchy_html, :id => 'hierarchy_navigation') +
  95 + content_tag('div', categories_selection_html, :id => 'categories_container_wrapper')
  96 + end
  97 +
  98 + def select_for_categories(categories, level = 0)
  99 + if categories.empty?
  100 + content_tag('div', '', :id => 'no_subcategories')
  101 + else
  102 + select_tag('category_id',
  103 + options_for_select_categories(categories),
  104 + :size => 10,
  105 + :onchange => remote_function_to_update_categories_selection("categories_container_level#{ level + 1 }", :with => "'category_id=' + this.value")
  106 + ) +
  107 + content_tag('div', '', :class => 'categories_container', :id => "categories_container_level#{ level + 1 }")
  108 + end
  109 + end
  110 +
  111 + def edit_link(label, url, html_options = {})
  112 + return '' unless (user && user.has_permission?('manage_products', profile))
  113 + link_to(label, url, html_options)
  114 + end
  115 +
  116 + def edit_product_link_to_remote(product, field, label, html_options = {})
  117 + return '' unless (user && user.has_permission?('manage_products', profile))
  118 + options = html_options.merge(:id => 'link-edit-product-' + field)
  119 + options[:class] = options[:class] ? options[:class] + ' link-to-remote' : 'link-to-remote'
  120 +
  121 + link_to_remote(label,
  122 + {:update => "product-#{field}",
  123 + :url => { :controller => 'manage_products', :action => "edit", :id => product.id, :field => field },
  124 + :method => :get,
  125 + :loading => "loading_for_button('#link-edit-product-#{field}')"},
  126 + options)
  127 + end
  128 +
  129 + def edit_button(type, label, url, html_options = {})
  130 + return '' unless (user && user.has_permission?('manage_products', profile))
  131 + button(type, label, url, html_options)
  132 + end
  133 +
  134 + def edit_product_button_to_remote(product, field, label, html_options = {})
  135 + the_class = 'button with-text icon-edit'
  136 + if html_options.has_key?(:class)
  137 + the_class << ' ' << html_options[:class]
  138 + end
  139 + edit_product_link_to_remote(product, field, label, html_options.merge(:class => the_class))
  140 + end
  141 +
  142 + def edit_ui_button(label, url, html_options = {})
  143 + return '' unless (user && user.has_permission?('manage_products', profile))
  144 + ui_button(label, url, html_options)
  145 + end
  146 +
  147 + def edit_product_ui_button_to_remote(product, field, label, html_options = {})
  148 + return '' unless (user && user.has_permission?('manage_products', profile))
  149 + id = 'edit-product-remote-button-ui-' + field
  150 + options = html_options.merge(:id => id)
  151 +
  152 + ui_button_to_remote(label,
  153 + {:update => "product-#{field}",
  154 + :url => { :controller => 'manage_products', :action => "edit", :id => product.id, :field => field },
  155 + :complete => "$('edit-product-button-ui-#{field}').hide()",
  156 + :method => :get,
  157 + :loading => "loading_for_button('##{id}')"},
  158 + options)
  159 + end
  160 +
  161 + def cancel_edit_product_link(product, field, html_options = {})
  162 + return '' unless (user && user.has_permission?('manage_products', profile))
  163 + button_to_function(:cancel, _('Cancel'), nil, html_options) do |page|
  164 + page.replace_html "product-#{field}", :partial => "display_#{field}", :locals => {:product => product}
  165 + end
  166 + end
  167 +
  168 + def edit_product_category_link(product, html_options = {})
  169 + return '' unless (user && user.has_permission?('manage_products', profile))
  170 + options = html_options.merge(:id => 'link-edit-product-category')
  171 + link_to(_('Change category'), { :action => 'edit_category', :id => product.id}, options)
  172 + end
  173 +
  174 + def display_value(product)
  175 + price = product.price
  176 + return '' if price.blank? || price.zero?
  177 + discount = product.discount
  178 + if discount.blank? || discount.zero?
  179 + result = display_price(_('Price: '), price)
  180 + else
  181 + result = display_price_with_discount(price, product.price_with_discount)
  182 + end
  183 + content_tag('span', content_tag('span', result, :class => 'product-price'), :class => "#{product.available? ? '' : 'un'}available-product")
  184 + end
  185 +
  186 + def display_availability(product)
  187 + if !product.available?
  188 + ui_highlight(_('Product not available!'))
  189 + end
  190 + end
  191 +
  192 + def display_price(label, price)
  193 + content_tag('span', label, :class => 'field-name') +
  194 + content_tag('span', float_to_currency(price), :class => 'field-value')
  195 + end
  196 +
  197 + def display_price_with_discount(price, price_with_discount)
  198 + original_value = content_tag('span', display_price(_('List price: '), price), :class => 'list-price')
  199 + discount_value = content_tag('span', display_price(_('On sale: '), price_with_discount), :class => 'on-sale-price')
  200 + original_value + tag('br') + discount_value
  201 + end
  202 +
  203 + def display_qualifiers(product)
  204 + data = ''
  205 + product.product_qualifiers.each do |pq|
  206 + certified_by = ''
  207 + certifier = pq.certifier
  208 + if certifier
  209 + certifier_name = certifier.link.blank? ? certifier.name : link_to(certifier.name, certifier.link)
  210 + certified_by = _('certified by %s') % certifier_name
  211 + else
  212 + certified_by = _('(Self declared)')
  213 + end
  214 + data << content_tag('li', "✔ #{pq.qualifier.name} #{certified_by}", :class => 'product-qualifiers-item')
  215 + end
  216 + content_tag('ul', data, :id => 'product-qualifiers')
  217 + end
  218 +
  219 + def qualifiers_for_select
  220 + [[_('Select...'), nil]] + environment.qualifiers.map{ |c| [c.name, c.id] }
  221 + end
  222 + def certifiers_for_select(qualifier)
  223 + [[_('Self declared'), nil]] + qualifier.certifiers.map{ |c| [c.name, c.id] }
  224 + end
  225 + def select_qualifiers(product, selected = nil)
  226 + select_tag('selected_qualifier', options_for_select(qualifiers_for_select, selected),
  227 + :onchange => remote_function(
  228 + :url => {:action => 'certifiers_for_selection'},
  229 + :with => "'id=' + value + '&certifier_area=' + jQuery(this).parent().next().attr('id')",
  230 + :before => "small_loading(jQuery(this).parent().next().attr('id'), '&nbsp;')"
  231 + ),
  232 + :id => nil
  233 + )
  234 + end
  235 + def select_certifiers(qualifier, product = nil)
  236 + if qualifier
  237 + selected = product ? product.product_qualifiers.find_by_qualifier_id(qualifier.id).certifier_id : nil
  238 + select_tag("product[qualifiers_list][#{qualifier.id}]", options_for_select(certifiers_for_select(qualifier), selected))
  239 + else
  240 + select_tag("product[qualifiers_list][nil]")
  241 + end
  242 + end
  243 +
  244 + def select_unit(object)
  245 + selected = object.unit.nil? ? '' : object.unit
  246 + select(object.class.name.downcase, 'unit', Product::UNITS.map{|unit| [_(unit[0]), unit[0]]}, {:selected => selected, :include_blank => _('Select the unit')})
  247 + end
  248 +
  249 + def input_icon(input)
  250 + if input.is_from_solidarity_economy?
  251 + hint = _('Product from solidarity economy')
  252 + image_tag("/images/solidarity-economy.png", :class => 'solidatiry-economy-icon', :alt => hint, :title => hint)
  253 + end
  254 + end
  255 +
  256 + def display_price_by(unit)
  257 + selected_unit = content_tag('span', unit, :class => 'selected-unit')
  258 + content_tag('span', _('by') + ' ' + selected_unit, :class => 'price-by-unit')
  259 + end
  260 +
  261 + def label_amount_used(input)
  262 + product_unit = input.product.unit
  263 + if product_unit.blank?
  264 + _('Amount used in this product or service')
  265 + else
  266 + _('Amount used by %s of this product or service') % _(product_unit)
  267 + end
  268 + end
  269 +
  270 + def display_unit(input)
  271 + input_amount_used = content_tag('span', input.formatted_amount, :class => 'input-amount-used')
  272 + return input_amount_used if input.unit.blank?
  273 + units = Product::UNITS.find {|unit| unit[0] == input.unit}
  274 + n_('1 %{singular_unit}', '%{num} %{plural_unit}', input.amount_used.to_f) % { :num => input_amount_used, :singular_unit => content_tag('span', units[0], :class => 'input-unit'), :plural_unit => content_tag('span', units[1], :class => 'input-unit') }
  275 + end
2 276 end
... ...
app/helpers/profile_helper.rb
... ... @@ -15,9 +15,12 @@ module ProfileHelper
15 15 end
16 16 end
17 17  
18   - def pagination_links(collection, options={})
19   - options = {:prev_label => '&laquo; ' + _('Previous'), :next_label => _('Next') + ' &raquo;'}.merge(options)
20   - will_paginate(collection, options)
21   - end
  18 + def render_tabs(tabs)
  19 + titles = tabs.inject(''){ |result, tab| result << content_tag(:li, link_to(tab[:title], '#'+tab[:id]), :class => 'tab') }
  20 + contents = tabs.inject(''){ |result, tab| result << content_tag(:div, tab[:content], :id => tab[:id]) }
22 21  
  22 + content_tag :div, :class => 'ui-tabs' do
  23 + content_tag(:ul, titles) + contents
  24 + end
  25 + end
23 26 end
... ...
app/helpers/search_helper.rb
... ... @@ -98,11 +98,6 @@ module SearchHelper
98 98 ), :class => 'profile-info')
99 99 end
100 100  
101   - def pagination_links(collection, options={})
102   - options = {:prev_label => '&laquo; ' + _('Previous'), :next_label => _('Next') + ' &raquo;'}.merge(options)
103   - will_paginate(collection, options)
104   - end
105   -
106 101 def product_categories_menu(asset, product_category, object_ids = nil)
107 102 cats = ProductCategory.menu_categories(@product_category, environment)
108 103 cats += cats.select { |c| c.children_count > 0 }.map(&:children).flatten
... ...
app/models/action_tracker_notification.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class ActionTrackerNotification < ActiveRecord::Base
  2 +
  3 + belongs_to :profile
  4 + belongs_to :action_tracker, :class_name => 'ActionTracker::Record', :foreign_key => 'action_tracker_id'
  5 +
  6 + validates_presence_of :profile_id, :action_tracker_id
  7 + validates_uniqueness_of :action_tracker_id, :scope => :profile_id
  8 +
  9 +end
  10 +
  11 +ActionTracker::Record.has_many :action_tracker_notifications, :class_name => 'ActionTrackerNotification', :foreign_key => 'action_tracker_id', :dependent => :destroy
... ...
app/models/add_friend.rb
1 1 class AddFriend < Task
2 2  
3   - acts_as_having_settings :group_for_person, :group_for_friend, :field => :data
  3 + settings_items :group_for_person, :group_for_friend
4 4  
5 5 validates_presence_of :requestor_id, :target_id
6 6  
7 7 validates_uniqueness_of :target_id, :scope => [ :requestor_id ]
8 8  
9   - validates_length_of :group_for_person, :group_for_friend, :maximum => 150, :allow_nil => true
  9 + validates_length_of :group_for_person, :group_for_friend, :maximum => 150, :allow_nil => true
10 10  
11 11 alias :person :requestor
12 12 alias :person= :requestor=
... ... @@ -15,21 +15,37 @@ class AddFriend &lt; Task
15 15 alias :friend= :target=
16 16  
17 17 def perform
18   - requestor.add_friend(target, group_for_person)
19 18 target.add_friend(requestor, group_for_friend)
20   - end
21   -
22   - def description
23   - _('%s wants to be your friend.') % requestor.name
  19 + requestor.add_friend(target, group_for_person)
24 20 end
25 21  
26 22 def permission
27 23 :manage_friends
28 24 end
29 25  
  26 + def target_notification_description
  27 + _('%{requestor} wants to be your friend.') % {:requestor => requestor.name}
  28 + end
  29 +
30 30 def target_notification_message
31   - description + "\n\n" +
  31 + target_notification_description + "\n\n" +
32 32 _('You need to login to %{system} in order to accept %{requestor} as your friend.') % { :system => target.environment.name, :requestor => requestor.name }
33 33 end
34 34  
  35 + def title
  36 + _("New friend")
  37 + end
  38 +
  39 + def information
  40 + {:message => _('%{requestor} wants to be your friend.') }
  41 + end
  42 +
  43 + def accept_details
  44 + true
  45 + end
  46 +
  47 + def icon
  48 + {:type => :profile_image, :profile => requestor, :url => requestor.url}
  49 + end
  50 +
35 51 end
... ...
app/models/add_member.rb
... ... @@ -8,23 +8,39 @@ class AddMember &lt; Task
8 8 alias :organization :target
9 9 alias :organization= :target=
10 10  
11   - acts_as_having_settings :roles, :field => :data
  11 + settings_items :roles
12 12  
13 13 def perform
14 14 self.roles ||= [Profile::Roles.member(organization.environment.id).id]
15 15 target.affiliate(requestor, self.roles.select{|r| !r.to_i.zero? }.map{|i| Role.find(i)})
16 16 end
17 17  
18   - def description
19   - _('%s wants to be a member of "%s".') % [requestor.name, organization.name]
  18 + def title
  19 + _("New member")
  20 + end
  21 +
  22 + def information
  23 + {:message => _('%{requestor} wants to be a member of this community.')}
  24 + end
  25 +
  26 + def accept_details
  27 + true
  28 + end
  29 +
  30 + def icon
  31 + {:type => :profile_image, :profile => requestor, :url => requestor.url}
20 32 end
21 33  
22 34 def permission
23 35 :manage_memberships
24 36 end
25 37  
  38 + def target_notification_description
  39 + _('%{requestor} wants to be a member of this community.') % {:requestor => requestor.name}
  40 + end
  41 +
26 42 def target_notification_message
27   - description + "\n\n" +
  43 + target_notification_description + "\n\n" +
28 44 _('You will need login to %{system} in order to accept or reject %{requestor} as a member of %{organization}.') % { :system => target.environment.name, :requestor => requestor.name, :organization => organization.name }
29 45 end
30 46  
... ...
app/models/approve_article.rb
1 1 class ApproveArticle < Task
2   - serialize :data, Hash
3   -
4 2 validates_presence_of :requestor_id, :target_id
5 3  
6   - def description
7   - _('%{author} wants to publish "%{article}" on %{community}') % { :author => requestor.name, :article => article_title, :community => target.name }
8   - end
9   -
10 4 def article_title
11 5 article ? article.title : _('(The original text was removed)')
12 6 end
13 7  
14   - def data
15   - self[:data] ||= {}
16   - end
17   -
18 8 def article
19 9 Article.find_by_id data[:article_id]
20 10 end
... ... @@ -24,53 +14,97 @@ class ApproveArticle &lt; Task
24 14 end
25 15  
26 16 def name
27   - data[:name]
  17 + data[:name].blank? ? (article ? article.name : _("Article removed.")) : data[:name]
28 18 end
29 19  
30 20 def name= value
31 21 data[:name] = value
32 22 end
33 23  
34   - def closing_statment
35   - data[:closing_statment]
  24 + settings_items :closing_statment, :article_parent_id, :highlighted
  25 +
  26 + def article_parent
  27 + Article.find_by_id article_parent_id.to_i
36 28 end
37   -
38   - def closing_statment= value
39   - data[:closing_statment] = value
  29 +
  30 + def article_parent= value
  31 + self.article_parent_id = value.id
40 32 end
41 33  
42   - def article_parent_id= value
43   - data[:parent_id] = value
  34 + def abstract= value
  35 + data[:abstract] = value
44 36 end
45 37  
46   - def article_parent_id
47   - data[:parent_id]
  38 + def abstract
  39 + data[:abstract].blank? ? (article ? article.abstract : '') : data[:abstract]
48 40 end
49 41  
50   - def article_parent
51   - Article.find_by_id article_parent_id.to_i
  42 + def body= value
  43 + data[:body] = value
52 44 end
53 45  
54   - def article_parent= value
55   - self.article_parent_id = value.id
  46 + def body
  47 + data[:body].blank? ? (article ? article.body : "") : data[:body]
56 48 end
57 49  
58   - def highlighted= value
59   - data[:highlighted] = value
  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)
60 52 end
61 53  
62   - def highlighted
63   - data[:highlighted]
  54 + def title
  55 + _("New article")
64 56 end
65 57  
66   - def perform
67   - PublishedArticle.create!(:name => name, :profile => target, :reference_article => article, :parent => article_parent, :highlighted => highlighted)
  58 + def icon
  59 + result = {:type => :defined_image, :src => '/images/icons-app/article-minor.png', :name => name}
  60 + result.merge({:url => article.url}) if article
  61 + return result
  62 + end
  63 +
  64 + def linked_subject
  65 + {:text => name, :url => article.url} if article
  66 + end
  67 +
  68 + def information
  69 + if article
  70 + {:message => _('%{requestor} wants to publish the article: %{linked_subject}.')}
  71 + else
  72 + {:message => _("The article was removed.")}
  73 + end
  74 + end
  75 +
  76 + def accept_details
  77 + true
  78 + end
  79 +
  80 + def default_decision
  81 + if article
  82 + 'skip'
  83 + else
  84 + 'reject'
  85 + end
  86 + end
  87 +
  88 + def accept_disabled?
  89 + article.blank?
  90 + end
  91 +
  92 + def target_notification_description
  93 + _('%{requestor} wants to publish the article: %{article}.') % {:requestor => requestor.name, :article => article.name}
68 94 end
69 95  
70 96 def target_notification_message
71 97 return nil if target.organization? && !target.moderated_articles?
72   - description + "\n\n" +
  98 + target_notification_description + "\n\n" +
73 99 _('You need to login on %{system} in order to approve or reject this article.') % { :system => target.environment.name }
74 100 end
75 101  
  102 + def task_finished_message
  103 + if !closing_statment.blank?
  104 + _("Your request for publishing the article \"%{article}\" was approved. Here is the comment left by the admin who approved your article:\n\n%{comment} ") % {:article => name, :comment => closing_statment}
  105 + else
  106 + _('Your request for publishing the article "%{article}" was approved.') % {:article => name}
  107 + end
  108 + end
  109 +
76 110 end
... ...
app/models/article.rb
  1 +require 'hpricot'
  2 +
1 3 class Article < ActiveRecord::Base
2 4  
  5 + track_actions :create_article, :after_create, :keep_params => [:name, :url], :if => Proc.new { |a| a.is_trackable? && !a.image? }, :custom_target => :action_tracker_target
  6 + track_actions :update_article, :before_update, :keep_params => [:name, :url], :if => Proc.new { |a| a.is_trackable? && (a.body_changed? || a.name_changed?) }, :custom_target => :action_tracker_target
  7 + track_actions :remove_article, :before_destroy, :keep_params => [:name], :if => Proc.new { |a| a.is_trackable? }, :custom_target => :action_tracker_target
  8 +
3 9 # xss_terminate plugin can't sanitize array fields
4 10 before_save :sanitize_tag_list
5 11  
... ... @@ -19,14 +25,25 @@ class Article &lt; ActiveRecord::Base
19 25 acts_as_having_settings :field => :setting
20 26  
21 27 settings_items :display_hits, :type => :boolean, :default => true
  28 + settings_items :author_name, :type => :string, :default => ""
22 29  
23 30 belongs_to :reference_article, :class_name => "Article", :foreign_key => 'reference_article_id'
24 31  
  32 + has_many :translations, :class_name => 'Article', :foreign_key => :translation_of_id
  33 + belongs_to :translation_of, :class_name => 'Article', :foreign_key => :translation_of_id
  34 + before_destroy :rotate_translations
  35 +
25 36 before_create do |article|
26 37 article.published_at = article.created_at if article.published_at.nil?
  38 + if article.reference_article && !article.parent
  39 + parent = article.reference_article.parent
  40 + if parent && parent.blog? && article.profile.has_blog?
  41 + article.parent = article.profile.blog
  42 + end
  43 + end
27 44 end
28 45  
29   - xss_terminate :only => [ :name ], :on => 'validation'
  46 + xss_terminate :only => [ :name ], :on => 'validation', :with => 'white_list'
30 47  
31 48 named_scope :in_category, lambda { |category|
32 49 {:include => 'categories', :conditions => { 'categories.id' => category.id }}
... ... @@ -38,20 +55,17 @@ class Article &lt; ActiveRecord::Base
38 55 ]
39 56 }}
40 57  
41   - def self.first_day_of_month(date)
42   - date ||= Date.today
43   - Date.new(date.year, date.month, 1)
44   - end
45   -
46   - def self.last_day_of_month(date)
47   - date ||= Date.today
48   - date >>= 1
49   - Date.new(date.year, date.month, 1) - 1.day
50   - end
51   -
52 58 URL_FORMAT = /\A(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?\Z/ix
53 59  
54 60 validates_format_of :external_link, :with => URL_FORMAT, :if => lambda { |article| !article.external_link.blank? }
  61 + validate :known_language
  62 + validate :used_translation
  63 + validate :native_translation_must_have_language
  64 + validate :translation_must_have_language
  65 +
  66 + def is_trackable?
  67 + self.published? && self.notifiable? && self.advertise?
  68 + end
55 69  
56 70 def external_link=(link)
57 71 if !link.blank? && link !~ /^[a-z]+:\/\//i
... ... @@ -60,6 +74,9 @@ class Article &lt; ActiveRecord::Base
60 74 self[:external_link] = link
61 75 end
62 76  
  77 + def action_tracker_target
  78 + self.profile
  79 + end
63 80  
64 81 def self.human_attribute_name(attrib)
65 82 case attrib.to_sym
... ... @@ -120,9 +137,9 @@ class Article &lt; ActiveRecord::Base
120 137  
121 138 # retrieves all articles belonging to the given +profile+ that are not
122 139 # sub-articles of any other article.
123   - def self.top_level_for(profile)
124   - self.find(:all, :conditions => [ 'parent_id is null and profile_id = ?', profile.id ])
125   - end
  140 + named_scope :top_level_for, lambda { |profile|
  141 + {:conditions => [ 'parent_id is null and profile_id = ?', profile.id ]}
  142 + }
126 143  
127 144 # retrieves the latest +limit+ articles, sorted from the most recent to the
128 145 # oldest.
... ... @@ -182,7 +199,7 @@ class Article &lt; ActiveRecord::Base
182 199 # to return their specific icons.
183 200 #
184 201 # FIXME use mime_type and generate this name dinamically
185   - def icon_name
  202 + def self.icon_name(article = nil)
186 203 'text-html'
187 204 end
188 205  
... ... @@ -206,10 +223,24 @@ class Article &lt; ActiveRecord::Base
206 223 name
207 224 end
208 225  
  226 + include ActionView::Helpers::TextHelper
  227 + def short_title
  228 + truncate self.title, 15, '...'
  229 + end
  230 +
209 231 def belongs_to_blog?
210 232 self.parent and self.parent.blog?
211 233 end
212 234  
  235 + def info_from_last_update
  236 + last_comment = comments.last
  237 + if last_comment
  238 + {:date => last_comment.created_at, :author_name => last_comment.author_name, :author_url => last_comment.author_url}
  239 + else
  240 + {:date => updated_at, :author_name => author.name, :author_url => author.url}
  241 + end
  242 + end
  243 +
213 244 def url
214 245 @url ||= self.profile.url.merge(:page => path.split('/'))
215 246 end
... ... @@ -230,6 +261,73 @@ class Article &lt; ActiveRecord::Base
230 261 false
231 262 end
232 263  
  264 + def forum?
  265 + false
  266 + end
  267 +
  268 + def has_posts?
  269 + false
  270 + end
  271 +
  272 + named_scope :native_translations, :conditions => { :translation_of_id => nil }
  273 +
  274 + def translatable?
  275 + false
  276 + end
  277 +
  278 + def native_translation
  279 + self.translation_of.nil? ? self : self.translation_of
  280 + end
  281 +
  282 + def possible_translations
  283 + possibilities = Noosfero.locales.keys - self.native_translation.translations(:select => :language).map(&:language) - [self.native_translation.language]
  284 + possibilities << self.language unless self.language_changed?
  285 + possibilities
  286 + end
  287 +
  288 + def known_language
  289 + unless self.language.blank?
  290 + errors.add(:language, N_('Language not supported by Noosfero')) unless Noosfero.locales.key?(self.language)
  291 + end
  292 + end
  293 +
  294 + def used_translation
  295 + unless self.language.blank? or self.translation_of.nil?
  296 + errors.add(:language, N_('Language is already used')) unless self.possible_translations.include?(self.language)
  297 + end
  298 + end
  299 +
  300 + def translation_must_have_language
  301 + unless self.translation_of.nil?
  302 + errors.add(:language, N_('Language must be choosen')) if self.language.blank?
  303 + end
  304 + end
  305 +
  306 + def native_translation_must_have_language
  307 + unless self.translation_of.nil?
  308 + errors.add_to_base(N_('A language must be choosen for the native article')) if self.translation_of.language.blank?
  309 + end
  310 + end
  311 +
  312 + def rotate_translations
  313 + unless self.translations.empty?
  314 + rotate = self.translations
  315 + root = rotate.shift
  316 + root.update_attribute(:translation_of_id, nil)
  317 + root.translations = rotate
  318 + end
  319 + end
  320 +
  321 + def get_translation_to(locale)
  322 + if self.language.nil? || self.language == locale
  323 + self
  324 + elsif self.native_translation.language == locale
  325 + self.native_translation
  326 + else
  327 + self.native_translation.translations.first(:conditions => { :language => locale }) || self
  328 + end
  329 + end
  330 +
233 331 def published?
234 332 if self.published
235 333 if self.parent && !self.parent.published?
... ... @@ -241,22 +339,33 @@ class Article &lt; ActiveRecord::Base
241 339 end
242 340 end
243 341  
244   - named_scope :published, :conditions => { :published => true }
245   - named_scope :folders, :conditions => { :type => ['Folder', 'Blog'] }
  342 + named_scope :published, :conditions => { :published => true }
  343 + named_scope :folders, :conditions => { :type => ['Folder', 'Blog', 'Forum', 'Gallery'] }
  344 + named_scope :galleries, :conditions => { :type => 'Gallery' }
  345 + named_scope :images, :conditions => { :is_image => true }
  346 +
  347 + def self.display_filter(user, profile)
  348 + return {:conditions => ['published = ?', true]} if !user
  349 + {:conditions => [" articles.published = ? OR
  350 + articles.last_changed_by_id = ? OR
  351 + articles.profile_id = ? OR
  352 + ?",
  353 + true, user.id, user.id, user.has_permission?(:view_private_content, profile)] }
  354 + end
246 355  
247 356 def display_unpublished_article_to?(user)
248   - self.author == user || allow_view_private_content?(user) || user == self.profile ||
249   - user.is_admin?(self.profile.environment) || user.is_admin?(self.profile)
  357 + user == author || allow_view_private_content?(user) || user == profile ||
  358 + user.is_admin?(profile.environment) || user.is_admin?(profile)
250 359 end
251 360  
252   - def display_to?(user)
253   - if self.published?
254   - self.profile.display_info_to?(user)
  361 + def display_to?(user = nil)
  362 + if published?
  363 + profile.display_info_to?(user)
255 364 else
256   - if user.nil?
  365 + if !user
257 366 false
258 367 else
259   - self.display_unpublished_article_to?(user)
  368 + display_unpublished_article_to?(user)
260 369 end
261 370 end
262 371 end
... ... @@ -286,12 +395,18 @@ class Article &lt; ActiveRecord::Base
286 395 end
287 396  
288 397  
289   - def copy(options)
  398 + def copy(options = {})
290 399 attrs = attributes.reject! { |key, value| ATTRIBUTES_NOT_COPIED.include?(key.to_sym) }
291 400 attrs.merge!(options)
292 401 self.class.create(attrs)
293 402 end
294 403  
  404 + def copy!(options = {})
  405 + attrs = attributes.reject! { |key, value| ATTRIBUTES_NOT_COPIED.include?(key.to_sym) }
  406 + attrs.merge!(options)
  407 + self.class.create!(attrs)
  408 + end
  409 +
295 410 ATTRIBUTES_NOT_COPIED = [
296 411 :id,
297 412 :profile_id,
... ... @@ -329,13 +444,28 @@ class Article &lt; ActiveRecord::Base
329 444 false
330 445 end
331 446  
332   - def display_as_gallery?
  447 + def event?
  448 + false
  449 + end
  450 +
  451 + def gallery?
  452 + false
  453 + end
  454 +
  455 + def tiny_mce?
333 456 false
334 457 end
335 458  
336 459 def author
337   - last_changed_by ||
338   - profile
  460 + if reference_article
  461 + reference_article.author
  462 + else
  463 + last_changed_by || profile
  464 + end
  465 + end
  466 +
  467 + def author_name
  468 + setting[:author_name].blank? ? author.name : setting[:author_name]
339 469 end
340 470  
341 471 alias :active_record_cache_key :cache_key
... ... @@ -348,8 +478,16 @@ class Article &lt; ActiveRecord::Base
348 478 end
349 479  
350 480 def first_paragraph
351   - to_html =~ /(.*<\/p>)/
352   - $1 || ''
  481 + paragraphs = Hpricot(to_html).search('p')
  482 + paragraphs.empty? ? '' : paragraphs.first.to_html
  483 + end
  484 +
  485 + def lead
  486 + abstract.blank? ? first_paragraph : abstract
  487 + end
  488 +
  489 + def short_lead
  490 + truncate sanitize_html(self.lead), 170, '...'
353 491 end
354 492  
355 493 def creator
... ... @@ -357,6 +495,10 @@ class Article &lt; ActiveRecord::Base
357 495 creator_id && Profile.find(creator_id)
358 496 end
359 497  
  498 + def notifiable?
  499 + false
  500 + end
  501 +
360 502 private
361 503  
362 504 def sanitize_tag_list
... ... @@ -368,4 +510,9 @@ class Article &lt; ActiveRecord::Base
368 510 tag_name.gsub(/[<>]/, '')
369 511 end
370 512  
  513 + def sanitize_html(text)
  514 + sanitizer = HTML::FullSanitizer.new
  515 + sanitizer.sanitize(text)
  516 + end
  517 +
371 518 end
... ...
app/models/block.rb
... ... @@ -19,12 +19,22 @@ class Block &lt; ActiveRecord::Base
19 19 # may contain the following keys:
20 20 #
21 21 # * <tt>:article</tt>: the article being viewed currently
  22 + # * <tt>:language</tt>: in which language the block will be displayed
22 23 def visible?(context = nil)
23 24 if display == 'never'
24 25 return false
25 26 end
26   - if context && context[:article] && display == 'home_page_only'
27   - return context[:article] == owner.home_page
  27 + if context
  28 + if language != 'all' && language != context[:locale]
  29 + return false
  30 + end
  31 + if display == 'home_page_only'
  32 + if context[:article]
  33 + return context[:article] == owner.home_page
  34 + else
  35 + return context[:request_path] == '/'
  36 + end
  37 + end
28 38 end
29 39 true
30 40 end
... ... @@ -37,6 +47,11 @@ class Block &lt; ActiveRecord::Base
37 47 # homepage of its owner.
38 48 settings_items :display, :type => :string, :default => 'always'
39 49  
  50 + # The block can be configured to be displayed in all languages or in just one language. It can assume any locale of the environment:
  51 + #
  52 + # * <tt>'all'</tt>: the block is always displayed
  53 + settings_items :language, :type => :string, :default => 'all'
  54 +
40 55 # returns the description of the block, used when the user sees a list of
41 56 # blocks to choose one to include in the design.
42 57 #
... ...
app/models/blog.rb
1 1 class Blog < Folder
2 2  
3   - has_many :posts, :class_name => 'Article', :foreign_key => 'parent_id', :source => :children, :conditions => [ 'type != ?', 'RssFeed' ], :order => 'published_at DESC, id DESC'
4   -
5   - attr_accessor :feed_attrs
6   -
7   - after_create do |blog|
8   - blog.children << RssFeed.new(:name => 'feed', :profile => blog.profile)
9   - blog.feed = blog.feed_attrs
10   - end
11   -
12   - settings_items :posts_per_page, :type => :integer, :default => 5
  3 + acts_as_having_posts
13 4  
14 5 def self.short_description
15 6 _('Blog')
... ... @@ -35,21 +26,6 @@ class Blog &lt; Folder
35 26 true
36 27 end
37 28  
38   - def feed
39   - self.children.find(:first, :conditions => {:type => 'RssFeed'})
40   - end
41   -
42   - def feed=(attrs)
43   - if attrs
44   - if self.feed
45   - self.feed.update_attributes(attrs)
46   - else
47   - self.feed_attrs = attrs
48   - end
49   - end
50   - self.feed
51   - end
52   -
53 29 has_one :external_feed, :foreign_key => 'blog_id', :dependent => :destroy
54 30  
55 31 attr_accessor :external_feed_data
... ... @@ -78,17 +54,15 @@ class Blog &lt; Folder
78 54 end
79 55 end
80 56  
81   - def name=(value)
82   - self.set_name(value)
83   - if self.slug.blank?
84   - self.slug = self.name.to_slug
85   - else
86   - self.slug = self.slug.to_slug
87   - end
  57 + def self.icon_name(article = nil)
  58 + 'blog'
88 59 end
89 60  
90 61 settings_items :visualization_format, :type => :string, :default => 'full'
91 62 validates_inclusion_of :visualization_format, :in => [ 'full', 'short' ], :if => :visualization_format
92 63  
  64 + settings_items :display_posts_in_current_language, :type => :boolean, :default => true
  65 +
  66 + alias :display_posts_in_current_language? :display_posts_in_current_language
93 67  
94 68 end
... ...
app/models/blog_archives_block.rb
... ... @@ -24,7 +24,7 @@ class BlogArchivesBlock &lt; Block
24 24 owner_blog = self.blog
25 25 return nil unless owner_blog
26 26 results = ''
27   - owner_blog.posts.group_by {|i| i.published_at.year }.sort_by { |year,count| -year }.each do |year, results_by_year|
  27 + owner_blog.posts.native_translations.group_by {|i| i.published_at.year }.sort_by { |year,count| -year }.each do |year, results_by_year|
28 28 results << content_tag('li', content_tag('strong', "#{year} (#{results_by_year.size})"))
29 29 results << "<ul class='#{year}-archive'>"
30 30 results_by_year.group_by{|i| [ ('%02d' % i.published_at.month()), gettext(MONTHS[i.published_at.month() - 1])]}.sort.reverse.each do |month, results_by_month|
... ...
app/models/categories_block.rb 0 → 100644
... ... @@ -0,0 +1,38 @@
  1 +class CategoriesBlock < Block
  2 +
  3 + CATEGORY_TYPES = {
  4 + _('Generic category') => nil,
  5 + _('Region') => 'Region',
  6 + _('Product') => 'ProductCategory'
  7 + }
  8 +
  9 + settings_items :category_types, :type => Array, :default => []
  10 +
  11 + def self.description
  12 + _("Categories Menu")
  13 + end
  14 +
  15 + def default_title
  16 + _("Categories Menu")
  17 + end
  18 +
  19 + def help
  20 + _('This block presents the categories like a web site menu.')
  21 + end
  22 +
  23 + def available_category_types
  24 + CATEGORY_TYPES
  25 + end
  26 +
  27 + def selected_categories
  28 + Category.top_level_for(self.owner).from_types(self.category_types)
  29 + end
  30 +
  31 + def content
  32 + block = self
  33 + lambda do
  34 + render :file => 'blocks/categories', :locals => { :block => block }
  35 + end
  36 + end
  37 +
  38 +end
... ...
app/models/category.rb
... ... @@ -9,9 +9,9 @@ class Category &lt; ActiveRecord::Base
9 9 validates_uniqueness_of :display_color, :scope => :environment_id, :if => (lambda { |cat| ! cat.display_color.nil? }), :message => N_('%{fn} was already assigned to another category.')
10 10  
11 11 # Finds all top level categories for a given environment.
12   - def self.top_level_for(environment)
13   - self.find(:all, :conditions => ['parent_id is null and environment_id = ?', environment.id ])
14   - end
  12 + named_scope :top_level_for, lambda { |environment|
  13 + {:conditions => ['parent_id is null and environment_id = ?', environment.id ]}
  14 + }
15 15  
16 16 acts_as_filesystem
17 17  
... ... @@ -30,6 +30,12 @@ class Category &lt; ActiveRecord::Base
30 30  
31 31 acts_as_having_image
32 32  
  33 + named_scope :from_types, lambda { |types|
  34 + types.select{ |t| t.blank? }.empty? ?
  35 + { :conditions => { :type => types } } :
  36 + { :conditions => [ "type IN (?) OR type IS NULL", types.reject{ |t| t.blank? } ] }
  37 + }
  38 +
33 39 def recent_articles(limit = 10)
34 40 self.articles.recent(limit)
35 41 end
... ... @@ -58,4 +64,9 @@ class Category &lt; ActiveRecord::Base
58 64 results
59 65 end
60 66  
  67 + def is_leaf_displayable_in_menu?
  68 + return false if self.display_in_menu == false
  69 + self.children.find(:all, :conditions => {:display_in_menu => true}).empty?
  70 + end
  71 +
61 72 end
... ...
app/models/certifier.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class Certifier < ActiveRecord::Base
  2 +
  3 + belongs_to :environment
  4 +
  5 + has_many :qualifier_certifiers
  6 + has_many :qualifiers, :through => :qualifier_certifiers
  7 +
  8 + validates_presence_of :environment_id
  9 + validates_presence_of :name
  10 +
  11 + def link
  12 + self[:link] || ''
  13 + end
  14 +end
... ...
app/models/change_password.rb
1 1 class ChangePassword < Task
2 2  
3   - serialize :data, Hash
4   - def data
5   - self[:data] ||= {}
6   - end
7   -
8 3 attr_accessor :login, :email, :password, :password_confirmation, :environment_id
9 4  
10 5 def self.human_attribute_name(attrib)
... ... @@ -45,7 +40,7 @@ class ChangePassword &lt; Task
45 40 end
46 41  
47 42 before_validation_on_create do |change_password|
48   - change_password.requestor = Person.find_by_identifier(change_password.login)
  43 + change_password.requestor = Person.find_by_identifier_and_environment_id(change_password.login, change_password.environment_id)
49 44 end
50 45  
51 46 ###################################################
... ... @@ -56,9 +51,16 @@ class ChangePassword &lt; Task
56 51 validates_presence_of :password_confirmation, :on => :update, :if => lambda { |change| change.status != Task::Status::CANCELLED }
57 52 validates_confirmation_of :password, :if => lambda { |change| change.status != Task::Status::CANCELLED }
58 53  
59   - def initialize(*args)
60   - super(*args)
61   - self[:data] = {}
  54 + def title
  55 + _("Change password")
  56 + end
  57 +
  58 + def information
  59 + {:message => _('%{requestor} wants to change its password.')}
  60 + end
  61 +
  62 + def icon
  63 + {:type => :profile_image, :profile => requestor, :url => requestor.url}
62 64 end
63 65  
64 66 def perform
... ... @@ -87,8 +89,8 @@ class ChangePassword &lt; Task
87 89 end
88 90 end
89 91  
90   - def description
91   - _('Password change request')
  92 + def environment
  93 + self.requestor.environment
92 94 end
93 95  
94 96 end
... ...
app/models/comment.rb
1 1 class Comment < ActiveRecord::Base
2   -
  2 +
  3 + track_actions :leave_comment, :after_create, :keep_params => ["article.title", "article.url", "title", "url", "body"], :custom_target => :action_tracker_target
  4 +
3 5 validates_presence_of :title, :body
4 6 belongs_to :article, :counter_cache => true
5 7 belongs_to :author, :class_name => 'Person', :foreign_key => 'author_id'
  8 + has_many :children, :class_name => 'Comment', :foreign_key => 'reply_of_id', :dependent => :destroy
  9 + belongs_to :reply_of, :class_name => 'Comment', :foreign_key => 'reply_of_id'
6 10  
7 11 # unauthenticated authors:
8 12 validates_presence_of :name, :if => (lambda { |record| !record.email.blank? })
... ... @@ -19,6 +23,10 @@ class Comment &lt; ActiveRecord::Base
19 23  
20 24 xss_terminate :only => [ :body, :title, :name ], :on => 'validation'
21 25  
  26 + def action_tracker_target
  27 + self.article.profile
  28 + end
  29 +
22 30 def author_name
23 31 if author
24 32 author.short_name
... ... @@ -35,16 +43,20 @@ class Comment &lt; ActiveRecord::Base
35 43 author ? author.url : email
36 44 end
37 45  
  46 + def author_url
  47 + author ? author.url : nil
  48 + end
  49 +
38 50 def url
39 51 article.view_url.merge(:anchor => anchor)
40 52 end
41 53  
42 54 def message
43   - author_id ? _('(removed user)') : ('<br />' + _('(unauthenticated user)'))
  55 + author_id ? _('(removed user)') : _('(unauthenticated user)')
44 56 end
45 57  
46 58 def removed_user_image
47   - '/images/icons-app/user_icon_size-minor.png'
  59 + '/images/icons-app/person-minor.png'
48 60 end
49 61  
50 62 def anchor
... ... @@ -62,18 +74,34 @@ class Comment &lt; ActiveRecord::Base
62 74 end
63 75  
64 76 after_create do |comment|
65   - if comment.article.notify_comments?
  77 + if comment.article.notify_comments? && !comment.article.profile.notification_emails.empty?
66 78 Comment::Notifier.deliver_mail(comment)
67 79 end
68 80 end
69 81  
  82 + def replies
  83 + @replies || children
  84 + end
  85 +
  86 + def replies=(comments_list)
  87 + @replies = comments_list
  88 + end
  89 +
  90 + def self.as_thread
  91 + result = {}
  92 + root = []
  93 + all.each do |c|
  94 + c.replies = []
  95 + result[c.id] ||= c
  96 + c.reply_of_id.nil? ? root << c : result[c.reply_of_id].replies << c
  97 + end
  98 + root
  99 + end
  100 +
70 101 class Notifier < ActionMailer::Base
71 102 def mail(comment)
72 103 profile = comment.article.profile
73   - email = profile.notification_emails
74   - return unless email
75   - recipients email
76   -
  104 + recipients profile.notification_emails
77 105 from "#{profile.environment.name} <#{profile.environment.contact_email}>"
78 106 subject _("[%s] you got a new comment!") % [profile.environment.name]
79 107 body :recipient => profile.nickname || profile.name,
... ... @@ -81,6 +109,8 @@ class Comment &lt; ActiveRecord::Base
81 109 :sender_link => comment.author_link,
82 110 :article_title => comment.article.name,
83 111 :comment_url => comment.url,
  112 + :comment_title => comment.title,
  113 + :comment_body => comment.body,
84 114 :environment => profile.environment.name,
85 115 :url => profile.environment.top_url
86 116 end
... ...
app/models/communities_block.rb
... ... @@ -8,10 +8,6 @@ class CommunitiesBlock &lt; ProfileListBlock
8 8 n__('{#} community', '{#} communities', profile_count)
9 9 end
10 10  
11   - def profile_image_link_method
12   - :community_image_link
13   - end
14   -
15 11 def help
16 12 __('This block displays the communities in which the user is a member.')
17 13 end
... ... @@ -25,35 +21,15 @@ class CommunitiesBlock &lt; ProfileListBlock
25 21 end
26 22 when Environment
27 23 lambda do
28   - link_to s_('communities|View all'), :controller => 'search', :action => 'assets', :asset => 'communities'
  24 + link_to s_('communities|View all'), :controller => 'browse', :action => 'communities'
29 25 end
30 26 else
31 27 ''
32 28 end
33 29 end
34 30  
35   - def profile_count
36   - if owner.kind_of?(Environment)
37   - owner.communities.count(:conditions => { :visible => true })
38   - else
39   - owner.communities(:visible => true).count
40   - end
41   - end
42   -
43   - def profile_finder
44   - @profile_finder ||= CommunitiesBlock::Finder.new(self)
45   - end
46   -
47   - class Finder < ProfileListBlock::Finder
48   - def ids
49   - # FIXME when owner is an environment (i.e. listing communities globally
50   - # this can become SLOW)
51   - if block.owner.kind_of?(Environment)
52   - block.owner.communities.all(:conditions => {:visible => true}, :limit => block.limit, :order => 'random()').map(&:id)
53   - else
54   - block.owner.communities(:visible => true).map(&:id)
55   - end
56   - end
  31 + def profiles
  32 + owner.communities
57 33 end
58 34  
59 35 end
... ...
app/models/community.rb
... ... @@ -67,4 +67,12 @@ class Community &lt; Organization
67 67 def blocks_to_expire_cache
68 68 [MembersBlock]
69 69 end
  70 +
  71 + def each_member(offset=0)
  72 + while member = self.members.first(:order => :id, :offset => offset)
  73 + yield member
  74 + offset = offset + 1
  75 + end
  76 + end
  77 +
70 78 end
... ...
app/models/consumption.rb
... ... @@ -1,9 +0,0 @@
1   -class Consumption < ActiveRecord::Base
2   - belongs_to :profile
3   - belongs_to :product_category
4   -
5   - validates_uniqueness_of :product_category_id, :scope => :profile_id
6   -
7   - xss_terminate :only => [ :aditional_specifications ], :on => 'validation'
8   -
9   -end
app/models/contact_list.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +class ContactList < ActiveRecord::Base
  2 +
  3 + serialize :list, Array
  4 +
  5 + def list
  6 + self[:list] || []
  7 + end
  8 +
  9 + def data
  10 + if self.fetched
  11 + { "fetched" => true, "contact_list" => self.id, "error" => self.error_fetching }
  12 + else
  13 + {}
  14 + end
  15 + end
  16 +
  17 + def register_auth_error
  18 + msg = _('There was an error while authenticating. Did you enter correct login and password?')
  19 + self.update_attributes(:fetched => true, :error_fetching => msg)
  20 + end
  21 +
  22 + def register_error
  23 + msg = _('There was an error while looking for your contact list. Please, try again')
  24 + self.update_attributes(:fetched => true, :error_fetching => msg)
  25 + end
  26 +
  27 +end
... ...
app/models/create_community.rb
... ... @@ -6,25 +6,11 @@ class CreateCommunity &lt; Task
6 6 alias :environment :target
7 7 alias :environment= :target=
8 8  
9   - serialize :data, Hash
10   - attr_protected :data
11   - def data
12   - self[:data] ||= Hash.new
13   - end
14   -
15 9 acts_as_having_image
16 10  
17 11 DATA_FIELDS = Community.fields + ['name', 'closed']
18   -
19 12 DATA_FIELDS.each do |field|
20   - # getter
21   - define_method(field) do
22   - self.data[field.to_sym]
23   - end
24   - # setter
25   - define_method("#{field}=") do |value|
26   - self.data[field.to_sym] = value
27   - end
  13 + settings_items field.to_sym
28 14 end
29 15  
30 16 def validate
... ... @@ -48,16 +34,30 @@ class CreateCommunity &lt; Task
48 34 community.add_admin(self.requestor)
49 35 end
50 36  
51   - def description
52   - _('%s wants to create community %s.') % [requestor.name, self.name]
  37 + def title
  38 + _("New community")
  39 + end
  40 +
  41 + def icon
  42 + src = image ? image.public_filename(:minor) : '/images/icons-app/community-minor.png'
  43 + {:type => :defined_image, :src => src, :name => name}
  44 + end
  45 +
  46 + def subject
  47 + name
53 48 end
54 49  
55   - def closing_statement
56   - data[:closing_statement]
  50 + def information
  51 + if description.blank?
  52 + { :message => _('%{requestor} wants to create community %{subject} with no description.') }
  53 + else
  54 + { :message => _('%{requestor} wants to create community %{subject} with this description:<p><em>%{description}</em></p>'),
  55 + :variables => {:description => description} }
  56 + end
57 57 end
58 58  
59   - def closing_statement= value
60   - data[:closing_statement] = value
  59 + def reject_details
  60 + true
61 61 end
62 62  
63 63 # tells if this request was rejected
... ... @@ -70,8 +70,11 @@ class CreateCommunity &lt; Task
70 70 self.status == Task::Status::FINISHED
71 71 end
72 72  
  73 + def target_notification_description
  74 + _('%{requestor} wants to create community %{subject}') % {:requestor => requestor.name, :subject => subject}
  75 + end
  76 +
73 77 def target_notification_message
74   - description + "\n\n" +
75 78 _("User \"%{user}\" just requested to create community %{community}. You have to approve or reject it through the \"Pending Validations\" section in your control panel.\n") % { :user => self.requestor.name, :community => self.name }
76 79 end
77 80  
... ... @@ -82,7 +85,7 @@ class CreateCommunity &lt; Task
82 85 end
83 86  
84 87 def task_cancelled_message
85   - _("Your request for registering community %{community} at %{environment} was not approved by the environment administrator. The following explanation was given: \n\n%{explanation}") % { :community => self.name, :environment => self.environment, :explanation => self.closing_statement }
  88 + _("Your request for registering community %{community} at %{environment} was not approved by the environment administrator. The following explanation was given: \n\n%{explanation}") % { :community => self.name, :environment => self.environment, :explanation => self.reject_explanation }
86 89 end
87 90  
88 91 def task_finished_message
... ...
app/models/create_enterprise.rb
... ... @@ -11,23 +11,9 @@ class CreateEnterprise &lt; Task
11 11 N_('Economic activity')
12 12 N_('Management information')
13 13  
14   - DATA_FIELDS = Enterprise.fields + %w[name identifier region_id reject_explanation]
15   -
16   - serialize :data, Hash
17   - attr_protected :data
18   - def data
19   - self[:data] ||= Hash.new
20   - end
21   -
  14 + DATA_FIELDS = Enterprise.fields + %w[name identifier region_id]
22 15 DATA_FIELDS.each do |field|
23   - # getter
24   - define_method(field) do
25   - self.data[field.to_sym]
26   - end
27   - # setter
28   - define_method("#{field}=") do |value|
29   - self.data[field.to_sym] = value
30   - end
  16 + settings_items field.to_sym
31 17 end
32 18  
33 19 # checks for virtual attributes
... ... @@ -48,7 +34,6 @@ class CreateEnterprise &lt; Task
48 34  
49 35 # check for explanation when rejecting
50 36 validates_presence_of :reject_explanation, :if => (lambda { |record| record.status == Task::Status::CANCELLED } )
51   -
52 37 xss_terminate :only => [ :acronym, :address, :contact_person, :contact_phone, :economic_activity, :legal_form, :management_information, :name ], :on => 'validation'
53 38  
54 39 def validate
... ... @@ -59,7 +44,7 @@ class CreateEnterprise &lt; Task
59 44 end
60 45 end
61 46  
62   - if self.identifier && Profile.exists?(:identifier => self.identifier)
  47 + if self.status != Task::Status::CANCELLED && self.identifier && Profile.exists?(:identifier => self.identifier)
63 48 self.errors.add(:identifier, '%{fn} is already being as identifier by another enterprise, organization or person.')
64 49 end
65 50 end
... ... @@ -91,7 +76,7 @@ class CreateEnterprise &lt; Task
91 76 end
92 77  
93 78 def environment
94   - region ? region.environment : self.requestor ? self.requestor.environment : Environment.default
  79 + requestor.environment
95 80 end
96 81  
97 82 def available_regions
... ... @@ -153,8 +138,24 @@ class CreateEnterprise &lt; Task
153 138 enterprise.add_admin(enterprise.user.person)
154 139 end
155 140  
156   - def description
157   - _('Enterprise registration: "%s"') % self.name
  141 + def title
  142 + _("Enterprise registration")
  143 + end
  144 +
  145 + def icon
  146 + {:type => :defined_image, :src => '/images/icons-app/enterprise-minor.png', :name => name}
  147 + end
  148 +
  149 + def subject
  150 + name
  151 + end
  152 +
  153 + def information
  154 + {:message => _('%{requestor} wants to create enterprise %{subject}.')}
  155 + end
  156 +
  157 + def reject_details
  158 + true
158 159 end
159 160  
160 161 def task_created_message
... ... @@ -164,18 +165,18 @@ class CreateEnterprise &lt; Task
164 165 end
165 166  
166 167 def task_finished_message
167   - _('Your request for registering the enterprise "%{enterprise}" was approved. You can access %{environment} now and provide start providing all relevant information your new enterprise.') % { :enterprise => self.name, :environment => self.environment }
  168 + __('Your request for registering the enterprise "%{enterprise}" was approved. You can access %{environment} now and provide start providing all relevant information your new enterprise.') % { :enterprise => self.name, :environment => self.environment }
168 169 end
169 170  
170 171 def task_cancelled_message
171   - _("Your request for registering the enterprise %{enterprise} at %{environment} was NOT approved by the validator organization. The following explanation was given: \n\n%{explanation}") % { :enterprise => self.name, :environment => self.environment, :explanation => self.reject_explanation }
  172 + __("Your request for registering the enterprise %{enterprise} at %{environment} was NOT approved by the validator organization. The following explanation was given: \n\n%{explanation}") % { :enterprise => self.name, :environment => self.environment, :explanation => self.reject_explanation }
172 173 end
173 174  
174 175 def target_notification_message
175 176 msg = ""
176   - msg << _("Enterprise \"%{enterprise}\" just requested to enter %{environment}. You have to approve or reject it through the \"Pending Validations\" section in your control panel.\n") % { :enterprise => self.name, :environment => self.environment }
  177 + msg << __("Enterprise \"%{enterprise}\" just requested to enter %{environment}. You have to approve or reject it through the \"Pending Validations\" section in your control panel.\n") % { :enterprise => self.name, :environment => self.environment }
177 178 msg << "\n"
178   - msg << _("The data provided by the enterprise was the following:\n") << "\n"
  179 + msg << __("The data provided by the enterprise was the following:\n") << "\n"
179 180  
180 181  
181 182 msg << (_("Name: %s") % self.name) << "\n"
... ... @@ -185,16 +186,20 @@ class CreateEnterprise &lt; Task
185 186 msg << (_("Foundation Year: %d") % self.foundation_year) << "\n" unless self.foundation_year.blank?
186 187 msg << (_("Economic activity: %s") % self.economic_activity) << "\n"
187 188  
188   - msg << _("Information about enterprise's management:\n") << self.management_information.to_s << "\n"
  189 + msg << __("Information about enterprise's management:\n") << self.management_information.to_s << "\n"
189 190  
190 191 msg << (_("Contact phone: %s") % self.contact_phone) << "\n"
191 192 msg << (_("Contact person: %s") % self.contact_person) << "\n"
192 193  
193   - msg << _('CreateEnterprise|Identifier')
  194 + msg << __('CreateEnterprise|Identifier')
194 195  
195 196 msg
196 197 end
197 198  
  199 + def target_notification_description
  200 + _('%{requestor} wants to create enterprise %{subject}.') % {:requestor => requestor.name, :subject => subject}
  201 + end
  202 +
198 203 def permission
199 204 :validate_enterprise
200 205 end
... ...
app/models/disabled_enterprise_message_block.rb
1 1 class DisabledEnterpriseMessageBlock < Block
2 2  
3 3 def self.description
4   - _('"Disabled enterprise" message')
  4 + __('"Disabled enterprise" message')
5 5 end
6 6  
7 7 def help
8   - _('Shows a message for disabled enterprises.')
  8 + __('Shows a message for disabled enterprises.')
9 9 end
10 10  
11 11 def default_title
... ...
app/models/email_activation.rb
... ... @@ -11,8 +11,20 @@ class EmailActivation &lt; Task
11 11 end
12 12 end
13 13  
14   - def description
15   - _("'%{user} wants to activate email '%{email}'") % { :user => person.name, :email => person.email_addresses.join(', ') }
  14 + def title
  15 + _("Email activation")
  16 + end
  17 +
  18 + def subject
  19 + person.email_addresses.join(', ')
  20 + end
  21 +
  22 + def information
  23 + {:message => _("%{requestor} wants to activate the following email: %{subject}.")}
  24 + end
  25 +
  26 + def icon
  27 + {:type => :profile_image, :profile => requestor, :url => requestor.url}
16 28 end
17 29  
18 30 def perform
... ...
app/models/enterprise.rb
... ... @@ -4,7 +4,8 @@ class Enterprise &lt; Organization
4 4  
5 5 N_('Enterprise')
6 6  
7   - has_many :products, :dependent => :destroy
  7 + has_many :products, :dependent => :destroy, :order => 'name ASC'
  8 + has_many :inputs, :through => :products
8 9  
9 10 extra_data_for_index :product_categories
10 11  
... ... @@ -51,6 +52,10 @@ class Enterprise &lt; Organization
51 52 environment ? environment.active_enterprise_fields : []
52 53 end
53 54  
  55 + def highlighted_products_with_image(options = {})
  56 + Product.find(:all, {:conditions => {:highlighted => true}, :joins => :image}.merge(options))
  57 + end
  58 +
54 59 def required_fields
55 60 environment ? environment.required_enterprise_fields : []
56 61 end
... ... @@ -117,17 +122,28 @@ class Enterprise &lt; Organization
117 122 end
118 123  
119 124 def default_set_of_blocks
  125 + links = [
  126 + {:name => _("Enterprises's profile"), :address => '/profile/{profile}', :icon => 'ok'},
  127 + {:name => _('Blog'), :address => '/{profile}/blog', :icon => 'edit'},
  128 + {:name => _('Products'), :address => '/catalog/{profile}', :icon => 'new'},
  129 + ]
120 130 blocks = [
121   - [MainBlock],
122   - [ProfileInfoBlock, MembersBlock],
123   - [RecentDocumentsBlock]
  131 + [MainBlock.new],
  132 + [ProfileImageBlock.new, LinkListBlock.new(:links => links)],
  133 + []
124 134 ]
125 135 if !environment.enabled?('disable_products_for_enterprises')
126   - blocks[2].unshift ProductsBlock
  136 + blocks[2].unshift ProductsBlock.new
127 137 end
128 138 blocks
129 139 end
130 140  
  141 + def default_set_of_articles
  142 + [
  143 + Blog.new(:name => _('Blog')),
  144 + ]
  145 + end
  146 +
131 147 before_create do |enterprise|
132 148 if enterprise.environment.enabled?('enterprises_are_disabled_when_created')
133 149 enterprise.enabled = false
... ... @@ -149,10 +165,4 @@ class Enterprise &lt; Organization
149 165 enable_contact_us
150 166 end
151 167  
152   - protected
153   -
154   - def default_homepage(attrs)
155   - EnterpriseHomepage.new(attrs)
156   - end
157   -
158 168 end
... ...
app/models/enterprise_activation.rb
... ... @@ -2,7 +2,6 @@ class EnterpriseActivation &lt; Task
2 2  
3 3 class RequestorRequired < Exception; end
4 4  
5   - acts_as_having_settings :field => :data
6 5 settings_items :enterprise_id, :integer
7 6  
8 7 validates_presence_of :enterprise_id
... ... @@ -20,4 +19,20 @@ class EnterpriseActivation &lt; Task
20 19 self.enterprise.enable(requestor)
21 20 end
22 21  
  22 + def title
  23 + _("Enterprise activation")
  24 + end
  25 +
  26 + def linked_subject
  27 + {:text => target.name, :url => target.public_profile_url}
  28 + end
  29 +
  30 + def information
  31 + {:message => _('%{requestor} wants to activate enterprise %{linked_subject}.')}
  32 + end
  33 +
  34 + def icon
  35 + {:type => :profile_image, :profile => requestor, :url => requestor.url}
  36 + end
  37 +
23 38 end
... ...
app/models/enterprise_homepage.rb
1 1 class EnterpriseHomepage < Article
2 2  
3 3 def self.short_description
4   - _('Enterprise homepage.')
  4 + __('Enterprise homepage.')
5 5 end
6 6  
7 7 def self.description
... ...
app/models/enterprises_block.rb
... ... @@ -28,29 +28,8 @@ class EnterprisesBlock &lt; ProfileListBlock
28 28 end
29 29 end
30 30  
31   - def profile_count
32   - if owner.kind_of?(Environment)
33   - owner.enterprises.count(:conditions => { :visible => true })
34   - else
35   - owner.enterprises(:visible => true).count
36   - end
37   -
38   - end
39   -
40   - def profile_finder
41   - @profile_finder ||= EnterprisesBlock::Finder.new(self)
42   - end
43   -
44   - class Finder < ProfileListBlock::Finder
45   - def ids
46   - # FIXME when owner is an environment (i.e. listing enterprises globally
47   - # this can become SLOW)
48   - if block.owner.kind_of?(Environment)
49   - block.owner.enterprises.all(:conditions => {:visible => true}, :limit => block.limit, :order => 'random()').map(&:id)
50   - else
51   - block.owner.enterprises.select(&:visible).map(&:id)
52   - end
53   - end
  31 + def profiles
  32 + owner.enterprises
54 33 end
55 34  
56 35 end
... ...
app/models/environment.rb
... ... @@ -3,6 +3,8 @@
3 3 # domains.
4 4 class Environment < ActiveRecord::Base
5 5  
  6 + has_many :users
  7 +
6 8 self.partial_updates = false
7 9  
8 10 has_many :tasks, :dependent => :destroy, :as => 'target'
... ... @@ -14,6 +16,7 @@ class Environment &lt; ActiveRecord::Base
14 16 'manage_environment_categories' => N_('Manage environment categories'),
15 17 'manage_environment_roles' => N_('Manage environment roles'),
16 18 'manage_environment_validators' => N_('Manage environment validators'),
  19 + 'manage_environment_users' => N_('Manage environment users'),
17 20 }
18 21  
19 22 module Roles
... ... @@ -42,9 +45,7 @@ class Environment &lt; ActiveRecord::Base
42 45 :name => N_('Member'),
43 46 :environment => self,
44 47 :permissions => [
45   - 'edit_profile',
46   - 'post_content',
47   - 'manage_products'
  48 + 'invite_members',
48 49 ]
49 50 )
50 51 # moderators for enterprises, communities etc
... ... @@ -67,7 +68,7 @@ class Environment &lt; ActiveRecord::Base
67 68 end
68 69  
69 70 def admins
70   - self.members_by_role(Environment::Roles.admin(self.id))
  71 + Person.members_of(self).all(:conditions => ['role_assignments.role_id = ?', Environment::Roles.admin(self).id])
71 72 end
72 73  
73 74 # returns the available features for a Environment, in the form of a
... ... @@ -80,7 +81,7 @@ class Environment &lt; ActiveRecord::Base
80 81 'disable_asset_communities' => __('Disable search for communities'),
81 82 'disable_asset_products' => _('Disable search for products'),
82 83 'disable_asset_events' => _('Disable search for events'),
83   - 'disable_products_for_enterprises' => _('Disable products for enterprises'),
  84 + 'disable_products_for_enterprises' => __('Disable products for enterprises'),
84 85 'disable_categories' => _('Disable categories'),
85 86 'disable_cms' => _('Disable CMS'),
86 87 'disable_header_and_footer' => _('Disable header/footer editing by users'),
... ... @@ -89,14 +90,12 @@ class Environment &lt; ActiveRecord::Base
89 90 'disable_select_city_for_contact' => _('Disable state/city select for contact form'),
90 91 'disable_contact_person' => _('Disable contact for people'),
91 92 'disable_contact_community' => _('Disable contact for groups/communities'),
92   - 'enterprise_registration' => _('Enterprise registration'),
93   - 'join_community_popup' => _('Ask users to join a group/community with a popup'),
  93 + 'enterprise_registration' => __('Enterprise registration'),
94 94  
95   - 'enterprise_activation' => _('Enable activation of enterprises'),
  95 + 'enterprise_activation' => __('Enable activation of enterprises'),
96 96 'wysiwyg_editor_for_environment_home' => _('Use WYSIWYG editor to edit environment home page'),
97 97 'media_panel' => _('Media panel in WYSIWYG editor'),
98 98 'select_preferred_domain' => _('Select preferred domains per profile'),
99   - 'display_wizard_signup' => _('Display wizard signup'),
100 99 'use_portal_community' => _('Use the portal as news source for front page'),
101 100 'user_themes' => _('Allow users to create their own themes'),
102 101 'search_in_home' => _("Display search form in home page"),
... ... @@ -107,7 +106,10 @@ class Environment &lt; ActiveRecord::Base
107 106 'organizations_are_moderated_by_default' => _("Organizations have moderated publication by default"),
108 107 'enable_organization_url_change' => _("Allow organizations to change their URL"),
109 108 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"),
110   - 'enterprises_are_disabled_when_created' => _('Enterprises are disabled when created'),
  109 + 'enterprises_are_disabled_when_created' => __('Enterprises are disabled when created'),
  110 + 'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'),
  111 + 'xmpp_chat' => _('XMPP/Jabber based chat'),
  112 + 'show_zoom_button_on_article_images' => _('Show a zoom link on all article images')
111 113 }
112 114 end
113 115  
... ... @@ -131,7 +133,8 @@ class Environment &lt; ActiveRecord::Base
131 133 env.boxes[1].blocks << RecentDocumentsBlock.new
132 134  
133 135 # "right" area
134   - env.boxes[2].blocks << ProfileListBlock.new
  136 + env.boxes[2].blocks << CommunitiesBlock.new(:limit => 6)
  137 + env.boxes[2].blocks << PeopleBlock.new(:limit => 6)
135 138 end
136 139  
137 140 # One Environment can be reached by many domains
... ... @@ -147,10 +150,16 @@ class Environment &lt; ActiveRecord::Base
147 150 has_many :categories
148 151 has_many :display_categories, :class_name => 'Category', :conditions => 'display_color is not null and parent_id is null', :order => 'display_color'
149 152  
  153 + has_many :product_categories, :conditions => { :type => 'ProductCategory'}
150 154 has_many :regions
151 155  
152 156 has_many :roles
153 157  
  158 + has_many :qualifiers
  159 + has_many :certifiers
  160 +
  161 + has_many :mailings, :class_name => 'EnvironmentMailing', :foreign_key => :source_id, :as => 'source'
  162 +
154 163 acts_as_accessible
155 164  
156 165 def superior_intances
... ... @@ -198,16 +207,21 @@ class Environment &lt; ActiveRecord::Base
198 207 settings_items :location, :type => String
199 208 settings_items :layout_template, :type => String, :default => 'default'
200 209 settings_items :homepage, :type => String
201   - settings_items :description, :type => String
202   - settings_items :category_types, :type => Array, :default => ['Category']
  210 + settings_items :description, :type => String, :default => '<div style="text-align: center"><a href="http://noosfero.org/"><img src="/images/noosfero-network.png" alt="Noosfero"/></a></div>'
203 211 settings_items :enable_ssl
204   - settings_items :theme, :type => String, :default => 'default'
205   - settings_items :icon_theme, :type => String, :default => 'default'
206 212 settings_items :local_docs, :type => Array, :default => []
207 213 settings_items :news_amount_by_folder, :type => Integer, :default => 4
208 214 settings_items :help_message_to_add_enterprise, :type => String, :default => ''
209 215 settings_items :tip_message_enterprise_activation_question, :type => String, :default => ''
210 216  
  217 + settings_items :currency_unit, :type => String, :default => '$'
  218 + settings_items :currency_separator, :type => String, :default => '.'
  219 + settings_items :currency_delimiter, :type => String, :default => ','
  220 +
  221 + settings_items :trusted_sites_for_iframe, :type => Array, :default => ['itheora.org', 'tv.softwarelivre.org', 'stream.softwarelivre.org']
  222 +
  223 + settings_items :enabled_plugins, :type => Array, :default => []
  224 +
211 225 def news_amount_by_folder=(amount)
212 226 settings[:news_amount_by_folder] = amount.to_i
213 227 end
... ... @@ -241,6 +255,29 @@ class Environment &lt; ActiveRecord::Base
241 255 end
242 256 end
243 257  
  258 + def enabled_features
  259 + features = self.class.available_features
  260 + features.delete_if{ |k, v| !self.enabled?(k) }
  261 + end
  262 +
  263 + before_create :enable_default_features
  264 + def enable_default_features
  265 + %w(
  266 + disable_asset_products
  267 + disable_gender_icon
  268 + disable_products_for_enterprises
  269 + disable_select_city_for_contact
  270 + enterprise_registration
  271 + media_panel
  272 + organizations_are_moderated_by_default
  273 + show_balloon_with_profile_links_when_clicked
  274 + use_portal_community
  275 + wysiwyg_editor_for_environment_home
  276 + ).each do |feature|
  277 + enable(feature)
  278 + end
  279 + end
  280 +
244 281 # returns <tt>true</tt> if this Environment has terms of use to be
245 282 # accepted by users before registration.
246 283 def has_terms_of_use?
... ... @@ -457,6 +494,10 @@ class Environment &lt; ActiveRecord::Base
457 494  
458 495 xss_terminate :only => [ :message_for_disabled_enterprise ], :with => 'white_list', :on => 'validation'
459 496  
  497 + validates_presence_of :theme
  498 +
  499 + include WhiteListFilter
  500 + filter_iframes :message_for_disabled_enterprise, :whitelist => lambda { trusted_sites_for_iframe }
460 501  
461 502 # #################################################
462 503 # Business logic in general
... ... @@ -525,7 +566,29 @@ class Environment &lt; ActiveRecord::Base
525 566 end
526 567  
527 568 def themes=(values)
528   - settings[:themes] = values.map(&:id)
  569 + settings[:themes] = values
  570 + end
  571 +
  572 + def add_themes(values)
  573 + if settings[:themes].nil?
  574 + self.themes = values
  575 + else
  576 + settings[:themes] += values
  577 + end
  578 + end
  579 +
  580 + before_create do |env|
  581 + env.settings[:themes] ||= %w[
  582 + aluminium
  583 + butter
  584 + chameleon
  585 + chocolate
  586 + noosfero
  587 + orange
  588 + plum
  589 + scarletred
  590 + skyblue
  591 + ]
529 592 end
530 593  
531 594 def community_template
... ... @@ -589,7 +652,7 @@ class Environment &lt; ActiveRecord::Base
589 652 end
590 653  
591 654 def portal_folders
592   - (settings[:portal_folders] || []).map{|fid| portal_community.articles.find(fid) }
  655 + (settings[:portal_folders] || []).map{|fid| portal_community.articles.find(:first, :conditions => { :id => fid }) }.compact
593 656 end
594 657  
595 658 def portal_folders=(folders)
... ... @@ -628,4 +691,17 @@ class Environment &lt; ActiveRecord::Base
628 691 end
629 692 end
630 693  
  694 + def highlighted_products_with_image(options = {})
  695 + Product.find(:all, {:conditions => {:highlighted => true, :enterprise_id => self.enterprises.find(:all, :select => :id) }, :joins => :image}.merge(options))
  696 + end
  697 +
  698 + settings_items :home_cache_in_minutes, :type => :integer, :default => 5
  699 + settings_items :general_cache_in_minutes, :type => :integer, :default => 15
  700 + settings_items :profile_cache_in_minutes, :type => :integer, :default => 15
  701 +
  702 + def image_galleries
  703 + portal_community ? portal_community.image_galleries : []
  704 + end
  705 +
631 706 end
  707 +
... ...
app/models/environment_mailing.rb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +class EnvironmentMailing < Mailing
  2 +
  3 + def recipients(offset=0, limit=100)
  4 + source.people.all(:order => :id, :offset => offset, :limit => limit, :joins => "LEFT OUTER JOIN mailing_sents m ON (m.mailing_id = #{id} AND m.person_id = profiles.id)", :conditions => { "m.person_id" => nil })
  5 + end
  6 +
  7 + def each_recipient
  8 + offset = 0
  9 + limit = 100
  10 + while !(people = recipients(offset, limit)).empty?
  11 + people.each do |person|
  12 + yield person
  13 + end
  14 + offset = offset + limit
  15 + end
  16 + end
  17 +
  18 + def signature_message
  19 + _('Sent by %s.') % source.name
  20 + end
  21 +
  22 + def url
  23 + source.top_url
  24 + end
  25 +end
... ...
app/models/environment_statistics_block.rb
... ... @@ -17,11 +17,12 @@ class EnvironmentStatisticsBlock &lt; Block
17 17 enterprises = owner.enterprises.visible.count
18 18 communities = owner.communities.visible.count
19 19  
20   - info = [
21   - n_('One user', '%{num} users', users) % { :num => users },
22   - n__('One enterprise', '%{num} enterprises', enterprises) % { :num => enterprises },
23   - n__('One community', '%{num} communities', communities) % { :num => communities },
24   - ]
  20 + info = []
  21 + info << (n_('One user', '%{num} users', users) % { :num => users })
  22 + unless owner.enabled?('disable_asset_enterprises')
  23 + info << (n__('One enterprise', '%{num} enterprises', enterprises) % { :num => enterprises })
  24 + end
  25 + info << (n__('One community', '%{num} communities', communities) % { :num => communities })
25 26  
26 27 block_title(title) + content_tag('ul', info.map {|item| content_tag('li', item) }.join("\n"))
27 28 end
... ...
app/models/event.rb
1 1 class Event < Article
2 2  
3   - acts_as_having_settings :field => :body
4   -
5   - settings_items :description, :type => :string
6   - settings_items :link, :type => :string
7 3 settings_items :address, :type => :string
8 4  
  5 + def link=(value)
  6 + self.setting[:link] = maybe_add_http(value)
  7 + end
  8 +
  9 + def link
  10 + maybe_add_http(self.setting[:link])
  11 + end
  12 +
9 13 xss_terminate :only => [ :link ], :on => 'validation'
10   - xss_terminate :only => [ :description, :link, :address ], :with => 'white_list', :on => 'validation'
  14 + xss_terminate :only => [ :body, :link, :address ], :with => 'white_list', :on => 'validation'
  15 +
  16 + def initialize(*args)
  17 + super(*args)
  18 + self.start_date ||= Date.today
  19 + end
11 20  
12 21 validates_presence_of :title, :start_date
13 22  
... ... @@ -21,6 +30,9 @@ class Event &lt; Article
21 30 {:conditions => ['start_date = :date AND end_date IS NULL OR (start_date <= :date AND end_date >= :date)', {:date => date}]}
22 31 }
23 32  
  33 + include WhiteListFilter
  34 + filter_iframes :body, :link, :address, :whitelist => lambda { profile && profile.environment && profile.environment.trusted_sites_for_iframe }
  35 +
24 36 def self.description
25 37 _('A calendar event')
26 38 end
... ... @@ -29,7 +41,7 @@ class Event &lt; Article
29 41 _('Event')
30 42 end
31 43  
32   - def icon_name
  44 + def self.icon_name(article = nil)
33 45 'event'
34 46 end
35 47  
... ... @@ -88,26 +100,27 @@ class Event &lt; Article
88 100 }
89 101 }
90 102  
91   - if self.description
  103 + if self.body
92 104 html.div('_____XXXX_DESCRIPTION_GOES_HERE_XXXX_____', :class => 'event-description')
93 105 end
94 106 }
95 107  
96   - if self.description
97   - result.sub!('_____XXXX_DESCRIPTION_GOES_HERE_XXXX_____', self.description)
  108 + if self.body
  109 + result.sub!('_____XXXX_DESCRIPTION_GOES_HERE_XXXX_____', self.body)
98 110 end
99 111  
100 112 result
101 113 end
102 114  
103   - def link=(value)
104   - self.body[:link] = maybe_add_http(value)
  115 + def event?
  116 + true
105 117 end
106 118  
107   - def link
108   - maybe_add_http(self.body[:link])
  119 + def tiny_mce?
  120 + true
109 121 end
110 122  
  123 + include Noosfero::TranslatableContent
111 124 include MaybeAddHttp
112 125  
113 126 end
... ...
app/models/favorite_enterprises_block.rb
... ... @@ -9,7 +9,7 @@ class FavoriteEnterprisesBlock &lt; ProfileListBlock
9 9 end
10 10  
11 11 def self.description
12   - __('Favorite enterprises')
  12 + __('Favorite Enterprises')
13 13 end
14 14  
15 15 def footer
... ... @@ -20,18 +20,8 @@ class FavoriteEnterprisesBlock &lt; ProfileListBlock
20 20 end
21 21 end
22 22  
23   - def profile_count
24   - owner.favorite_enterprises.count
25   - end
26   -
27   - def profile_finder
28   - @profile_finder ||= FavoriteEnterprisesBlock::Finder.new(self)
29   - end
30   -
31   - class Finder < ProfileListBlock::Finder
32   - def ids
33   - block.owner.favorite_enterprises.map(&:id)
34   - end
  23 + def profiles
  24 + owner.favorite_enterprises
35 25 end
36 26  
37 27 end
... ...
app/models/featured_products_block.rb 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +class FeaturedProductsBlock < Block
  2 +
  3 + settings_items :product_ids, :type => Array, :default => []
  4 + settings_items :groups_of, :type => :integer, :default => 3
  5 + settings_items :speed, :type => :integer, :default => 1000
  6 + settings_items :reflect, :type => :boolean, :default => true
  7 +
  8 + before_save do |block|
  9 + if block.owner.kind_of?(Environment) && block.product_ids.blank?
  10 + seed = block.owner.products.count
  11 + block.product_ids = block.owner.highlighted_products_with_image(:offset => (rand(seed) % (seed - block.groups_of * 3)), :limit => block.groups_of * 3).map(&:id)
  12 + end
  13 + block.groups_of = block.groups_of.to_i
  14 + end
  15 +
  16 + def self.description
  17 + _('Featured Products')
  18 + end
  19 +
  20 + def products
  21 + Product.find(self.product_ids) || []
  22 + end
  23 +
  24 + def products_for_selection
  25 + self.owner.highlighted_products_with_image
  26 + end
  27 +
  28 + def content
  29 + block = self
  30 + lambda do
  31 + render :file => 'blocks/featured_products', :locals => { :block => block }
  32 + end
  33 + end
  34 +
  35 +end
... ...
app/models/feed_reader_block.rb
... ... @@ -12,7 +12,7 @@ class FeedReaderBlock &lt; Block
12 12 def address=(new_address)
13 13 old_address = address
14 14 orig_set_address(new_address)
15   - self.enabled = (old_address.blank? && !new_address.blank?) || (new_address && new_address != old_address) || false
  15 + self.enabled = (new_address && new_address != old_address) || (new_address && self.enabled) || false
16 16 end
17 17  
18 18 settings_items :limit, :type => :integer
... ...
app/models/folder.rb
... ... @@ -2,19 +2,10 @@ class Folder &lt; Article
2 2  
3 3 acts_as_having_settings :field => :setting
4 4  
5   - settings_items :view_as, :type => :string, :default => 'folder'
6   -
7 5 xss_terminate :only => [ :body ], :with => 'white_list', :on => 'validation'
8 6  
9   - def self.select_views
10   - [[_('Folder'), 'folder'], [_('Image gallery'), 'image_gallery']]
11   - end
12   -
13   - def self.views
14   - select_views.map(&:last)
15   - end
16   -
17   - validates_inclusion_of :view_as, :in => self.views
  7 + include WhiteListFilter
  8 + filter_iframes :body, :whitelist => lambda { profile && profile.environment && profile.environment.trusted_sites_for_iframe }
18 9  
19 10 def self.short_description
20 11 _('Folder')
... ... @@ -24,37 +15,22 @@ class Folder &lt; Article
24 15 _('A folder, inside which you can put other articles.')
25 16 end
26 17  
27   - def icon_name
  18 + def self.icon_name(article = nil)
28 19 'folder'
29 20 end
30 21  
31   -
  22 + include ActionView::Helpers::TagHelper
32 23 def to_html(options = {})
33   - send(view_as)
34   - end
35   -
36   - def folder
37 24 folder = self
38 25 lambda do
39 26 render :file => 'content_viewer/folder', :locals => { :folder => folder }
40 27 end
41 28 end
42 29  
43   - def image_gallery
44   - article = self
45   - lambda do
46   - render :file => 'content_viewer/image_gallery', :locals => {:article => article}
47   - end
48   - end
49   -
50 30 def folder?
51 31 true
52 32 end
53 33  
54   - def display_as_gallery?
55   - view_as == 'image_gallery'
56   - end
57   -
58 34 def can_display_hits?
59 35 false
60 36 end
... ... @@ -70,7 +46,5 @@ class Folder &lt; Article
70 46 has_many :images, :class_name => 'Article',
71 47 :foreign_key => 'parent_id',
72 48 :order => 'articles.type, articles.name',
73   - :include => :reference_article,
74   - :conditions => ["articles.type = 'UploadedFile' and articles.content_type in (?) or articles.type = 'Folder' or (articles.type = 'PublishedArticle' and reference_articles_articles.type = 'UploadedFile' and reference_articles_articles.content_type in (?))", UploadedFile.content_types, UploadedFile.content_types]
75   -
  49 + :conditions => ["articles.type = 'UploadedFile' and articles.content_type in (?) or articles.type in ('Folder','Gallery')", UploadedFile.content_types]
76 50 end
... ...
app/models/forum.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +class Forum < Folder
  2 +
  3 + acts_as_having_posts :order => 'updated_at DESC'
  4 +
  5 + def self.short_description
  6 + _('Forum')
  7 + end
  8 +
  9 + def self.description
  10 + _('An internet forum, also called message board, where discussions can be held.')
  11 + end
  12 +
  13 + include ActionView::Helpers::TagHelper
  14 + def to_html(options = {})
  15 + lambda do
  16 + render :file => 'content_viewer/forum_page'
  17 + end
  18 + end
  19 +
  20 + def forum?
  21 + true
  22 + end
  23 +
  24 + def self.icon_name(article = nil)
  25 + 'forum'
  26 + end
  27 +end
... ...
app/models/friends_block.rb
... ... @@ -19,18 +19,8 @@ class FriendsBlock &lt; ProfileListBlock
19 19 end
20 20 end
21 21  
22   - class FriendsBlock::Finder < ProfileListBlock::Finder
23   - def ids
24   - self.block.owner.friend_ids
25   - end
26   - end
27   -
28   - def profile_finder
29   - @profile_finder ||= FriendsBlock::Finder.new(self)
30   - end
31   -
32   - def profile_count
33   - owner.friends.visible.count
  22 + def profiles
  23 + owner.friends
34 24 end
35 25  
36 26 end
... ...