diff options
-rw-r--r-- | app/helpers/application_helper.rb | 3 | ||||
-rw-r--r-- | app/helpers/date_time_helper.rb | 69 | ||||
-rwxr-xr-x | app/helpers/link_to_helper.rb | 83 | ||||
-rw-r--r-- | app/mailers/request_mailer.rb | 2 | ||||
-rw-r--r-- | app/models/incoming_message.rb | 15 | ||||
-rw-r--r-- | app/views/track_mailer/event_digest.text.erb | 6 | ||||
-rw-r--r-- | app/views/user/_user_listing_single.html.erb | 2 | ||||
-rw-r--r-- | app/views/user/show.html.erb | 2 | ||||
-rw-r--r-- | doc/DEPLOY.md | 41 | ||||
-rw-r--r-- | doc/INSTALL-exim4.md | 99 | ||||
-rw-r--r-- | doc/INSTALL-postfix.md | 68 | ||||
-rw-r--r-- | doc/INSTALL.md | 651 | ||||
-rw-r--r-- | doc/README.md | 9 | ||||
-rw-r--r-- | doc/THEMES.md | 165 | ||||
-rw-r--r-- | doc/TRANSLATE.md | 106 | ||||
-rw-r--r-- | lib/attachment_to_html/adapters/rtf.rb | 2 | ||||
-rw-r--r-- | spec/helpers/date_time_helper_spec.rb | 71 | ||||
-rw-r--r-- | spec/helpers/link_to_helper_spec.rb | 123 | ||||
-rw-r--r-- | spec/lib/attachment_to_html/adapters/rtf_spec.rb | 11 | ||||
-rw-r--r-- | spec/models/incoming_message_spec.rb | 18 |
20 files changed, 293 insertions, 1253 deletions
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 33525cb3d..45b042354 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -13,6 +13,9 @@ module ApplicationHelper # all of all. include LinkToHelper + # Some extra date and time formatters + include DateTimeHelper + # Site-wide access to configuration settings include ConfigHelper diff --git a/app/helpers/date_time_helper.rb b/app/helpers/date_time_helper.rb new file mode 100644 index 000000000..5f129e590 --- /dev/null +++ b/app/helpers/date_time_helper.rb @@ -0,0 +1,69 @@ +module DateTimeHelper + # Public: Usually-correct format for a DateTime-ish object + # To define a new new format define the `simple_date_{FORMAT}` method + # + # date - a DateTime, Date or Time + # opts - a Hash of options (default: { format: :html}) + # :format - :html returns a HTML <time> tag + # :text returns a plain String + # + # Examples + # + # simple_date(Time.now) + # # => "<time>..." + # + # simple_date(Time.now, :format => :text) + # # => "March 10, 2014" + # + # Returns a String + # Raises ArgumentError if the format is unrecognized + def simple_date(date, opts = {}) + opts = { :format => :html }.merge(opts) + date_formatter = "simple_date_#{ opts[:format] }" + + if respond_to?(date_formatter) + send(date_formatter, date) + else + raise ArgumentError, "Unrecognized format :#{ opts[:format] }" + end + end + + # Usually-correct HTML formatting of a DateTime-ish object + # Use LinkToHelper#simple_date with desired formatting options + # + # date - a DateTime, Date or Time + # + # Returns a String + def simple_date_html(date) + date = date.in_time_zone unless date.is_a?(Date) + time_tag date, simple_date_text(date), :title => date.to_s + end + + # Usually-correct plain text formatting of a DateTime-ish object + # Use LinkToHelper#simple_date with desired formatting options + # + # date - a DateTime, Date or Time + # + # Returns a String + def simple_date_text(date) + date = date.in_time_zone.to_date unless date.is_a? Date + + date_format = _('simple_date_format') + date_format = :long if date_format == 'simple_date_format' + I18n.l(date, :format => date_format) + end + + # Strips the date from a DateTime + # + # date - a DateTime, Date or Time + # + # Examples + # + # simple_time(Time.now) + # # => "10:46:54" + # + # Returns a String + def simple_time(date) + date.strftime("%H:%M:%S").strip + end +end diff --git a/app/helpers/link_to_helper.rb b/app/helpers/link_to_helper.rb index dd6ffa805..3709469cf 100755 --- a/app/helpers/link_to_helper.rb +++ b/app/helpers/link_to_helper.rb @@ -28,19 +28,19 @@ module LinkToHelper # Incoming / outgoing messages def incoming_message_url(incoming_message, options = {}) - return request_url(incoming_message.info_request, options.merge(:anchor => "incoming-#{incoming_message.id}")) + message_url(incoming_message, options) end def incoming_message_path(incoming_message) - incoming_message_url(incoming_message, :only_path => true) + message_path(incoming_message) end def outgoing_message_url(outgoing_message, options = {}) - request_url(outgoing_message.info_request, options.merge(:anchor => "outgoing-#{outgoing_message.id}")) + message_url(outgoing_message, options) end def outgoing_message_path(outgoing_message) - outgoing_message_url(outgoing_message, :only_path => true) + message_path(outgoing_message) end def comment_url(comment, options = {}) @@ -279,73 +279,30 @@ module LinkToHelper end end - # Public: Usually-correct format for a DateTime-ish object - # To define a new new format define the `simple_date_{FORMAT}` method - # - # date - a DateTime, Date or Time - # opts - a Hash of options (default: { format: :html}) - # :format - :html returns a HTML <time> tag - # :text returns a plain String - # - # Examples - # - # simple_date(Time.now) - # # => "<time>..." - # - # simple_date(Time.now, :format => :text) - # # => "March 10, 2014" - # - # Returns a String - # Raises ArgumentError if the format is unrecognized - def simple_date(date, opts = {}) - opts = { :format => :html }.merge(opts) - date_formatter = "simple_date_#{ opts[:format] }" - - if respond_to?(date_formatter) - send(date_formatter, date) - else - raise ArgumentError, "Unrecognised format :#{ opts[:format] }" - end - end + #I18n locale switcher - # Usually-correct HTML formatting of a DateTime-ish object - # Use LinkToHelper#simple_date with desired formatting options - # - # date - a DateTime, Date or Time - # - # Returns a String - def simple_date_html(date) - date = date.in_time_zone unless date.is_a? Date - time_tag date, simple_date_text(date), :title => date.to_s + def locale_switcher(locale, params) + params['locale'] = locale + return url_for(params) end - # Usually-correct plain text formatting of a DateTime-ish object - # Use LinkToHelper#simple_date with desired formatting options - # - # date - a DateTime, Date or Time - # - # Returns a String - def simple_date_text(date) - date = date.in_time_zone.to_date unless date.is_a? Date + private - date_format = _("simple_date_format") - date_format = :long if date_format == "simple_date_format" - I18n.l(date, :format => date_format) - end + # Private: Generate a request_url linking to the new correspondence + def message_url(message, options = {}) + message_type = message.class.to_s.gsub('Message', '').downcase - def simple_time(date) - return date.strftime("%H:%M:%S").strip - end + default_options = { :anchor => "#{ message_type }-#{ message.id }" } - def year_from_date(date) - return date.strftime("%Y").strip - end + if options.delete(:cachebust) + default_options.merge!(:nocache => "#{ message_type }-#{ message.id }") + end - #I18n locale switcher + request_url(message.info_request, options.merge(default_options)) + end - def locale_switcher(locale, params) - params['locale'] = locale - return url_for(params) + def message_path(message) + message_url(message, :only_path => true) end end diff --git a/app/mailers/request_mailer.rb b/app/mailers/request_mailer.rb index 360549f0c..768257ba8 100644 --- a/app/mailers/request_mailer.rb +++ b/app/mailers/request_mailer.rb @@ -71,7 +71,7 @@ class RequestMailer < ApplicationMailer def new_response(info_request, incoming_message) # Don't use login link here, just send actual URL. This is # because people tend to forward these emails amongst themselves. - @url = incoming_message_url(incoming_message) + @url = incoming_message_url(incoming_message, :cachebust => true) @incoming_message, @info_request = incoming_message, info_request headers('Return-Path' => blackhole_email, diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb index 39a5c996e..135a6bdaf 100644 --- a/app/models/incoming_message.rb +++ b/app/models/incoming_message.rb @@ -834,14 +834,15 @@ class IncomingMessage < ActiveRecord::Base def fully_destroy ActiveRecord::Base.transaction do - for o in self.outgoing_message_followups - o.incoming_message_followup = nil - o.save! + outgoing_message_followups.each do |outgoing_message_followup| + outgoing_message_followup.incoming_message_followup = nil + outgoing_message_followup.save! + end + info_request_events.each do |info_request_event| + info_request_event.track_things_sent_emails.each { |a| a.destroy } + info_request_event.user_info_request_sent_alerts.each { |a| a.destroy } + info_request_event.destroy end - info_request_event = InfoRequestEvent.find_by_incoming_message_id(self.id) - info_request_event.track_things_sent_emails.each { |a| a.destroy } - info_request_event.user_info_request_sent_alerts.each { |a| a.destroy } - info_request_event.destroy self.raw_email.destroy_file_representation! self.destroy end diff --git a/app/views/track_mailer/event_digest.text.erb b/app/views/track_mailer/event_digest.text.erb index b83c184f0..a154f430f 100644 --- a/app/views/track_mailer/event_digest.text.erb +++ b/app/views/track_mailer/event_digest.text.erb @@ -17,14 +17,14 @@ # e.g. Julian Burgess sent a request to Royal Mail Group (15 May 2008) if event.event_type == 'response' - url = incoming_message_url(event.incoming_message) + url = incoming_message_url(event.incoming_message, :cachebust => true) main_text += _("{{public_body}} sent a response to {{user_name}}", :public_body => event.info_request.public_body.name, :user_name => event.info_request.user_name) elsif event.event_type == 'followup_sent' - url = outgoing_message_url(event.outgoing_message) + url = outgoing_message_url(event.outgoing_message, :cachebust => true) main_text += _("{{user_name}} sent a follow up message to {{public_body}}", :user_name => event.info_request.user_name, :public_body => event.info_request.public_body.name) elsif event.event_type == 'sent' # this is unlikely to happen in real life, but happens in the test code - url = outgoing_message_url(event.outgoing_message) + url = outgoing_message_url(event.outgoing_message, :cachebust => true) main_text += _("{{user_name}} sent a request to {{public_body}}", :user_name => event.info_request.user_name, :public_body => event.info_request.public_body.name) elsif event.event_type == 'comment' url = comment_url(event.comment) diff --git a/app/views/user/_user_listing_single.html.erb b/app/views/user/_user_listing_single.html.erb index ed1b95718..3cb0d283f 100644 --- a/app/views/user/_user_listing_single.html.erb +++ b/app/views/user/_user_listing_single.html.erb @@ -18,7 +18,7 @@ end %> <span class="bottomline"> <%= pluralize(display_user.info_requests.size, "request") %> <%= _('made.')%> <%= pluralize(display_user.visible_comments.size, "annotation") %> <%= _('made.')%> - <%= _('Joined in')%> <%= year_from_date(display_user.created_at) %>. + <%= _('Joined in')%> <%= display_user.created_at.year %>. </span> </div> diff --git a/app/views/user/show.html.erb b/app/views/user/show.html.erb index ce328b46f..7ae577565 100644 --- a/app/views/user/show.html.erb +++ b/app/views/user/show.html.erb @@ -64,7 +64,7 @@ <h1> <%= h(@display_user.name) + (@is_you ? _(" (you)") : "") %></h1> <p class="subtitle"> - <%= _('Joined {{site_name}} in', :site_name=>site_name) %> <%= year_from_date(@display_user.created_at) %> + <%= _('Joined {{site_name}} in', :site_name=>site_name) %> <%= @display_user.created_at.year %> <% if !@user.nil? && @user.admin_page_links? %> (<%= link_to "admin", admin_user_show_path(@display_user) %>) <% end %> diff --git a/doc/DEPLOY.md b/doc/DEPLOY.md deleted file mode 100644 index f2e643ec5..000000000 --- a/doc/DEPLOY.md +++ /dev/null @@ -1,41 +0,0 @@ -# Deployment - -mySociety uses a custom deployment and buildout system however Capistrano is included as part of Alaveteli as a standard deployment system. - -## Capistrano - -### Set up - -First you need to customise your deployment settings, e.g. the name of the server you're deploying to. This is done by copying the example file `config/deploy.yml.example` to `config/deploy.yml` and editing the settings to suit you. - -TODO: The following instructions could be greatly improved - -These are the general steps required to get your staging server up and running: - -* Install packages from `config/packages` -* Install Postgres and configure a user -* Create a directory to deploy to and make sure your deployment user can write to it -* Run `cap deploy:setup` to create directories, etc. -* Run `cap deploy:update_code` so that we've got a copy of the example config on the server. This process will take a long time installing gems, etc. it will also fail on `rake:themes:install` but that's OK -* SSH to the server, change to the `deploy_to` directory -* `cp releases/[SOME_DATE]/config/general.yml-example shared/general.yml` -* `cp releases/[SOME_DATE]/config/database.yml-example shared/database.yml` -* Edit those files to match your required settings -* Back on your machine run `cap deploy` and it should successfully deploy -* Run the DB migrations `cap deploy:migrate` -* Build the Xapian DB `cap xapian:rebuild_index` -* Configure Apache/Passenger with a DocumentRoot of `your_deploy_to/current/public` -* Phew. Time to admire your work by browsing to the server! - -### Usage - -Ensure you've got a `config/deploy.yml` file with the correct settings for your site. You'll need to share this with everyone in your team that deploys so it might be a good idea to keep the latest version in a [Gist](http://gist.github.com/). - -To deploy to staging just run `cap deploy` but if you want to deploy to production you need to run `cap -S stage=production deploy`. - -For additional usage instructions, see the [Capistrano wiki](https://github.com/capistrano/capistrano/wiki/). - -### TODO - -* Get `cap deploy:setup` to do most of the work described above in the *Set up* section -* Use [Whenever](https://github.com/javan/whenever) to set up cronjobs diff --git a/doc/INSTALL-exim4.md b/doc/INSTALL-exim4.md deleted file mode 100644 index 796fb295c..000000000 --- a/doc/INSTALL-exim4.md +++ /dev/null @@ -1,99 +0,0 @@ -As an example of how to set up your MTA, in exim on Ubuntu, you might -add the following to its configuration. - -In `/etc/exim4/conf.d/main/04_alaveteli_options`: - - ALAVETELI_HOME=/path/to/alaveteli/software - ALAVETELI_USER=www-data - log_file_path=/var/log/exim4/exim-%slog-%D - MAIN_LOG_SELECTOR==+all -retry_defer - extract_addresses_remove_arguments=false - -(The user ALAVETELI_USER should have write permissions on ALAVETELI_HOME). - -Note that the name and location of the log files created by Exim must match -what the `load-mail-server-logs` script expects, hence the need for the extra -`log_file_path` setting. And the `check-recent-requests-sent` scripts expects -the logs to contain the `from=<...>` envelope information, so we make the -logs more verbose with `log_selector`. The ALAVETELI_USER may need to also -need to be added to the `trusted_users` list in your Exim config in order to -set the return path on outgoing mail, depending on your setup. - -In `/etc/exim4/conf.d/router/04_alaveteli`: - - alaveteli_request: - debug_print = "R: alaveteli for $local_part@$domain" - driver = redirect - data = ${lookup{$local_part}wildlsearch{ALAVETELI_HOME/config/aliases}} - pipe_transport = alaveteli_mailin_transport - -In `/etc/exim4/conf.d/transport/04_alaveteli`: - - alaveteli_mailin_transport: - driver = pipe - command = $address_pipe ${lc:$local_part} - current_directory = ALAVETELI_HOME - home_directory = ALAVETELI_HOME - user = ALAVETELI_USER - group = ALAVETELI_USER - -And, assuming you set `INCOMING_EMAIL_PREFIX` in your config at -`config/general` to "foi+", create `config/aliases` with the following -content: - - ^foi\\+.*: |/path/to/alaveteli/software/script/mailin - -You should also configure exim to discard any messages sent to the -`BLACKHOLE_PREFIX` address, whose default value is -'do-not-reply-to-this-address'. For example, add the following to -config/aliases: - - # We use this for envelope from for some messages where we don't care about delivery - do-not-reply-to-this-address: :blackhole: - -If you want to make use of the automatic bounce-message handling, then -set the `TRACK_SENDER_EMAIL` address to be filtered through -`script/handle-mail-replies`. Messages that are not bounces or -out-of-office autoreplies will be forwarded to -`FORWARD_NONBOUNCE_RESPONSES_TO`. For example, in WhatDoTheyKnow the -configuration looks like this: - - raw_team: [a list of people on the team] - team: |/path/to/alaveteli/software/script/handle-mail-replies - -with `FORWARD_NONBOUNCE_RESPONSES_TO: 'raw_team@whatdotheyknow.com'` - -Finally, make sure you have `dc_use_split_config='true'` in -`/etc/exim4/update-exim4.conf.conf`, and execute the command -`update-exim4.conf`. - -NB: if the file `/etc/exim4/exim4.conf` exists then `update-exim4.conf` -will silently do nothing. Some distributions include this file. If -yours does, you will need to rename it before running `update-exim4.conf`. - -(You may also want to set `dc_eximconfig_configtype='internet'`, -`dc_local_interfaces='0.0.0.0 ; ::1'`, and -`dc_other_hostnames='<your-host-name>'`) - -# Troubleshooting - -To test mail delivery, run: - - exim -bt foi+request-1234@localhost - -This should tell you which routers are being processed. You should -see something like: - - $ exim -bt foi+request-1234@localhost - R: alaveteli pipe for snafflerequest-234@localhost - snafflerequest-234@localhost -> |/home/alaveteli/alaveteli/script/mailin - transport = alaveteli_mailin_transport - -This tells you that the routing part (making emails to -`foi\+.*@localhost` be forwarded to Alaveteli's `mailin` script) is -working. - -There is a great -[Exim Cheatsheet](http://bradthemad.org/tech/notes/exim_cheatsheet.php) -online that you may find useful. - diff --git a/doc/INSTALL-postfix.md b/doc/INSTALL-postfix.md deleted file mode 100644 index a73d67ce1..000000000 --- a/doc/INSTALL-postfix.md +++ /dev/null @@ -1,68 +0,0 @@ -As an example of how to set up your MTA, in postfix on Ubuntu, you might -add the following to its configuration. - -In /etc/postfix/master.cf: - - alaveteli unix - n n - 50 pipe - flags=R user=ALAVETELI_USER argv=ALAVETELI_HOME/script/mailin - -In /etc/postfix/main.cf - - virtual_alias_maps = regexp:/etc/postfix/regexp - -For example - -ALAVETELI_HOME=/path/to/alaveteli/software -ALAVETELI_USER=www-data - -The user ALAVETELI_USER should have write permissions on ALAVETELI_HOME. - -And, assuming you set `OPTION_INCOMING_EMAIL_PREFIX` in your config at -`config/general` to "foi+", create `/etc/postfix/regexp` with the following -content: - - /^foi.*/ alaveteli - - -You should also configure postfix to discard any messages sent to the `BLACKHOLE_PREFIX` -address, whose default value is 'do-not-reply-to-this-address'. For example, add the -following to /etc/aliases: - - # We use this for envelope from for some messages where we don't care about delivery - do-not-reply-to-this-address: :blackhole: - -# Logging - -For the postfix logs to be read by the script 'load-mail-server-logs' succesfully they need to be log rotated with a date in the filename. Since that will create a lot of rotated log files (one for each day), it's good to have them in their own directory. For example (on Ubuntu) /etc/rsyslog.d/50-default.conf - - mail.* -/var/log/mail/mail.log - -And also edit /etc/logrotate.d/rsyslog: - - /var/log/mail/mail.log - { - rotate 30 - daily - dateext - missingok - notifempty - compress - delaycompress - sharedscripts - postrotate - reload rsyslog >/dev/null 2>&1 || true - endscript - } - -You'll also need to tell Alaveteli where the log files are stored and that they're in postfix format. Update config/general.yml with: - - MTA_LOG_PATH: '/var/log/mail/mail.log-*' - MTA_LOG_TYPE: "postfix" - -# Troubleshooting - -To test mail delivery, run: - - $ /usr/sbin/sendmail -bv foi+requrest-1234@localhost - -This tells you if sending the emails to 'foi\+.*localhost' is working. diff --git a/doc/INSTALL.md b/doc/INSTALL.md deleted file mode 100644 index f6563be99..000000000 --- a/doc/INSTALL.md +++ /dev/null @@ -1,651 +0,0 @@ -# Installation Script and AMI - -The easiest options for installating Alaveteli for evaluation -are to use our install script or to use the AMI (Amazon Machine -Image) to create an instance on Amazon EC2. These options are -described below. If you would prefer to install the site -manually, please go to the Manual Installation section below. - -## Installing from an AMI (Amazon Machine Image) - -To help people try out Alaveteli, we have created an AMI (Amazon -Machine Image) with a basic installation of Alaveteli, which you -can use to create a running server on an Amazon EC2 instance. -This creates an instance that runs in development mode, so we -wouldn't recommend you use it for a production system without -changing the configuration. - -Unfortunately, Alaveteli will not run properly on a free Micro -instance due to the low amount of memory available on those -instances; you will need to use at least a Small instance, which -Amazon will charge for. - -The AMI can be found in the EU West (Ireland) region, with the -ID ami-8603f4f1 and name “Basic Alaveteli installation -2014-01-29”. You can launch an instance based on that AMI with -[this link](https://console.aws.amazon.com/ec2/home?region=eu-west-1#launchAmi=ami-8603f4f1). - -When you create an EC2 instance based on that AMI, make sure -that you choose Security Groups that allows at least inbound -HTTP, HTTPS, SSH and, if you want to test incoming mail as well, -SMTP. - -When your EC2 instance is launched, you will be able to log in -as the `ubuntu` user. This user can `sudo` freely to run -commands as root. However, the code is actually owned by (and -runs as) the `alaveteli` user. After creating the instance, you -may want to edit a configuration file to customize the site's -configuration. That configuration file is -`/var/www/alaveteli/alaveteli/config/general.yml`, which can be -edited with: - - ubuntu@ip-10-58-191-98:~$ sudo su - alaveteli - alaveteli@ip-10-58-191-98:~$ cd alaveteli - alaveteli@ip-10-58-191-98:~/alaveteli$ nano config/general.yml - -Then you should restart the Thin webserver with: - - alaveteli@ip-10-58-191-98:~/alaveteli$ logout - ubuntu@ip-10-58-191-98:~$ sudo /etc/init.d/alaveteli restart - -If you find the hostname of your EC2 instance from the AWS -console, you should then be able to see the site at -`http://your-ec2-hostname.eu-west-1.compute.amazonaws.com` - -If you have any problems or questions, please ask on the -[Alaveteli Google Group](https://groups.google.com/forum/#!forum/alaveteli-dev) -or [report an issue](https://github.com/mysociety/alaveteli/issues?state=open). - -## Installing with the Installation Script - -If you have a clean installation of Debian squeeze or Ubuntu -precise, you can use an install script in our commonlib -repository to set up a working instance of Alaveteli. This is -not suitable for production (it runs in development mode, for -example) but should set up a functional installation of the -site. - -**Warning: only use this script on a newly installed server – it -will make significant changes to your server’s setup, including -modifying your nginx setup, creating a user account, creating a -database, installing new packages etc.** - -To download the script, run the following command: - - curl -O https://raw.github.com/mysociety/commonlib/master/bin/install-site.sh - -If you run this script with `sh install-site.sh`, you'll see its -usage message: - - Usage: ./install-site.sh [--default] <SITE-NAME> <UNIX-USER> [HOST] - HOST is only optional if you are running this on an EC2 instance. - --default means to install as the default site for this server, - rather than a virtualhost for HOST. - -In this case `<SITE-NAME>` should be `alaveteli`. `<UNIX-USER>` -is the name of the Unix user that you want to own and run the -code. (This user will be created by the script.) - -The `HOST` parameter is a hostname for the server that will be -usable externally – a virtualhost for this name will be created -by the script, unless you specified the `--default` option. This -parameter is optional if you are on an EC2 instance, in which -case the hostname of that instance will be used. - -For example, if you wish to use a new user called `alaveteli` -and the hostname `alaveteli.127.0.0.1.xip.io`, creating a -virtualhost just for that hostname, you could download and run -the script with: - - sudo sh install-site.sh alaveteli alaveteli alaveteli.127.0.0.1.xip.io - -([xip.io](http://xip.io/) is a helpful domain for development.) - -Or, if you want to set this up as the default site on an EC2 -instance, you could download the script, make it executable and -then invoke it with: - - sudo ./install-site.sh --default alaveteli alaveteli - -When the script has finished, you should have a working copy of -the website, accessible via the hostname you supplied to the -script. - -If you have any problems or questions, please ask on the -[Alaveteli Google Group](https://groups.google.com/forum/#!forum/alaveteli-dev) -or [report an issue](https://github.com/mysociety/alaveteli/issues?state=open). - -# Manual Installation - -These instructions assume Debian Squeeze (64-bit) or Ubuntu 12.04 LTS (precise). -[Install instructions for OS X](https://github.com/mysociety/alaveteli/wiki/OS-X-Quickstart) -are under development. Debian Squeeze is the best supported -deployment platform. - -Commands are intended to be run via the terminal or over ssh. - -As an aid to evaluation, there is an -[Amazon AMI](https://github.com/mysociety/alaveteli/wiki/Alaveteli-ec2-ami) -with all these steps configured. It is *not* production-ready. - -## Get Alaveteli - -To start with, you may need to install git, e.g. with `sudo apt-get -install git-core` - -Next, get hold of the Alaveteli source code from github: - - git clone https://github.com/mysociety/alaveteli.git - cd alaveteli - -This will get the development branch, which has the latest (possibly -buggy) code. If you don't want to add or try new features, swap to the -master branch (which always contains the latest stable release): - - git checkout master - -## Package pinning - -You need to configure [apt-pinning](http://wiki.debian.org/AptPreferences#Pinning-1) preferences in order to prevent packages being pulled from the debian wheezy distribution in preference to the stable distribution once you have added the wheezy repository as described below. - -In order to configure apt-pinning and to keep most packages coming from the Debian stable repository while installing the ones required from wheezy and the mySociety repository you need to run the following commands: - - echo "Package: *" >> /tmp/preferences - echo "Pin: release a=squeeze-backports">> /tmp/preferences - echo "Pin-Priority: 200" >> /tmp/preferences - echo "" >> /tmp/preferences - echo "Package: *" >> /tmp/preferences - echo "Pin: release a=wheezy">> /tmp/preferences - echo "Pin-Priority: 50" >> /tmp/preferences - sudo cp /tmp/preferences /etc/apt/ - rm /tmp/preferences - -## Install system dependencies - -These are packages that the software depends on: third-party software -used to parse documents, host the site, etc. There are also packages -that contain headers necessary to compile some of the gem dependencies -in the next step. - -If you are running Debian, add the following repositories to -`/etc/apt/sources.list` and run `apt-get update`: - - deb http://debian.mysociety.org squeeze main - deb http://ftp.debian.org/debian/ wheezy main non-free contrib - deb http://backports.debian.org/debian-backports squeeze-backports main contrib non-free - -The repositories above allow us to install the packages -`wkhtmltopdf-static` and `bundler` using `apt`; so if you're running -Ubuntu, you won't be able to use the above repositories, and you will -need to comment out those two lines in `config/packages` before -following the next step (and install bundler manually). - -Now install the packages that are listed in config/packages using apt-get -e.g.: - - sudo apt-get install `cut -d " " -f 1 config/packages | grep -v "^#"` - -Some of the files also have a version number listed in config/packages -- check that you have appropriate versions installed. Some also list -"|" and offer a choice of packages. - -## Install Ruby dependencies - -To install Alaveteli's Ruby dependencies, we need to install -bundler. In Debian, this is provided as a package (installed as part -of the package install process above). You could also install it as a -gem: - - sudo gem1.8 install bundler - -## Install mySociety libraries - -You will also want to install mySociety's common ruby libraries and the Rails -code. Run: - - git submodule update --init - -to fetch the contents of the submodules. - -### Packages customised by mySociety - -Debian users should add the mySociety debian archive to their -`/etc/apt/sources.list` as described above. Doing this and following -the above instructions should install a couple of custom -dependencies. Users of other platforms can optionally install these -dependencies manually, as follows: - -1. If you would like users to be able to download pretty PDFs as part of -the downloadable zipfile of their request history, you should install -[wkhtmltopdf](http://code.google.com/p/wkhtmltopdf/downloads/list). -We recommend downloading the latest, statically compiled version from -the project website, as this allows running headless (i.e. without a -graphical interface running) on Linux. If you do install -`wkhtmltopdf`, you need to edit a setting in the config file to point -to it (see below). If you don't install it, everything will still -work, but users will get ugly, plain text versions of their requests -when they download them. - -2. Version 1.44 of `pdftk` contains a bug which makes it to loop forever -in certain edge conditions. Until it's incorporated into an official -release, you can either hope you don't encounter the bug (it ties up a -rails process until you kill it) you'll need to patch it yourself or -use the Debian package compiled by mySociety (see link in -[issue 305](https://github.com/mysociety/alaveteli/issues/305)) - - -## Configure Database - -There has been a little work done in trying to make the code work with -other databases (e.g. SQLite), but the currently supported database is -PostgreSQL. - -If you don't have it installed: - - apt-get install postgresql postgresql-client - -Now we need to set up the database config file to contain the name, -username and password of your postgres database. - -* copy `database.yml-example` to `database.yml` in `alaveteli/config` -* edit it to point to your local postgresql database in the development - and test sections and create the databases: - -Make sure that the user specified in database.yml exists, and has full -permissions on these databases. As they need the ability to turn off -constraints whilst running the tests they also need to be a superuser. -If you don't want your database user to be a superuser, you can add a line -`constraint_disabling: false` to the test config in database.yml, as seen in database.yml-example - -You can create a `foi` user from the command line, thus: - - # su - postgres - $ createuser -s -P foi - -And you can create a database thus: - - $ createdb -T template0 -E SQL_ASCII -O foi foi_production - $ createdb -T template0 -E SQL_ASCII -O foi foi_test - $ createdb -T template0 -E SQL_ASCII -O foi foi_development - -We create using the ``SQL_ASCII`` encoding, because in postgres this -is means "no encoding"; and because we handle and store all kinds of -data that may not be valid UTF (for example, data originating from -various broken email clients that's not 8-bit clean), it's safer to be -able to store *anything*, than reject data at runtime. - -## Configure email - -You will need to set up an email server (MTA) to send and receive -emails. Full configuration for an MTA is beyond the scope of this -document, though we describe an example configuration for Exim in -`INSTALL-exim4.md`. - -Note that in development mode, mail is handled by default by mailcatcher -so that you can see the mails in a browser - see http://mailcatcher.me/ -for more details. Start mailcatcher by running `bundle exec mailcatcher` -in your application directory. - -### Minimal - -If you just want to get the tests to pass, you will at a minimum need -to allow sending emails via a `sendmail` command (a requirement met, -for example, with `sudo apt-get install exim4`). - -### Detailed - -When an authority receives an email, the email's `reply-to` field is a -magic address which is parsed and consumed by the Rails app. - -To receive such email in a production setup, you will need to -configure your MTA to pipe incoming emails to the Alaveteli script -`script/mailin`. Therefore, you will need to configure your MTA to -accept emails to magic addresses, and to pipe such emails to this -script. - -Magic email addresses are of the form: - - <foi+request-3-691c8388@example.com> - -The respective parts of this address are controlled with options in -config/general.yml, thus: - - INCOMING_EMAIL_PREFIX = 'foi+' - INCOMING_EMAIL_DOMAIN = 'example.com' - -When you set up your MTA, note that if there is some error inside -Rails, the email is returned with an exit code 75, which for Exim at -least means the MTA will try again later. Additionally, a stacktrace -is emailed to `CONTACT_EMAIL`. - -`INSTALL-exim4.md` describes one possible configuration for Exim (>= -1.9). - -A well-configured installation of this code will separately have had -Exim make a backup copy of the email in a separate mailbox, just in -case. - -## Set up configs - -Copy `config/general.yml-example` to `config/general.yml` and edit to -your taste. - -Note that the default settings for frontpage examples are designed to -work with the dummy data shipped with Alaveteli; once you have real -data, you should certainly edit these. - -The default theme is the "Alaveteli" theme. When you run -`rails-post-deploy` (see below), that theme gets installed -automatically. - -Finally, copy `config/newrelic.yml-example` to `config/newrelic.yml`. -This file contains configuration information for the New Relic -performance management system. By default, monitoring is switched off -by the `agent_enabled: false` setting. See https://github.com/newrelic/rpm -for instructions on switching on local and remote performance analysis. - -## Deployment - -In the 'alaveteli' directory, run: - - script/rails-post-deploy - -(This will need execute privs so `chmod 755` if necessary.) This sets -up directory structures, creates logs, installs/updates themes, runs -database migrations, etc. You should run it after each new software -update. - -One of the things the script does is install dependencies (using -`bundle install`). Note that the first time you run it, part of the -`bundle install` that compiles `xapian-full` takes a *long* time! - -If you want some dummy data to play with, you can try loading the -fixtures that the test suite uses into your development database. You -can do this with: - - script/load-sample-data - -Next we need to create the index for the search engine (Xapian): - - script/rebuild-xapian-index - -If this fails, the site should still mostly run, but it's a core -component so you should really try to get this working. - -## Run the Tests - -Make sure everything looks OK: - - bundle exec rake spec - -If there are failures here, something has gone wrong with the -preceding steps (see the next section for a common problem and -workaround). You might be able to move on to the next step, depending -on how serious they are, but ideally you should try to find out what's -gone wrong. - -### glibc bug workaround - -There's a -[bug in glibc](http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=637239) -which causes Xapian to segfault when running the tests. Although the -bug report linked to claims it's fixed in the current Debian stable, -it's not as of version `2.11.3-2`. - -Until it's fixed (e.g. `libc6 2.13-26` does work), you can get the -tests to pass by setting `export LD_PRELOAD=/lib/libuuid.so.1`. - -## Run the Server - -Run the following to get the server running: - - bundle exec rails server --environment=development - -By default the server listens on all interfaces. You can restrict it to the -localhost interface by adding ` --binding=127.0.0.1` - -The server should have told you the URL to access in your browser to see -the site in action. - -## Administrator privileges - -The administrative interface is at the URL `/admin`. - -Only users with the `super` admin level can access the admin -interface. Users create their own accounts in the usual way, and then -administrators can give them `super` privileges. - -There is an emergency user account which can be accessed via -`/admin?emergency=1`, using the credentials `ADMIN_USERNAME` and -`ADMIN_PASSWORD`, which are set in `general.yml`. To bootstrap the -first `super` level accounts, you will need to log in as the emergency -user. You can disable the emergency user account by setting `DISABLE_EMERGENCY_USER` to `true` in `general.yml`. - -Users with the superuser role also have extra privileges in the -website frontend, such as being able to categorise any request, being -able to view items that have been hidden from the search, and being -presented with "admin" links next to individual requests and comments -in the front end. - -It is possible completely to override the administrator authentication -by setting `SKIP_ADMIN_AUTH` to `true` in `general.yml`. - -## Cron jobs and init scripts - -`config/crontab-example` contains the cronjobs run on WhatDoTheyKnow. -It's in a strange templating format they use in mySociety. mySociety -render the example file to reference absolute paths, and then drop it -in `/etc/cron.d/` on the server. - -The `ugly` format uses simple variable substitution. A variable looks -like `!!(*= $this *)!!`. The variables are: - -* `vhost`: part of the path to the directory where the software is - served from. In the mySociety files, it usually comes as - `/data/vhost/!!(*= $vhost *)!!` -- you should replace that whole - port with a path to the directory where your Alaveteli software - installation lives, e.g. `/var/www/` -* `vhost_dir`: the entire path to the directory where the software is - served from. -- you should replace this with a path to the - directory where your Alaveteli software installation lives, - e.g. `/var/www/` -* `vcspath`: the name of the alaveteli checkout, e.g. `alaveteli`. - Thus, `/data/vhost/!!(*= $vhost *)!!/!!(*= $vcspath *)!!` might be - replaced with `/var/www/alaveteli` in your cron tab -* `user`: the user that the software runs as -* `site`: a string to identify your alaveteli instance - -There is a rake task that will help to rewrite this file into -one that is useful to you, which can be invoked with: - - bundle exec rake config_files:convert_crontab \ - DEPLOY_USER=deploy \ - VHOST_DIR=/dir/above/alaveteli \ - VCSPATH=alaveteli \ - SITE=alaveteli \ - CRONTAB=config/crontab-example > crontab - -You should change the `DEPLOY_USER`, `VHOST_DIR`, `VCSPATH` and -`SITE` environment variables to match your server and -installation. You should also edit the resulting `crontab` file -to customize the `MAILTO` variable. - -One of the cron jobs refers to a script at -`/etc/init.d/foi-alert-tracks`. This is an init script, a copy of -which lives in `config/alert-tracks-debian.ugly`. As with the cron -jobs above, replace the variables (and/or bits near the variables) -with paths to your software. You can use the rake task `rake -config_files:convert_init_script` to do this. -`config/purge-varnish-debian.ugly` is a -similar init script, which is optional and not required if you choose -not to run your site behind Varnish (see below). Either tweak the file -permissions to make the scripts executable by your deploy user, or add the -following line to your sudoers file to allow these to be run by your deploy -user (named `deploy` in this case): - - deploy ALL = NOPASSWD: /etc/init.d/foi-alert-tracks, /etc/init.d/foi-purge-varnish - -The cron jobs refer to a program `run-with-lockfile`. See -[this issue](https://github.com/mysociety/alaveteli/issues/112) for a -discussion of where to find this program, and how you might replace -it. This [one line script](https://gist.github.com/3741194) can install -this program system-wide. - -## Set up production web server - -It is not recommended to run the website using the default Rails web -server. There are various recommendations here: -http://rubyonrails.org/deploy - -We usually use Passenger / mod_rails. The file at `conf/httpd.conf-example` -gives you an example config file for WhatDoTheyKnow. At a minimum, you should -include the following in an Apache configuration file: - - PassengerResolveSymlinksInDocumentRoot on - PassengerMaxPoolSize 6 # Recommend setting this to 3 or less on servers with 512MB RAM - -Under all but light loads, it is strongly recommended to run the -server behind an http accelerator like Varnish. A sample varnish VCL -is supplied in `../conf/varnish-alaveteli.vcl`. - -It's strongly recommended that you run the site over SSL. (Set FORCE_SSL to true in -config/general.yml). For this you will need an SSL certificate for your domain and you will -need to configure an SSL terminator to sit in front of Varnish. If you're already using Apache -as a web server you could simply use Apache as the SSL terminator. A minimal configuration -would look something like this: - -<VirtualHost *:443> - ServerName www.yourdomain - - ProxyRequests Off - ProxyPreserveHost On - ProxyPass / http://localhost:80/ - ProxyPassReverse / http://localhost:80/ - RequestHeader set X-Forwarded-Proto 'https' - - SSLEngine on - SSLProtocol all -SSLv2 - SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM - - SSLCertificateFile /etc/apache2/ssl/ssl.crt - SSLCertificateKeyFile /etc/apache2/ssl/ssl.key - SSLCertificateChainFile /etc/apache2/ssl/sub.class2.server.ca.pem - SSLCACertificateFile /etc/apache2/ssl/ca.pem - SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown -</VirtualHost> - -Notice the line "RequestHeader" that sets the X-Forwarded-Proto header. This is important. This ultimately tells Rails that it's serving a page over https and so it knows to include that in any absolute urls it serves. - -Some -[production server best practice notes](https://github.com/mysociety/alaveteli/wiki/Production-Server-Best-Practices) -are evolving on the wiki. - -## Upgrading Alaveteli - -The developer team policy is that the master branch in git should -always contain the latest stable release. Therefore, in production, -you should usually have your software deployed from the master branch, -and an upgrade can be simply `git pull`. - -Patch version increases (e.g. 1.2.3 -> 1.2.4) should not require any -further action on your part. - -Minor version increases (e.g. 1.2.4 -> 1.3.0) will usually require -further action. You should read the `CHANGES.md` document to see -what's changed since your last deployment, paying special attention to -anything in the "Updgrading" sections. - -Any upgrade may include new translations strings, i.e. new or altered -messages to the user that need translating to your locale. You should -visit Transifex and try to get your translation up to 100% on each new -release. Failure to do so means that any new words added to the -Alaveteli source code will appear in your website in English by -default. If your translations didn't make it to the latest release, -you will need to download the updated `app.po` for your locale from -Transifex and save it in the `locale/` folder. - -You should always run the script `scripts/rails-post-deploy` after -each deployment. This runs any database migrations for you, plus -various other things that can be automated for deployment. - -## Troubleshooting - -* **Incoming emails aren't appearing in my Alaveteli install** - - First, you need to check that your MTA is delivering relevant - incoming emails to the `script/mailin` command. There are various - ways of setting your MTA up to do this; we have documented one way - of doing it in Exim at `doc/INSTALL-exim4.conf`, including a - command you can use to check that the email routing is set up - correctly. - - Second, you need to test that the mailin script itself is working - correctly, by running it from the command line, First, find a - valid "To" address for a request in your system. You can do this - through your site's admin interface, or from the command line, - like so: - - $ ./script/console - Loading development environment (Rails 2.3.14) - >> InfoRequest.find_by_url_title("why_do_you_have_such_a_fancy_dog").incoming_email - => "request-101-50929748@localhost" - - Now take the source of a valid email (there are some sample emails in - `spec/fixtures/files/`); edit the `To:` header to match this address; - and then pipe it through the mailin script. A non-zero exit code - means there was a problem. For example: - - $ cp spec/fixtures/files/incoming-request-plain.email /tmp/ - $ perl -pi -e 's/^To:.*/To: <request-101-50929748@localhost>/' /tmp/incoming-request-plain.email - $ ./script/mailin < /tmp/incoming-request-plain.email - $ echo $? - 75 - - The `mailin` script emails the details of any errors to - `CONTACT_EMAIL` (from your `general.yml` file). A common problem is - for the user that the MTA runs as not to have write access to - `files/raw_emails/`. - -* **Various tests fail with "*Your PostgreSQL connection does not support - unescape_bytea. Try upgrading to pg 0.9.0 or later.*"** - - You have an old version of `pg`, the ruby postgres driver. In - Ubuntu, for example, this is provided by the package `libdbd-pg-ruby`. - - Try upgrading your system's `pg` installation, or installing the pg - gem with `gem install pg` - -* **Some of the tests relating to mail are failing, with messages like - "*when using TMail should load an email with funny MIME settings' - FAILED*"** - - This sounds like the tests are running using the `production` - environment, rather than the `test` environment, for some reason. - -* **Non-ASCII characters are being displayed as asterisks in my incoming messages** - - We rely on `elinks` to convert HTML email to plain text. - Normally, the encoding should just work, but under some - circumstances it appears that `elinks` ignores the parameters - passed to it from Alaveteli. - - To force `elinks` always to treat input as UTF8, add the following - to `/etc/elinks/elinks.conf`: - - set document.codepage.assume = "utf-8" - set document.codepage.force_assumed = 1 - - You should also check that your locale is set up correctly. See - [https://github.com/mysociety/alaveteli/issues/128#issuecomment-1814845](this issue followup) - for further discussion. - -* **I'm seeing `rake: command not found` when running the post install script - - The script uses `rake`. - - It may be that the binaries installed by bundler are not put in the - system `PATH`; therefore, in order to run `rake` (needed for - deployments), you may need to do something like: - - ln -s /usr/lib/ruby/gems/1.8/bin/rake /usr/local/bin/ - diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 000000000..d8e9b409c --- /dev/null +++ b/doc/README.md @@ -0,0 +1,9 @@ +The main Alaveteli documentation now lives on the [Alaveteli documentation site](http://code.alaveteli.org/docs/) + +Specifically: + +[Installation](http://code.alaveteli.org/docs/installing/) +[Themes](http://code.alaveteli.org/docs/customising/themes/) +[Translation](http://code.alaveteli.org/docs/customising/translation/) +[Email](http://code.alaveteli.org/docs/installing/email/) +[Deployment](http://code.alaveteli.org/docs/installing/deploy/) diff --git a/doc/THEMES.md b/doc/THEMES.md deleted file mode 100644 index 8a4828a99..000000000 --- a/doc/THEMES.md +++ /dev/null @@ -1,165 +0,0 @@ -When installing an Alaveteli site, there are a few things that you -might want to do to customise it, beyond the available settings in the -`config/general` file. - -The most common requirement is to brand the site: at a minimum, -inserting your own logo and colour scheme. You may also want to tweak -the different states that a request can go through. You'll also want -to edit the categories that public bodies can appear in (i.e. the -groupings on the left hand side of the -"[View authorities](https://www.whatdotheyknow.com/body/list/all)" page -on WhatDoTheyKnow. - -There may also be other things you want to customise; drop a line on -the developer's mailing list to discuss, if so. We're still working -out the best way of doing these kinds of customisations! - -In any case, the important principle to bear in mind is that the less -you override and customise the code, the easier your site will be to -maintain in the long term. Any customisation is possible, but for -each customisation beyond the simple cases documented here, ask -yourself or your client, "can we possibly live without this?" If the -answer is "no", then consider starting a discussion about a pluggable -way of achieving your goals, rather than overriding any of the core -code. - -# General principles - -We try to encapsulate all site-specific functionality in one of these -places: - -* Site configuration (e.g. the name of your site, the available - languages etc -- all in `config/general`) -* Data (e.g. the public bodies to whom requests should be addressed) -* A rails "plugin", installed in `vendor/plugins/`. We call these - "themes". - -This document is about what you can do in a theme. - -By default, the sample theme ("alavetelitheme") has already been -installed. See the setting `THEME_URLS` in `general.yml` for an -explanation. - -You can also install the sample theme by hand, by running: - - bundle exec rails plugin install git://github.com/mysociety/alavetelitheme.git - -The sample theme contains examples for nearly everything you might -want to customise. You should probably make a copy, rename it, and -use that as the basis for your own theme. - -# Make sure your theme is as lightweight as possible - -The more you put in your theme, the harder it will be to upgrade to -future versions of Alaveteli. Everything you place in your theme -overrides things in the core theme, so if you make a new "main -template", then new widgets that appear in the core theme won't appear -on your website. - -Therefore, you should consider how you can brand your website without -changing much in the core theme. The ideal would be if you are able -to rebrand the site by only changing the CSS. You will also need to -add custom help pages, as described below. - -# Branding the site - -The core templates that comprise the layout and user interface of an -Alaveteli site live in `app/views/`. They use Rails' ERB syntax. -For example, the template for the home page lives at -`app/views/general/frontpage.html.erb`, and the template for the "about -us" page is at `app/views/help/about.html.erb`. - -Obviously, you *could* edit those core files directly, but this would -be a Bad Idea, because you would find it increasingly hard to do -upgrades. Having said that, sometimes you may want to change the core -templates in a way that would benefit everyone, in which case, discuss -the changes on the mailing list, make them in a fork of Alaveteli, and -then issue a pull request. - -Normally, however, you should override these pages **in your own -theme**, by placing them at a corresponding location within your -theme's `lib/` directory. These means that a file at -`vendor/plugins/alavetelitheme/lib/help/about.rhml` will appear -instead of the core "about us" file. - -Rails expects all its stylesheets to live at `<railshome>/public`, -which presents a problem for plugins. Here's how we solve it: the -stylesheet and associated resources for your theme live (by -convention) in `alavetelitheme/public/`. This is symlinked from -the main Rails app -- see `alavetelitheme/install.rb` to see how this -happens. - -The partial at -`alavetelitheme/lib/views/general/_before_head_end.html.erb` includes the -custom CSS in your theme's stylesheet folder (by convention, in -`alavetelitheme/public/stylesheets/`), with: - - <%= stylesheet_link_tag "/alavetelitheme/stylesheets/custom" %> - -...which will, usually, need changing for your theme. - -# Adding your own categories for public bodies - -Categories are implemented in Alaveteli using tags. Specific tags can -be designated to group authorities together as a category. - -There's a file in the sample theme, -`alavetelitheme/lib/public_body_categories_en.rb`, which contains a -nested structure that defines categories. It contains a comment -describing its structure. You should make a copy of this file for each -locale you support. - -# Customising the request states - -As mentioned above, if you can possibly live with the -[default Alaveteli request statuses](https://github.com/mysociety/alaveteli/wiki/Alaveteli's-request-statuses), -it would be good to do so. Note that you can set how many days counts -as "overdue" in the main site config file. - -If you can't live with the states as they are, there's a very basic -way to add to them (which will get improved over time). There's not -currently a way to remove any easily. There is an example of how to -do this in the `alavetelitheme`. - -To do add states, create two modules in your theme, -`InfoRequestCustomStates` and `RequestControllerCustomStates`. The -former must have these methods: - -* `theme_calculate_status`: return a tag to identify the current state of the request -* `theme_extra_states`: return a list of tags which identify the extra states you'd like to support -* `theme_display_status`: return human-readable strings corresponding with these tags - -The latter must have one method: - -* `theme_describe_state`: Return a notice for the user suitable for - displaying after they've categorised a request; and redirect them to - a suitable next page - -When you've added your extra states, you also need to create the following files in your theme: - -* `lib/views/general/_custom_state_descriptions.html.erb`: Descriptions - of your new states, suitable for displaying to end users -* `lib/views/general/_custom_state_transitions_complete.html.erb`: - Descriptions for any new states that you might characterise as - 'completion' states, for displaying on the categorisation form that - we ask requestors to fill out -* `lib/views/general/_custom_state_transitions_pending.html.erb`: As - above, but for new states you might characterise as 'pending' - states. - -You can see examples of these customisations in -[this commit](https://github.com/sebbacon/informatazyrtare-theme/commit/2b240491237bd72415990399904361ce9bfa431d) -for the Kosovan version of Alaveteli, Informata Zyrtare (ignore the -file `lib/views/general/_custom_state_transitions.html.erb`, which is -unused). - -# Adding new pages in the navigation - -`alavetelitheme/lib/config/custom-routes.rb` allows you to extend the base routes in -Alaveteli. The example in `alavetelitheme` adds an extra help page. -You can also use this to override the behaviour of specific pages if -necessary. - -# Adding or overriding models and controllers - -If you need to extend the behaviour of Alaveteli at the controller or model level, see `alavetelitheme/lib/controller_patches.rb` and `alavetelitheme/lib/model_patches.rb` for examples. diff --git a/doc/TRANSLATE.md b/doc/TRANSLATE.md deleted file mode 100644 index aef2cfdc9..000000000 --- a/doc/TRANSLATE.md +++ /dev/null @@ -1,106 +0,0 @@ -The software translations are implemented using GNU gettext, and the -resource files are managed in Transifex. - -The Transifex project is at -https://www.transifex.net/projects/p/alaveteli/; you'll probably want -an account there (ask on the mailing list). It has a fairly easy to -use interface for contributing translations. - -# Translation process: translator's view - -When a developer adds a new feature to the user interface in -Alaveteli, they use some code to mark sentences or words ("strings") -that they think will need to be translated. - -When the Alaveteli release manager is planning a release, they upload -a template containing all the strings to be translated (called a POT) -to Transifex. This causes your own translations in Transifex to be -updated with the latest strings. - -When you visit Transifex, it will prompt you to fill out values for -all new strings, and all strings that have been modified. In the case -where a string has only been slightly modified, such as with -punctuation ("Hello" has become "Hello!"), Transifex will suggest a -suitable translation for you (look for the "suggestions" tab under the -source string). - -In order for this feature to work properly, the release manager has to -download your translations, run a program that inserts the -suggestions, and then upload them again. Therefore, when a release -candidate is announced, make sure you have uploaded any outstanding -translations, or you will lose them. - -When a release candidate has been annouced, there is a **translation -freeze**: during this period, developers must not add any new strings -to the software, so you can be confident that you're translating -everything that will be in the final release. - -The release manager will also give you a **translation deadline**. After -this date, you can continue to contribute new translations, but they -won't make it into the release. - -## General notes on translation in Transifex - -Some bits of text will have comments attached to them from the Alaveteli -application developers about the context in which the text appears in the -application - these comments will appear under the 'Details' tab for the -text in Transifex. - -Some texts will have placeholders in them to indicate that another bit of text generated by Alaveteli will be inserted into them when they're displayed. They will look like `some text with a {{placeholder}}`. For these texts, don't translate the placeholder. It needs to stay exactly the same for the text to be inserted properly. - -Similarly, some texts will have some code in angle brackets in them to turn bits of the text into links, and other small bits of HTML formatting. e.g. `please <a href=\"{{url}}\">send it to us</a>`. Again, don't edit the bits in angle brackets, preserve them in your translation, just edit the text around them. - -Some bits of text are in the form of two bits of text separated by a `|` -character e.g. `IncomingMessage|Subject`. These represent attribute names, so -`IncomingMessage|Subject` is the subject attribute of an incoming message on -the site. You should not prioritise these types of text when translating - -they do not appear on the site anywhere at the moment, and when they do, -they will only be used in the admin interface. If you do translate them, only -translate the text after the `|`. - -# Translation process: release manager's view - -Before the Alaveteli release manager cuts a new release branch, they -must: - -* pick a date for the release branch to be cut ("release candidate date") -* make an announcement to the translators (using the "announcements" - feature in Transifex) that they should ensure they have any pending - translations saved in Transifex before the release candidate date -* make an announcement to the developers that all new strings should - be committed before the release candidate date -* on the release candidate date: - * download (`tx pull -a -f`) and commit all the current translations (important: - there's no revision history in Transifex!) - * you should also commit these translations to a hotfix for the - previous version, so they are preserved against the last known - good msgids - * regenerate the POT file and individual PO files for each - language, using `bundle exec rake - gettext:store_model_attributes`, followed by `bundle exec rake - gettext:find` - * careful of including msgids from themes in `lib/themes`; - you might want to move them out of the way before running - the above commands - * this updates the PO template, but also merges it with the - individual PO files, marking strings that have only changed - slightly as "fuzzy" - * reupload (`tx push -s -t`) the POT and PO files to Transifex from the - current release branch - * The point of uploading the PO files is that Transifex - converts the "fuzzy" suggestions from Transifex into - "suggestions" under each source string - * Note that Transifex *does not* preserve fuzzy strings in the - PO files it makes available for download, on the grounds - that Transifex supports multiple suggestions, whereas - gettext only allows one fuzzy suggestion per msgid. - * remove the fuzzy strings from the local PO files (because they - make Rails very noisy), and then commit the result. You can do - this by re-pulling from Transifex. -* on the release date: - * download and commit all the current translations to the current - release branch - -# Translations: developers' view - -See the [I18n guide](https://github.com/mysociety/alaveteli/wiki/I18n-guide) on the wiki. diff --git a/lib/attachment_to_html/adapters/rtf.rb b/lib/attachment_to_html/adapters/rtf.rb index 859c0e541..95f499689 100644 --- a/lib/attachment_to_html/adapters/rtf.rb +++ b/lib/attachment_to_html/adapters/rtf.rb @@ -73,6 +73,8 @@ module AttachmentToHTML # Works around http://savannah.gnu.org/bugs/?42015 in unrtf ~> 0.21 def sanitize_converted(html) + html.nil? ? html = '' : html + invalid = %Q(<!DOCTYPE html PUBLIC -//W3C//DTD HTML 4.01 Transitional//EN>) valid = %Q(<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN>") if html.include?(invalid) diff --git a/spec/helpers/date_time_helper_spec.rb b/spec/helpers/date_time_helper_spec.rb new file mode 100644 index 000000000..c4fdee1d1 --- /dev/null +++ b/spec/helpers/date_time_helper_spec.rb @@ -0,0 +1,71 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe DateTimeHelper do + + include DateTimeHelper + + describe :simple_date do + + it 'formats a date in html by default' do + time = Time.utc(2012, 11, 07, 21, 30, 26) + self.should_receive(:simple_date_html).with(time) + simple_date(time) + end + + it 'formats a date in the specified format' do + time = Time.utc(2012, 11, 07, 21, 30, 26) + self.should_receive(:simple_date_text).with(time) + simple_date(time, :format => :text) + end + + it 'raises an argument error if given an unrecognized format' do + time = Time.utc(2012, 11, 07, 21, 30, 26) + expect { simple_date(time, :format => :unknown) }.to raise_error(ArgumentError) + end + + end + + describe :simple_date_html do + + it 'formats a date in a time tag' do + Time.use_zone('London') do + time = Time.utc(2012, 11, 07, 21, 30, 26) + expected = %Q(<time datetime="2012-11-07T21:30:26+00:00" title="2012-11-07 21:30:26 +0000">November 07, 2012</time>) + simple_date_html(time).should == expected + end + end + + end + + describe :simple_date_text do + + it 'should respect time zones' do + Time.use_zone('Australia/Sydney') do + simple_date_text(Time.utc(2012, 11, 07, 21, 30, 26)).should == 'November 08, 2012' + end + end + + it 'should handle Date objects' do + simple_date_text(Date.new(2012, 11, 21)).should == 'November 21, 2012' + end + + end + + describe :simple_time do + + it 'returns 00:00:00 for a date' do + simple_time(Date.new(2012, 11, 21)).should == '00:00:00' + end + + it 'returns the time component of a datetime' do + date = DateTime.new(2012, 11, 21, 10, 34, 56) + simple_time(date).should == '10:34:56' + end + + it 'returns the time component of a time' do + time = Time.utc(2000, 'jan', 1, 20, 15, 1) + simple_time(time).should == '20:15:01' + end + + end +end diff --git a/spec/helpers/link_to_helper_spec.rb b/spec/helpers/link_to_helper_spec.rb index 4a01ec683..b11c35056 100644 --- a/spec/helpers/link_to_helper_spec.rb +++ b/spec/helpers/link_to_helper_spec.rb @@ -20,6 +20,82 @@ describe LinkToHelper do end + describe 'when linking to new incoming messages' do + + before do + @info_request = mock_model(InfoRequest, :id => 123, :url_title => 'test_title') + @incoming_message = mock_model(IncomingMessage, :id => 32, :info_request => @info_request) + end + + context 'for external links' do + + it 'generates the url to the info request of the message' do + incoming_message_url(@incoming_message).should include('http://test.host/request/test_title') + end + + it 'includes an anchor to the new message' do + incoming_message_url(@incoming_message).should include('#incoming-32') + end + + it 'includes does not cache by default' do + incoming_message_url(@incoming_message).should_not include('nocache=incoming-32') + end + + it 'includes a cache busting parameter if set' do + incoming_message_url(@incoming_message, :cachebust => true).should include('nocache=incoming-32') + end + + end + + context 'for internal links' do + + it 'generates the incoming_message_url with the path only' do + expected = '/request/test_title#incoming-32' + incoming_message_path(@incoming_message).should == expected + end + + end + + end + + describe 'when linking to new outgoing messages' do + + before do + @info_request = mock_model(InfoRequest, :id => 123, :url_title => 'test_title') + @outgoing_message = mock_model(OutgoingMessage, :id => 32, :info_request => @info_request) + end + + context 'for external links' do + + it 'generates the url to the info request of the message' do + outgoing_message_url(@outgoing_message).should include('http://test.host/request/test_title') + end + + it 'includes an anchor to the new message' do + outgoing_message_url(@outgoing_message).should include('#outgoing-32') + end + + it 'includes does not cache by default' do + outgoing_message_url(@outgoing_message).should_not include('nocache=outgoing-32') + end + + it 'includes a cache busting parameter if set' do + outgoing_message_url(@outgoing_message, :cachebust => true).should include('nocache=outgoing-32') + end + + end + + context 'for internal links' do + + it 'generates the outgoing_message_url with the path only' do + expected = '/request/test_title#outgoing-32' + outgoing_message_path(@outgoing_message).should == expected + end + + end + + end + describe 'when displaying a user link for a request' do context "for external requests" do @@ -69,51 +145,4 @@ describe LinkToHelper do end - describe 'simple_date' do - - it 'formats a date in html by default' do - time = Time.utc(2012, 11, 07, 21, 30, 26) - self.should_receive(:simple_date_html).with(time) - simple_date(time) - end - - it 'formats a date in the specified format' do - time = Time.utc(2012, 11, 07, 21, 30, 26) - self.should_receive(:simple_date_text).with(time) - simple_date(time, :format => :text) - end - - it 'raises an argument error if given an unrecognized format' do - time = Time.utc(2012, 11, 07, 21, 30, 26) - expect { simple_date(time, :format => :unknown) }.to raise_error(ArgumentError) - end - - end - - describe 'simple_date_html' do - - it 'formats a date in a time tag' do - Time.use_zone('London') do - time = Time.utc(2012, 11, 07, 21, 30, 26) - expected = "<time datetime=\"2012-11-07T21:30:26+00:00\" title=\"2012-11-07 21:30:26 +0000\">November 07, 2012</time>" - simple_date_html(time).should == expected - end - end - - end - - describe 'simple_date_text' do - - it 'should respect time zones' do - Time.use_zone('Australia/Sydney') do - simple_date_text(Time.utc(2012, 11, 07, 21, 30, 26)).should == 'November 08, 2012' - end - end - - it 'should handle Date objects' do - simple_date_text(Date.new(2012, 11, 21)).should == 'November 21, 2012' - end - - end - end diff --git a/spec/lib/attachment_to_html/adapters/rtf_spec.rb b/spec/lib/attachment_to_html/adapters/rtf_spec.rb index a3bf0e27e..2c53b5272 100644 --- a/spec/lib/attachment_to_html/adapters/rtf_spec.rb +++ b/spec/lib/attachment_to_html/adapters/rtf_spec.rb @@ -60,6 +60,17 @@ describe AttachmentToHTML::Adapters::RTF do adapter.body.should_not include('//W3C//DTD HTML 4.01 Transitional//EN') end + it 'converts empty files' do + attachment = FactoryGirl.build(:rtf_attachment, :body => load_file_fixture('empty.rtf')) + adapter = AttachmentToHTML::Adapters::RTF.new(attachment) + adapter.body.should == '' + end + + it 'doesnt fail if the external command returns nil' do + AlaveteliExternalCommand.stub(:run).and_return(nil) + adapter.body.should == '' + end + end diff --git a/spec/models/incoming_message_spec.rb b/spec/models/incoming_message_spec.rb index f06dcbeeb..3b6887f76 100644 --- a/spec/models/incoming_message_spec.rb +++ b/spec/models/incoming_message_spec.rb @@ -112,6 +112,24 @@ describe IncomingMessage, 'when asked if a user can view it' do end +describe 'when destroying a message' do + + before do + @incoming_message = FactoryGirl.create(:plain_incoming_message) + end + + it 'can destroy a message with more than one info request event' do + @info_request = @incoming_message.info_request + @info_request.log_event('response', + :incoming_message_id => @incoming_message.id) + @info_request.log_event('edit_incoming', + :incoming_message_id => @incoming_message.id) + @incoming_message.fully_destroy + IncomingMessage.where(:id => @incoming_message.id).should be_empty + end + +end + describe 'when asked if it is indexed by search' do before do |